diff --git a/substratevm/src/com.oracle.svm.core.jdk16/src/com/oracle/svm/core/jdk16/RecordSupportJDK16OrLater.java b/substratevm/src/com.oracle.svm.core.jdk16/src/com/oracle/svm/core/jdk16/RecordSupportJDK16OrLater.java index 70e543c7697a..48e2adaef83e 100644 --- a/substratevm/src/com.oracle.svm.core.jdk16/src/com/oracle/svm/core/jdk16/RecordSupportJDK16OrLater.java +++ b/substratevm/src/com.oracle.svm.core.jdk16/src/com/oracle/svm/core/jdk16/RecordSupportJDK16OrLater.java @@ -26,14 +26,19 @@ // Checkstyle: allow reflection +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedType; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.RecordComponent; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.util.GuardedAnnotationAccess; import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.jdk.RecordSupport; @@ -70,6 +75,27 @@ public Constructor getCanonicalRecordConstructor(Class clazz) { throw VMError.shouldNotReachHere("Malformed record class that does not declare a canonical constructor: " + clazz.getTypeName()); } } + + @Override + public Map getRecordComponentsAnnotations(Class clazz) { + Map componentAnnotations = new HashMap<>(); + Arrays.stream(clazz.getRecordComponents()) + .forEach(t -> { + componentAnnotations.put(t.getName(), GuardedAnnotationAccess.getAnnotations(t)); + }); + return componentAnnotations; + } + + @Override + public Map getRecordComponentAnnotatedType(Class clazz) { + Map componentAnnotTypes = new HashMap<>(); + Arrays.stream(clazz.getRecordComponents()) + .forEach(t -> { + componentAnnotTypes.put(t.getName(), t.getAnnotatedType()); + }); + return componentAnnotTypes; + } + } @AutomaticFeature diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 32f6f910eb11..75a90a5f6144 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -49,6 +49,7 @@ import java.security.ProtectionDomain; import java.security.cert.Certificate; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.StringJoiner; @@ -67,7 +68,9 @@ import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.Hybrid; +import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.KeepOriginal; +import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; @@ -1024,25 +1027,32 @@ public T getDeclaredAnnotation(Class annotationClass) */ public static final class ReflectionData { static final ReflectionData EMPTY = new ReflectionData(new Field[0], new Field[0], new Field[0], new Method[0], new Method[0], new Constructor[0], new Constructor[0], null, new Field[0], - new Method[0], new Class[0], new Class[0], null, null); + new Method[0], new Class[0], new Class[0], null, null, null, null); public static ReflectionData get(Field[] declaredFields, Field[] publicFields, Field[] publicUnhiddenFields, Method[] declaredMethods, Method[] publicMethods, Constructor[] declaredConstructors, Constructor[] publicConstructors, Constructor nullaryConstructor, Field[] declaredPublicFields, - Method[] declaredPublicMethods, Class[] declaredClasses, Class[] publicClasses, Executable enclosingMethodOrConstructor, Object[] recordComponents) { + Method[] declaredPublicMethods, Class[] declaredClasses, Class[] publicClasses, Executable enclosingMethodOrConstructor, Object[] recordComponents, + Map recordAnnotations, Map recordAnnotatedType) { if (z(declaredFields) && z(publicFields) && z(publicUnhiddenFields) && z(declaredMethods) && z(publicMethods) && z(declaredConstructors) && z(publicConstructors) && nullaryConstructor == null && z(declaredPublicFields) && z(declaredPublicMethods) && z(declaredClasses) && - z(publicClasses) && enclosingMethodOrConstructor == null && (recordComponents == null || z(recordComponents))) { + z(publicClasses) && enclosingMethodOrConstructor == null && (recordComponents == null || z(recordComponents)) && + (recordAnnotations == null || e(recordAnnotations)) && (recordAnnotatedType == null || e(recordAnnotatedType))) { return EMPTY; // avoid redundant objects in image heap } return new ReflectionData(declaredFields, publicFields, publicUnhiddenFields, declaredMethods, publicMethods, declaredConstructors, publicConstructors, nullaryConstructor, - declaredPublicFields, declaredPublicMethods, declaredClasses, publicClasses, enclosingMethodOrConstructor, recordComponents); + declaredPublicFields, declaredPublicMethods, declaredClasses, publicClasses, enclosingMethodOrConstructor, recordComponents, recordAnnotations, + recordAnnotatedType); } private static boolean z(Object[] array) { // for better readability above return array.length == 0; } + private static boolean e(Map map) { + return map.isEmpty(); + } + final Field[] declaredFields; final Field[] publicFields; final Field[] publicUnhiddenFields; @@ -1056,6 +1066,8 @@ private static boolean z(Object[] array) { // for better readability above final Class[] declaredClasses; final Class[] publicClasses; final Object[] recordComponents; + final Map recordAnnotations; // component name => annotations + final Map recordAnnotatedType; // component name => annotated type /** * The result of {@link Class#getEnclosingMethod()} or @@ -1066,7 +1078,7 @@ private static boolean z(Object[] array) { // for better readability above ReflectionData(Field[] declaredFields, Field[] publicFields, Field[] publicUnhiddenFields, Method[] declaredMethods, Method[] publicMethods, Constructor[] declaredConstructors, Constructor[] publicConstructors, Constructor nullaryConstructor, Field[] declaredPublicFields, Method[] declaredPublicMethods, Class[] declaredClasses, Class[] publicClasses, Executable enclosingMethodOrConstructor, - Object[] recordComponents) { + Object[] recordComponents, Map recordAnnotations, Map recordAnnotatedType) { this.declaredFields = declaredFields; this.publicFields = publicFields; this.publicUnhiddenFields = publicUnhiddenFields; @@ -1081,6 +1093,8 @@ private static boolean z(Object[] array) { // for better readability above this.publicClasses = publicClasses; this.enclosingMethodOrConstructor = enclosingMethodOrConstructor; this.recordComponents = recordComponents; + this.recordAnnotations = recordAnnotations; + this.recordAnnotatedType = recordAnnotatedType; } } @@ -1218,11 +1232,21 @@ Method[] privateGetPublicMethods() { @TargetElement(onlyWith = JDK16OrLater.class) private Target_java_lang_reflect_RecordComponent[] getRecordComponents0() { Object[] result = rd.recordComponents; + Map annotations = rd.recordAnnotations; + Map annotatedTypes = rd.recordAnnotatedType; if (result == null) { /* See ReflectionDataBuilder.buildRecordComponents() for details. */ throw VMError.unsupportedFeature("Record components not available for record class " + getTypeName() + ". " + "All record component accessor methods of this record class must be included in the reflection configuration at image build time, then this method can be called."); } + // Set component annotations for the the record-components + for (Object rec : result) { + Target_java_lang_reflect_RecordComponent recordComp = (Target_java_lang_reflect_RecordComponent) rec; + Annotation[] annot = annotations.get(recordComp.getName()); + AnnotatedType type = annotatedTypes.get(recordComp.getName()); + recordComp.componentAnnotations = annot; + recordComp.annotatedType = type; + } return (Target_java_lang_reflect_RecordComponent[]) result; } @@ -1671,4 +1695,41 @@ final class Target_jdk_internal_reflect_ConstantPool { @TargetClass(className = "java.lang.reflect.RecordComponent", onlyWith = JDK16OrLater.class) final class Target_java_lang_reflect_RecordComponent { + + @Inject // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + volatile Annotation[] componentAnnotations; + + @Inject // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + volatile AnnotatedType annotatedType; + + @Substitute // + public Annotation[] getAnnotations() { + return componentAnnotations; + } + + @Substitute // + public Annotation[] getDeclaredAnnotations() { + return componentAnnotations; + } + + @Substitute // + public Annotation getAnnotation(Class annotationClass) { + for (Annotation ann : componentAnnotations) { + if (annotationClass.isAssignableFrom(ann.annotationType())) { + return ann; + } + } + return null; + } + + @Substitute // + public AnnotatedType getAnnotatedType() { + return annotatedType; + } + + @Alias // + public native String getName(); + } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java index 7ed1e196a7fa..50b4cb1fbb72 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java @@ -142,7 +142,8 @@ public ReflectionData getCompleteReflectionData() { completeReflectionData = new ReflectionData(hub.rd.declaredFields, hub.rd.publicFields, hub.rd.publicUnhiddenFields, newDeclaredMethods.toArray(new Method[0]), newPublicMethods.toArray(new Method[0]), newDeclaredConstructors.toArray(new Constructor[0]), newPublicConstructors.toArray(new Constructor[0]), hub.rd.nullaryConstructor, hub.rd.declaredPublicFields, - newDeclaredPublicMethods.toArray(new Method[0]), hub.rd.declaredClasses, hub.rd.publicClasses, hub.rd.enclosingMethodOrConstructor, hub.rd.recordComponents); + newDeclaredPublicMethods.toArray(new Method[0]), hub.rd.declaredClasses, hub.rd.publicClasses, hub.rd.enclosingMethodOrConstructor, + hub.rd.recordComponents, hub.rd.recordAnnotations, hub.rd.recordAnnotatedType); } return completeReflectionData; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecordSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecordSupport.java index ff0c3dfb39e4..e575b88172d5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecordSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecordSupport.java @@ -26,8 +26,11 @@ // Checkstyle: allow reflection +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedType; import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.util.Map; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.ImageSingletons; @@ -60,6 +63,10 @@ public static RecordSupport singleton() { */ public abstract Object[] getRecordComponents(Class clazz); + public abstract Map getRecordComponentsAnnotations(Class clazz); + + public abstract Map getRecordComponentAnnotatedType(Class clazz); + /** * Returns the {@code RecordComponent.getAccessor} method for each of the * {@code Class.getRecordComponents()}. @@ -101,6 +108,16 @@ public Method[] getRecordComponentAccessorMethods(Class clazz) { public Constructor getCanonicalRecordConstructor(Class clazz) { throw VMError.shouldNotReachHere(); } + + @Override + public Map getRecordComponentsAnnotations(Class clazz) { + throw VMError.shouldNotReachHere(); + } + + @Override + public Map getRecordComponentAnnotatedType(Class clazz) { + throw VMError.shouldNotReachHere(); + } } @AutomaticFeature diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java index e7d522fd282e..afd6e2661336 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java @@ -28,6 +28,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; @@ -134,6 +135,8 @@ private static DynamicHub.ReflectionData getArrayReflectionData() { EMPTY_CLASSES, EMPTY_CLASSES, null, + null, + null, null); } @@ -519,7 +522,9 @@ private void processClass(DuringAnalysisAccessImpl access, Class clazz) { filterClasses(declaredClasses, reflectionClasses, access), filterClasses(classes, reflectionClasses, access), enclosingMethodOrConstructor(clazz), - buildRecordComponents(clazz, access)); + buildRecordComponents(clazz, access), + buildRecordComponentAnnotations(clazz), + buildRecordAnnotatedTypes(clazz)); } hub.setReflectionData(reflectionData); } @@ -562,6 +567,22 @@ private Object[] buildRecordComponents(Class clazz, DuringAnalysisAccessImpl } } + private static Map buildRecordComponentAnnotations(Class clazz) { + RecordSupport support = RecordSupport.singleton(); + if (!support.isRecord(clazz)) { + return null; + } + return support.getRecordComponentsAnnotations(clazz); + } + + private static Map buildRecordAnnotatedTypes(Class clazz) { + RecordSupport support = RecordSupport.singleton(); + if (!support.isRecord(clazz)) { + return null; + } + return support.getRecordComponentAnnotatedType(clazz); + } + private static void reportLinkingErrors(Class clazz, List errors) { if (errors.isEmpty()) { return;