diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 998c491855..804c6fa58a 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -34,6 +34,7 @@ dependencies { api("com.google.protobuf:protobuf-java-util:4.28.3") api("com.h2database:h2:2.3.232") api("com.jayway.jsonpath:json-path:2.9.0") + api("com.networknt:json-schema-validator:1.5.3") api("com.oracle.database.jdbc:ojdbc11:21.9.0.0") api("com.rometools:rome:1.19.0") api("com.squareup.okhttp3:mockwebserver:3.14.9") diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index a08d0e1667..ebad4fa180 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -104,6 +104,7 @@ dependencies { testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") testImplementation("org.mockito:mockito-core") + testImplementation("com.networknt:json-schema-validator"); testImplementation("org.skyscreamer:jsonassert") testImplementation("org.xmlunit:xmlunit-assertj") testImplementation("org.xmlunit:xmlunit-matchers") diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java index 5ef7d21e26..d0886760c4 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,7 @@ package org.springframework.aot.nativex; import java.util.function.Consumer; -import org.springframework.aot.hint.ProxyHints; -import org.springframework.aot.hint.ReflectionHints; -import org.springframework.aot.hint.ResourceHints; import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.SerializationHints; /** * Write {@link RuntimeHints} as GraalVM native configuration. @@ -30,8 +26,9 @@ import org.springframework.aot.hint.SerializationHints; * @author Sebastien Deleuze * @author Stephane Nicoll * @author Janne Valkealahti + * @author Brian Clozel * @since 6.0 - * @see Native Image Build Configuration + * @see Native Image Build Configuration */ public abstract class NativeConfigurationWriter { @@ -40,24 +37,21 @@ public abstract class NativeConfigurationWriter { * @param hints the hints to handle */ public void write(RuntimeHints hints) { - if (hints.serialization().javaSerializationHints().findAny().isPresent()) { - writeSerializationHints(hints.serialization()); - } - if (hints.proxies().jdkProxyHints().findAny().isPresent()) { - writeProxyHints(hints.proxies()); - } - if (hints.reflection().typeHints().findAny().isPresent()) { - writeReflectionHints(hints.reflection()); - } - if (hints.resources().resourcePatternHints().findAny().isPresent() || - hints.resources().resourceBundleHints().findAny().isPresent()) { - writeResourceHints(hints.resources()); - } - if (hints.jni().typeHints().findAny().isPresent()) { - writeJniHints(hints.jni()); + if (hasAnyHint(hints)) { + writeTo("reachability-metadata.json", + writer -> new RuntimeHintsWriter().write(writer, hints)); } } + private boolean hasAnyHint(RuntimeHints hints) { + return (hints.serialization().javaSerializationHints().findAny().isPresent() + || hints.proxies().jdkProxyHints().findAny().isPresent() + || hints.reflection().typeHints().findAny().isPresent() + || hints.resources().resourcePatternHints().findAny().isPresent() + || hints.resources().resourceBundleHints().findAny().isPresent() + || hints.jni().typeHints().findAny().isPresent()); + } + /** * Write the specified GraalVM native configuration file, using the * provided {@link BasicJsonWriter}. @@ -66,29 +60,4 @@ public abstract class NativeConfigurationWriter { */ protected abstract void writeTo(String fileName, Consumer writer); - private void writeSerializationHints(SerializationHints hints) { - writeTo("serialization-config.json", writer -> - SerializationHintsWriter.INSTANCE.write(writer, hints)); - } - - private void writeProxyHints(ProxyHints hints) { - writeTo("proxy-config.json", writer -> - ProxyHintsWriter.INSTANCE.write(writer, hints)); - } - - private void writeReflectionHints(ReflectionHints hints) { - writeTo("reflect-config.json", writer -> - ReflectionHintsWriter.INSTANCE.write(writer, hints)); - } - - private void writeResourceHints(ResourceHints hints) { - writeTo("resource-config.json", writer -> - ResourceHintsWriter.INSTANCE.write(writer, hints)); - } - - private void writeJniHints(ReflectionHints hints) { - writeTo("jni-config.json", writer -> - ReflectionHintsWriter.INSTANCE.write(writer, hints)); - } - } diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java deleted file mode 100644 index 51cd3132ef..0000000000 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.aot.nativex; - -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import org.springframework.aot.hint.JdkProxyHint; -import org.springframework.aot.hint.ProxyHints; -import org.springframework.aot.hint.TypeReference; - -/** - * Write {@link JdkProxyHint}s contained in a {@link ProxyHints} to the JSON - * output expected by the GraalVM {@code native-image} compiler, typically named - * {@code proxy-config.json}. - * - * @author Sebastien Deleuze - * @author Stephane Nicoll - * @author Brian Clozel - * @since 6.0 - * @see Dynamic Proxy in Native Image - * @see Native Image Build Configuration - */ -class ProxyHintsWriter { - - public static final ProxyHintsWriter INSTANCE = new ProxyHintsWriter(); - - private static final Comparator JDK_PROXY_HINT_COMPARATOR = - (left, right) -> { - String leftSignature = left.getProxiedInterfaces().stream() - .map(TypeReference::getCanonicalName).collect(Collectors.joining(",")); - String rightSignature = right.getProxiedInterfaces().stream() - .map(TypeReference::getCanonicalName).collect(Collectors.joining(",")); - return leftSignature.compareTo(rightSignature); - }; - - public void write(BasicJsonWriter writer, ProxyHints hints) { - writer.writeArray(hints.jdkProxyHints().sorted(JDK_PROXY_HINT_COMPARATOR) - .map(this::toAttributes).toList()); - } - - private Map toAttributes(JdkProxyHint hint) { - Map attributes = new LinkedHashMap<>(); - handleCondition(attributes, hint); - attributes.put("interfaces", hint.getProxiedInterfaces()); - return attributes; - } - - private void handleCondition(Map attributes, JdkProxyHint hint) { - if (hint.getReachableType() != null) { - Map conditionAttributes = new LinkedHashMap<>(); - conditionAttributes.put("typeReachable", hint.getReachableType()); - attributes.put("condition", conditionAttributes); - } - } - -} diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsAttributes.java similarity index 57% rename from spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java rename to spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsAttributes.java index 3d678b0d85..4d7b2b27a4 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,48 +16,72 @@ package org.springframework.aot.nativex; +import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.aot.hint.ConditionalHint; import org.springframework.aot.hint.ExecutableHint; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.FieldHint; +import org.springframework.aot.hint.JdkProxyHint; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeHint; +import org.springframework.aot.hint.TypeReference; import org.springframework.lang.Nullable; /** - * Write {@link ReflectionHints} to the JSON output expected by the GraalVM - * {@code native-image} compiler, typically named {@code reflect-config.json} - * or {@code jni-config.json}. + * Collect {@link ReflectionHints} as map attributes ready for JSON serialization for the GraalVM + * {@code native-image} compiler. * * @author Sebastien Deleuze * @author Stephane Nicoll * @author Janne Valkealahti - * @since 6.0 - * @see Reflection Use in Native Images - * @see Java Native Interface (JNI) in Native Image - * @see Native Image Build Configuration + * @see Reflection Use in Native Images + * @see Java Native Interface (JNI) in Native Image + * @see Native Image Build Configuration */ -class ReflectionHintsWriter { +class ReflectionHintsAttributes { - public static final ReflectionHintsWriter INSTANCE = new ReflectionHintsWriter(); + private static final Comparator JDK_PROXY_HINT_COMPARATOR = + (left, right) -> { + String leftSignature = left.getProxiedInterfaces().stream() + .map(TypeReference::getCanonicalName).collect(Collectors.joining(",")); + String rightSignature = right.getProxiedInterfaces().stream() + .map(TypeReference::getCanonicalName).collect(Collectors.joining(",")); + return leftSignature.compareTo(rightSignature); + }; - public void write(BasicJsonWriter writer, ReflectionHints hints) { - writer.writeArray(hints.typeHints() + public List> reflection(RuntimeHints hints) { + List> reflectionHints = new ArrayList<>(); + reflectionHints.addAll(hints.reflection().typeHints() .sorted(Comparator.comparing(TypeHint::getType)) .map(this::toAttributes).toList()); + reflectionHints.addAll(hints.proxies().jdkProxyHints() + .sorted(JDK_PROXY_HINT_COMPARATOR) + .map(this::toAttributes).toList()); + return reflectionHints; + } + + public List> jni(RuntimeHints hints) { + List> jniHints = new ArrayList<>(); + jniHints.addAll(hints.jni().typeHints() + .sorted(Comparator.comparing(TypeHint::getType)) + .map(this::toAttributes).toList()); + return jniHints; } private Map toAttributes(TypeHint hint) { Map attributes = new LinkedHashMap<>(); - attributes.put("name", hint.getType()); + attributes.put("type", hint.getType()); handleCondition(attributes, hint); handleCategories(attributes, hint.getMemberCategories()); handleFields(attributes, hint.fields()); @@ -66,33 +90,23 @@ class ReflectionHintsWriter { return attributes; } - private void handleCondition(Map attributes, TypeHint hint) { + private void handleCondition(Map attributes, ConditionalHint hint) { if (hint.getReachableType() != null) { - Map conditionAttributes = new LinkedHashMap<>(); - conditionAttributes.put("typeReachable", hint.getReachableType()); - attributes.put("condition", conditionAttributes); + attributes.put("condition", Map.of("typeReached", hint.getReachableType())); } } private void handleFields(Map attributes, Stream fields) { addIfNotEmpty(attributes, "fields", fields .sorted(Comparator.comparing(FieldHint::getName, String::compareToIgnoreCase)) - .map(this::toAttributes).toList()); - } - - private Map toAttributes(FieldHint hint) { - Map attributes = new LinkedHashMap<>(); - attributes.put("name", hint.getName()); - return attributes; + .map(fieldHint -> Map.of("name", fieldHint.getName())) + .toList()); } private void handleExecutables(Map attributes, List hints) { addIfNotEmpty(attributes, "methods", hints.stream() .filter(h -> h.getMode().equals(ExecutableMode.INVOKE)) .map(this::toAttributes).toList()); - addIfNotEmpty(attributes, "queriedMethods", hints.stream() - .filter(h -> h.getMode().equals(ExecutableMode.INTROSPECT)) - .map(this::toAttributes).toList()); } private Map toAttributes(ExecutableHint hint) { @@ -102,28 +116,19 @@ class ReflectionHintsWriter { return attributes; } + @SuppressWarnings("removal") private void handleCategories(Map attributes, Set categories) { categories.stream().sorted().forEach(category -> { switch (category) { - case PUBLIC_FIELDS -> attributes.put("allPublicFields", true); - case DECLARED_FIELDS -> attributes.put("allDeclaredFields", true); - case INTROSPECT_PUBLIC_CONSTRUCTORS -> - attributes.put("queryAllPublicConstructors", true); - case INTROSPECT_DECLARED_CONSTRUCTORS -> - attributes.put("queryAllDeclaredConstructors", true); + case INVOKE_PUBLIC_FIELDS, PUBLIC_FIELDS -> attributes.put("allPublicFields", true); + case INVOKE_DECLARED_FIELDS, DECLARED_FIELDS -> attributes.put("allDeclaredFields", true); case INVOKE_PUBLIC_CONSTRUCTORS -> attributes.put("allPublicConstructors", true); case INVOKE_DECLARED_CONSTRUCTORS -> attributes.put("allDeclaredConstructors", true); - case INTROSPECT_PUBLIC_METHODS -> - attributes.put("queryAllPublicMethods", true); - case INTROSPECT_DECLARED_METHODS -> - attributes.put("queryAllDeclaredMethods", true); case INVOKE_PUBLIC_METHODS -> attributes.put("allPublicMethods", true); case INVOKE_DECLARED_METHODS -> attributes.put("allDeclaredMethods", true); - case PUBLIC_CLASSES -> attributes.put("allPublicClasses", true); - case DECLARED_CLASSES -> attributes.put("allDeclaredClasses", true); } } ); @@ -135,4 +140,11 @@ class ReflectionHintsWriter { } } + private Map toAttributes(JdkProxyHint hint) { + Map attributes = new LinkedHashMap<>(); + handleCondition(attributes, hint); + attributes.put("type", Map.of("proxy", hint.getProxiedInterfaces())); + return attributes; + } + } diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java similarity index 68% rename from spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java rename to spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java index 6829006e90..519a986fd9 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java @@ -21,7 +21,6 @@ import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; import org.springframework.aot.hint.ConditionalHint; import org.springframework.aot.hint.ResourceBundleHint; @@ -31,8 +30,8 @@ import org.springframework.aot.hint.ResourcePatternHints; import org.springframework.lang.Nullable; /** - * Write a {@link ResourceHints} to the JSON output expected by the GraalVM - * {@code native-image} compiler, typically named {@code resource-config.json}. + * Collect {@link ResourceHints} as map attributes ready for JSON serialization for the GraalVM + * {@code native-image} compiler. * * @author Sebastien Deleuze * @author Stephane Nicoll @@ -41,9 +40,7 @@ import org.springframework.lang.Nullable; * @see Accessing Resources in Native Images * @see Native Image Build Configuration */ -class ResourceHintsWriter { - - public static final ResourceHintsWriter INSTANCE = new ResourceHintsWriter(); +class ResourceHintsAttributes { private static final Comparator RESOURCE_PATTERN_HINT_COMPARATOR = Comparator.comparing(ResourcePatternHint::getPattern); @@ -52,30 +49,17 @@ class ResourceHintsWriter { Comparator.comparing(ResourceBundleHint::getBaseName); - public void write(BasicJsonWriter writer, ResourceHints hints) { - Map attributes = new LinkedHashMap<>(); - addIfNotEmpty(attributes, "resources", toAttributes(hints)); - handleResourceBundles(attributes, hints.resourceBundleHints()); - writer.writeObject(attributes); - } - - private Map toAttributes(ResourceHints hint) { - Map attributes = new LinkedHashMap<>(); - addIfNotEmpty(attributes, "includes", hint.resourcePatternHints() + public List> resources(ResourceHints hint) { + return hint.resourcePatternHints() .map(ResourcePatternHints::getIncludes).flatMap(List::stream).distinct() .sorted(RESOURCE_PATTERN_HINT_COMPARATOR) - .map(this::toAttributes).toList()); - addIfNotEmpty(attributes, "excludes", hint.resourcePatternHints() - .map(ResourcePatternHints::getExcludes).flatMap(List::stream).distinct() - .sorted(RESOURCE_PATTERN_HINT_COMPARATOR) - .map(this::toAttributes).toList()); - return attributes; + .map(this::toAttributes).toList(); } - private void handleResourceBundles(Map attributes, Stream resourceBundles) { - addIfNotEmpty(attributes, "bundles", resourceBundles + public List> resourceBundles(ResourceHints hint) { + return hint.resourceBundleHints() .sorted(RESOURCE_BUNDLE_HINT_COMPARATOR) - .map(this::toAttributes).toList()); + .map(this::toAttributes).toList(); } private Map toAttributes(ResourceBundleHint hint) { @@ -88,7 +72,7 @@ class ResourceHintsWriter { private Map toAttributes(ResourcePatternHint hint) { Map attributes = new LinkedHashMap<>(); handleCondition(attributes, hint); - attributes.put("pattern", hint.toRegex().toString()); + attributes.put("glob", hint.getPattern()); return attributes; } @@ -111,7 +95,7 @@ class ResourceHintsWriter { private void handleCondition(Map attributes, ConditionalHint hint) { if (hint.getReachableType() != null) { Map conditionAttributes = new LinkedHashMap<>(); - conditionAttributes.put("typeReachable", hint.getReachableType()); + conditionAttributes.put("typeReached", hint.getReachableType()); attributes.put("condition", conditionAttributes); } } diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/RuntimeHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/RuntimeHintsWriter.java new file mode 100644 index 0000000000..782497dee8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/nativex/RuntimeHintsWriter.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.aot.nativex; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.core.SpringVersion; + +/** + * Write a {@link RuntimeHints} instance to the JSON output expected by the + * GraalVM {@code native-image} compiler, typically named {@code reachability-metadata.json}. + * + * @author Brian Clozel + * @since 7.0 + * @see GraalVM Reachability Metadata + */ +class RuntimeHintsWriter { + + public void write(BasicJsonWriter writer, RuntimeHints hints) { + Map document = new LinkedHashMap<>(); + String springVersion = SpringVersion.getVersion(); + if (springVersion != null) { + document.put("comment", "Spring Framework " + springVersion); + } + List> reflection = new ReflectionHintsAttributes().reflection(hints); + if (!reflection.isEmpty()) { + document.put("reflection", reflection); + } + List> jni = new ReflectionHintsAttributes().jni(hints); + if (!jni.isEmpty()) { + document.put("jni", jni); + } + List> resourceHints = new ResourceHintsAttributes().resources(hints.resources()); + if (!resourceHints.isEmpty()) { + document.put("resources", resourceHints); + } + List> resourceBundles = new ResourceHintsAttributes().resourceBundles(hints.resources()); + if (!resourceBundles.isEmpty()) { + document.put("bundles", resourceBundles); + } + List> serialization = new SerializationHintsAttributes().toAttributes(hints.serialization()); + if (!serialization.isEmpty()) { + document.put("serialization", serialization); + } + + writer.writeObject(document); + } + +} diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsAttributes.java similarity index 70% rename from spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsWriter.java rename to spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsAttributes.java index 73b2524851..d9ca1d6d5f 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsAttributes.java @@ -18,6 +18,7 @@ package org.springframework.aot.nativex; import java.util.Comparator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.springframework.aot.hint.ConditionalHint; @@ -25,40 +26,36 @@ import org.springframework.aot.hint.JavaSerializationHint; import org.springframework.aot.hint.SerializationHints; /** - * Write a {@link SerializationHints} to the JSON output expected by the - * GraalVM {@code native-image} compiler, typically named - * {@code serialization-config.json}. + * Collect {@link SerializationHints} as map attributes ready for JSON serialization for the GraalVM + * {@code native-image} compiler. * * @author Sebastien Deleuze * @author Stephane Nicoll * @author Brian Clozel - * @since 6.0 - * @see Native Image Build Configuration + * @see Native Image Build Configuration */ -class SerializationHintsWriter { - - public static final SerializationHintsWriter INSTANCE = new SerializationHintsWriter(); +class SerializationHintsAttributes { private static final Comparator JAVA_SERIALIZATION_HINT_COMPARATOR = Comparator.comparing(JavaSerializationHint::getType); - public void write(BasicJsonWriter writer, SerializationHints hints) { - writer.writeArray(hints.javaSerializationHints() + public List> toAttributes(SerializationHints hints) { + return hints.javaSerializationHints() .sorted(JAVA_SERIALIZATION_HINT_COMPARATOR) - .map(this::toAttributes).toList()); + .map(this::toAttributes).toList(); } private Map toAttributes(JavaSerializationHint serializationHint) { LinkedHashMap attributes = new LinkedHashMap<>(); handleCondition(attributes, serializationHint); - attributes.put("name", serializationHint.getType()); + attributes.put("type", serializationHint.getType()); return attributes; } private void handleCondition(Map attributes, ConditionalHint hint) { if (hint.getReachableType() != null) { Map conditionAttributes = new LinkedHashMap<>(); - conditionAttributes.put("typeReachable", hint.getReachableType()); + conditionAttributes.put("typeReached", hint.getReachableType()); attributes.put("condition", conditionAttributes); } } diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java index db6d82a421..3874d9f967 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,6 @@ import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; import java.util.function.Consumer; import java.util.function.Function; @@ -40,7 +38,6 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.SerializationHints; import org.springframework.aot.hint.TypeReference; import org.springframework.core.codec.StringDecoder; -import org.springframework.util.MimeType; import static org.assertj.core.api.Assertions.assertThat; @@ -74,10 +71,13 @@ class FileNativeConfigurationWriterTests { serializationHints.registerType(Long.class); generator.write(hints); assertEquals(""" - [ - { "name": "java.lang.Integer" }, - { "name": "java.lang.Long" } - ]""", "serialization-config.json"); + { + "serialization": [ + { "type": "java.lang.Integer" }, + { "type": "java.lang.Long" } + ] + } + """); } @Test @@ -89,10 +89,13 @@ class FileNativeConfigurationWriterTests { proxyHints.registerJdkProxy(Function.class, Consumer.class); generator.write(hints); assertEquals(""" - [ - { "interfaces": [ "java.util.function.Function" ] }, - { "interfaces": [ "java.util.function.Function", "java.util.function.Consumer" ] } - ]""", "proxy-config.json"); + { + "reflection": [ + { type: {"proxy": [ "java.util.function.Function" ] } }, + { type: {"proxy": [ "java.util.function.Function", "java.util.function.Consumer" ] } } + ] + } + """); } @Test @@ -102,48 +105,36 @@ class FileNativeConfigurationWriterTests { ReflectionHints reflectionHints = hints.reflection(); reflectionHints.registerType(StringDecoder.class, builder -> builder .onReachableType(String.class) - .withMembers(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS, - MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, + .withMembers(MemberCategory.INVOKE_PUBLIC_FIELDS, MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES) + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS) .withField("DEFAULT_CHARSET") .withField("defaultCharset") - .withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT) - .withMethod("setDefaultCharset", TypeReference.listOf(Charset.class), ExecutableMode.INVOKE) - .withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT)); + .withMethod("setDefaultCharset", TypeReference.listOf(Charset.class), ExecutableMode.INVOKE)); generator.write(hints); assertEquals(""" - [ - { - "name": "org.springframework.core.codec.StringDecoder", - "condition": { "typeReachable": "java.lang.String" }, - "allPublicFields": true, - "allDeclaredFields": true, - "queryAllPublicConstructors": true, - "queryAllDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredConstructors": true, - "queryAllPublicMethods": true, - "queryAllDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredMethods": true, - "allPublicClasses": true, - "allDeclaredClasses": true, - "fields": [ - { "name": "DEFAULT_CHARSET" }, - { "name": "defaultCharset" } - ], - "methods": [ - { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] } - ], - "queriedMethods": [ - { "name": "", "parameterTypes": [ "java.util.List", "boolean", "org.springframework.util.MimeType" ] }, - { "name": "getDefaultCharset", "parameterTypes": [ ] } - ] - } - ]""", "reflect-config.json"); + { + "reflection": [ + { + "type": "org.springframework.core.codec.StringDecoder", + "condition": { "typeReached": "java.lang.String" }, + "allPublicFields": true, + "allDeclaredFields": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "fields": [ + { "name": "DEFAULT_CHARSET" }, + { "name": "defaultCharset" } + ], + "methods": [ + { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] } + ] + } + ] + } + """); } @Test @@ -155,12 +146,14 @@ class FileNativeConfigurationWriterTests { jniHints.registerType(StringDecoder.class, builder -> builder.onReachableType(String.class)); generator.write(hints); assertEquals(""" - [ - { - "name": "org.springframework.core.codec.StringDecoder", - "condition": { "typeReachable": "java.lang.String" } - } - ]""", "jni-config.json"); + { + "jni": [ + { + "type": "org.springframework.core.codec.StringDecoder", + "condition": { "typeReached": "java.lang.String" } + } + ] + }"""); } @Test @@ -173,23 +166,21 @@ class FileNativeConfigurationWriterTests { generator.write(hints); assertEquals(""" { - "resources": { - "includes": [ - {"pattern": "\\\\Qcom/example/test.properties\\\\E"}, - {"pattern": "\\\\Q/\\\\E"}, - {"pattern": "\\\\Qcom\\\\E"}, - {"pattern": "\\\\Qcom/example\\\\E"}, - {"pattern": "\\\\Qcom/example/another.properties\\\\E"} - ] - } - }""", "resource-config.json"); + "resources": [ + {"glob": "com/example/test.properties"}, + {"glob": "/"}, + {"glob": "com"}, + {"glob": "com/example"}, + {"glob": "com/example/another.properties"} + ] + }"""); } @Test void namespace() { String groupId = "foo.bar"; String artifactId = "baz"; - String filename = "resource-config.json"; + String filename = "reachability-metadata.json"; FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir, groupId, artifactId); RuntimeHints hints = new RuntimeHints(); ResourceHints resourceHints = hints.resources(); @@ -199,8 +190,8 @@ class FileNativeConfigurationWriterTests { assertThat(jsonFile.toFile()).exists(); } - private void assertEquals(String expectedString, String filename) throws IOException, JSONException { - Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve(filename); + private void assertEquals(String expectedString) throws IOException, JSONException { + Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve("reachability-metadata.json"); String content = Files.readString(jsonFile); JSONAssert.assertEquals(expectedString, content, JSONCompareMode.NON_EXTENSIBLE); } diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java deleted file mode 100644 index 6a65db7e9d..0000000000 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.aot.nativex; - -import java.io.StringWriter; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.aot.hint.ProxyHints; -import org.springframework.aot.hint.TypeReference; - -/** - * Tests for {@link ProxyHintsWriter}. - * - * @author Sebastien Deleuze - * @author Stephane Nicoll - */ -class ProxyHintsWriterTests { - - @Test - void empty() throws JSONException { - ProxyHints hints = new ProxyHints(); - assertEquals("[]", hints); - } - - @Test - void shouldWriteOneEntry() throws JSONException { - ProxyHints hints = new ProxyHints(); - hints.registerJdkProxy(Function.class); - assertEquals(""" - [ - { "interfaces": [ "java.util.function.Function" ] } - ]""", hints); - } - - @Test - void shouldWriteMultipleEntries() throws JSONException { - ProxyHints hints = new ProxyHints(); - hints.registerJdkProxy(Function.class); - hints.registerJdkProxy(Function.class, Consumer.class); - assertEquals(""" - [ - { "interfaces": [ "java.util.function.Function" ] }, - { "interfaces": [ "java.util.function.Function", "java.util.function.Consumer" ] } - ]""", hints); - } - - @Test - void shouldWriteEntriesInNaturalOrder() throws JSONException { - ProxyHints hints = new ProxyHints(); - hints.registerJdkProxy(Supplier.class); - hints.registerJdkProxy(Function.class); - assertEquals(""" - [ - { "interfaces": [ "java.util.function.Function" ] }, - { "interfaces": [ "java.util.function.Supplier" ] } - ]""", hints); - } - - @Test - void shouldWriteInnerClass() throws JSONException { - ProxyHints hints = new ProxyHints(); - hints.registerJdkProxy(Inner.class); - assertEquals(""" - [ - { "interfaces": [ "org.springframework.aot.nativex.ProxyHintsWriterTests$Inner" ] } - ]""", hints); - } - - @Test - void shouldWriteCondition() throws JSONException { - ProxyHints hints = new ProxyHints(); - hints.registerJdkProxy(builder -> builder.proxiedInterfaces(Function.class) - .onReachableType(TypeReference.of("org.example.Test"))); - assertEquals(""" - [ - { "condition": { "typeReachable": "org.example.Test"}, "interfaces": [ "java.util.function.Function" ] } - ]""", hints); - } - - private void assertEquals(String expectedString, ProxyHints hints) throws JSONException { - StringWriter out = new StringWriter(); - BasicJsonWriter writer = new BasicJsonWriter(out, "\t"); - ProxyHintsWriter.INSTANCE.write(writer, hints); - JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT); - } - - interface Inner { - - } - -} diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java deleted file mode 100644 index c9fb6901d7..0000000000 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.aot.nativex; - -import java.io.StringWriter; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.List; - -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.aot.hint.ExecutableMode; -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.ReflectionHints; -import org.springframework.aot.hint.TypeReference; -import org.springframework.core.codec.StringDecoder; -import org.springframework.util.MimeType; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ReflectionHintsWriter}. - * - * @author Sebastien Deleuze - * @author Stephane Nicoll - */ -class ReflectionHintsWriterTests { - - @Test - void empty() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - assertEquals("[]", hints); - } - - @Test - void one() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(StringDecoder.class, builder -> builder - .onReachableType(String.class) - .withMembers(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS, - MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, - MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES) - .withField("DEFAULT_CHARSET") - .withField("defaultCharset") - .withField("aScore") - .withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT) - .withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ExecutableMode.INVOKE) - .withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT)); - assertEquals(""" - [ - { - "name": "org.springframework.core.codec.StringDecoder", - "condition": { "typeReachable": "java.lang.String" }, - "allPublicFields": true, - "allDeclaredFields": true, - "queryAllPublicConstructors": true, - "queryAllDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredConstructors": true, - "queryAllPublicMethods": true, - "queryAllDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredMethods": true, - "allPublicClasses": true, - "allDeclaredClasses": true, - "fields": [ - { "name": "aScore" }, - { "name": "DEFAULT_CHARSET" }, - { "name": "defaultCharset" } - ], - "methods": [ - { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] } - ], - "queriedMethods": [ - { "name": "", "parameterTypes": [ "java.util.List", "boolean", "org.springframework.util.MimeType" ] }, - { "name": "getDefaultCharset", "parameterTypes": [ ] } - ] - } - ]""", hints); - } - - @Test - void two() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> { - }); - hints.registerType(Long.class, builder -> { - }); - - assertEquals(""" - [ - { "name": "java.lang.Integer" }, - { "name": "java.lang.Long" } - ]""", hints); - } - - @Test - void queriedMethods() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class), ExecutableMode.INTROSPECT)); - - assertEquals(""" - [ - { - "name": "java.lang.Integer", - "queriedMethods": [ - { - "name": "parseInt", - "parameterTypes": ["java.lang.String"] - } - ] - } - ] - """, hints); - } - - @Test - void methods() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class), ExecutableMode.INVOKE)); - - assertEquals(""" - [ - { - "name": "java.lang.Integer", - "methods": [ - { - "name": "parseInt", - "parameterTypes": ["java.lang.String"] - } - ] - } - ] - """, hints); - } - - @Test - void methodWithInnerClassParameter() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> builder.withMethod("test", - TypeReference.listOf(Inner.class), ExecutableMode.INVOKE)); - - assertEquals(""" - [ - { - "name": "java.lang.Integer", - "methods": [ - { - "name": "test", - "parameterTypes": ["org.springframework.aot.nativex.ReflectionHintsWriterTests$Inner"] - } - ] - } - ] - """, hints); - } - - @Test - void methodAndQueriedMethods() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class), ExecutableMode.INVOKE)); - hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class, int.class), ExecutableMode.INTROSPECT)); - - assertEquals(""" - [ - { - "name": "java.lang.Integer", - "queriedMethods": [ - { - "name": "parseInt", - "parameterTypes": ["java.lang.String", "int"] - } - ], - "methods": [ - { - "name": "parseInt", - "parameterTypes": ["java.lang.String"] - } - ] - } - ] - """, hints); - } - - @Test - void ignoreLambda() throws JSONException { - Runnable anonymousRunnable = () -> {}; - ReflectionHints hints = new ReflectionHints(); - hints.registerType(anonymousRunnable.getClass()); - assertEquals("[]", hints); - } - - @Test - void sortTypeHints() { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> {}); - hints.registerType(Long.class, builder -> {}); - - ReflectionHints hints2 = new ReflectionHints(); - hints2.registerType(Long.class, builder -> {}); - hints2.registerType(Integer.class, builder -> {}); - - assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); - } - - @Test - void sortFieldHints() { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> { - builder.withField("first"); - builder.withField("second"); - }); - ReflectionHints hints2 = new ReflectionHints(); - hints2.registerType(Integer.class, builder -> { - builder.withField("second"); - builder.withField("first"); - }); - assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); - } - - @Test - void sortConstructorHints() { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> { - builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE); - builder.withConstructor(List.of(TypeReference.of(String.class), - TypeReference.of(Integer.class)), ExecutableMode.INVOKE); - }); - - ReflectionHints hints2 = new ReflectionHints(); - hints2.registerType(Integer.class, builder -> { - builder.withConstructor(List.of(TypeReference.of(String.class), - TypeReference.of(Integer.class)), ExecutableMode.INVOKE); - builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE); - }); - assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); - } - - @Test - void sortMethodHints() { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> { - builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE); - builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE); - }); - - ReflectionHints hints2 = new ReflectionHints(); - hints2.registerType(Integer.class, builder -> { - builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE); - builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE); - }); - assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); - } - - private void assertEquals(String expectedString, ReflectionHints hints) throws JSONException { - JSONAssert.assertEquals(expectedString, writeJson(hints), JSONCompareMode.STRICT); - } - - private String writeJson(ReflectionHints hints) { - StringWriter out = new StringWriter(); - BasicJsonWriter writer = new BasicJsonWriter(out, "\t"); - ReflectionHintsWriter.INSTANCE.write(writer, hints); - return out.toString(); - } - - - static class Inner { - - } - -} diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java deleted file mode 100644 index b3fef587ef..0000000000 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.aot.nativex; - -import java.io.StringWriter; - -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.aot.hint.ResourceHints; -import org.springframework.aot.hint.TypeReference; - -/** - * Tests for {@link ResourceHintsWriter}. - * - * @author Sebastien Deleuze - * @author Brian Clozel - */ -class ResourceHintsWriterTests { - - @Test - void empty() throws JSONException { - ResourceHints hints = new ResourceHints(); - assertEquals("{}", hints); - } - - @Test - void registerExactMatch() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern("com/example/test.properties"); - hints.registerPattern("com/example/another.properties"); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": "\\\\Q/\\\\E" }, - { "pattern": "\\\\Qcom\\\\E"}, - { "pattern": "\\\\Qcom/example\\\\E"}, - { "pattern": "\\\\Qcom/example/another.properties\\\\E"}, - { "pattern": "\\\\Qcom/example/test.properties\\\\E"} - ] - } - }""", hints); - } - - @Test - void registerWildcardAtTheBeginningPattern() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern("*.properties"); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": ".*\\\\Q.properties\\\\E"}, - { "pattern": "\\\\Q\\/\\\\E"} - ] - } - }""", hints); - } - - @Test - void registerWildcardInTheMiddlePattern() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern("com/example/*.properties"); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": "\\\\Q/\\\\E" }, - { "pattern": "\\\\Qcom\\\\E"}, - { "pattern": "\\\\Qcom/example\\\\E"}, - { "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"} - ] - } - }""", hints); - } - - @Test - void registerWildcardAtTheEndPattern() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern("static/*"); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": "\\\\Q/\\\\E" }, - { "pattern": "\\\\Qstatic\\\\E"}, - { "pattern": "\\\\Qstatic/\\\\E.*"} - ] - } - }""", hints); - } - - @Test - void registerPatternWithIncludesAndExcludes() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern(hint -> hint.includes("com/example/*.properties").excludes("com/example/to-ignore.properties")); - hints.registerPattern(hint -> hint.includes("org/other/*.properties").excludes("org/other/to-ignore.properties")); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": "\\\\Q/\\\\E"}, - { "pattern": "\\\\Qcom\\\\E"}, - { "pattern": "\\\\Qcom/example\\\\E"}, - { "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"}, - { "pattern": "\\\\Qorg\\\\E"}, - { "pattern": "\\\\Qorg/other\\\\E"}, - { "pattern": "\\\\Qorg/other/\\\\E.*\\\\Q.properties\\\\E"} - ], - "excludes": [ - { "pattern": "\\\\Qcom/example/to-ignore.properties\\\\E"}, - { "pattern": "\\\\Qorg/other/to-ignore.properties\\\\E"} - ] - } - }""", hints); - } - - @Test - void registerWithReachableTypeCondition() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern(builder -> builder.includes(TypeReference.of("com.example.Test"), "com/example/test.properties")); - assertEquals(""" - { - "resources": { - "includes": [ - { "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Q/\\\\E"}, - { "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom\\\\E"}, - { "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example\\\\E"}, - { "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example/test.properties\\\\E"} - ] - } - }""", hints); - } - - @Test - void registerType() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerType(String.class); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": "\\\\Q/\\\\E" }, - { "pattern": "\\\\Qjava\\\\E" }, - { "pattern": "\\\\Qjava/lang\\\\E" }, - { "pattern": "\\\\Qjava/lang/String.class\\\\E" } - ] - } - }""", hints); - } - - @Test - void registerResourceBundle() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerResourceBundle("com.example.message2"); - hints.registerResourceBundle("com.example.message"); - assertEquals(""" - { - "bundles": [ - { "name": "com.example.message"}, - { "name": "com.example.message2"} - ] - }""", hints); - } - - private void assertEquals(String expectedString, ResourceHints hints) throws JSONException { - StringWriter out = new StringWriter(); - BasicJsonWriter writer = new BasicJsonWriter(out, "\t"); - ResourceHintsWriter.INSTANCE.write(writer, hints); - JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT); - } - -} diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/RuntimeHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/RuntimeHintsWriterTests.java new file mode 100644 index 0000000000..e0f832d35a --- /dev/null +++ b/spring-core/src/test/java/org/springframework/aot/nativex/RuntimeHintsWriterTests.java @@ -0,0 +1,594 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.aot.nativex; + +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import com.networknt.schema.InputFormat; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaValidatorsConfig; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; +import org.json.JSONException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + +import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.core.codec.StringDecoder; +import org.springframework.core.env.Environment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RuntimeHintsWriter}. + * + * @author Brian Clozel + * @author Sebastien Deleuze + * @author Stephane Nicoll + */ +class RuntimeHintsWriterTests { + + private static JsonSchema JSON_SCHEMA; + + @BeforeAll + static void setupSchemaValidator() { + JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909, builder -> + builder.schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://www.graalvm.org/", "classpath:org/springframework/aot/nativex/")) + ); + SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().build(); + JSON_SCHEMA = jsonSchemaFactory.getSchema(SchemaLocation.of("https://www.graalvm.org/reachability-metadata-schema-v1.0.0.json"), config); + } + + @Nested + class ReflectionHintsTests { + + @Test + void empty() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + assertEquals("{}", hints); + } + + @Test + void one() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(StringDecoder.class, builder -> builder + .onReachableType(String.class) + .withMembers(MemberCategory.INVOKE_PUBLIC_FIELDS, MemberCategory.INVOKE_DECLARED_FIELDS, + MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS) + .withField("DEFAULT_CHARSET") + .withField("defaultCharset") + .withField("aScore") + .withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ExecutableMode.INVOKE)); + assertEquals(""" + { + "reflection": [ + { + "type": "org.springframework.core.codec.StringDecoder", + "condition": { "typeReached": "java.lang.String" }, + "allPublicFields": true, + "allDeclaredFields": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "fields": [ + { "name": "aScore" }, + { "name": "DEFAULT_CHARSET" }, + { "name": "defaultCharset" } + ], + "methods": [ + { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] } + ] + } + ] + } + """, hints); + } + + @Test + void two() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> { + }); + hints.reflection().registerType(Long.class, builder -> { + }); + + assertEquals(""" + { + "reflection": [ + { "type": "java.lang.Integer" }, + { "type": "java.lang.Long" } + ] + } + """, hints); + } + + @Test + void methods() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> builder.withMethod("parseInt", + TypeReference.listOf(String.class), ExecutableMode.INVOKE)); + + assertEquals(""" + { + "reflection": [ + { + "type": "java.lang.Integer", + "methods": [ + { + "name": "parseInt", + "parameterTypes": ["java.lang.String"] + } + ] + } + ] + } + """, hints); + } + + @Test + void methodWithInnerClassParameter() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> builder.withMethod("test", + TypeReference.listOf(InnerClass.class), ExecutableMode.INVOKE)); + + assertEquals(""" + { + "reflection": [ + { + "type": "java.lang.Integer", + "methods": [ + { + "name": "test", + "parameterTypes": ["org.springframework.aot.nativex.RuntimeHintsWriterTests$InnerClass"] + } + ] + } + ] + } + """, hints); + } + + @Test + void methodAndQueriedMethods() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> builder.withMethod("parseInt", + TypeReference.listOf(String.class), ExecutableMode.INVOKE)); + + assertEquals(""" + { + "reflection": [ + { + "type": "java.lang.Integer", + "methods": [ + { + "name": "parseInt", + "parameterTypes": ["java.lang.String"] + } + ] + } + ] + } + """, hints); + } + + @Test + void ignoreLambda() throws JSONException { + Runnable anonymousRunnable = () -> {}; + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(anonymousRunnable.getClass()); + assertEquals("{}", hints); + } + + @Test + void sortTypeHints() { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> {}); + hints.reflection().registerType(Long.class, builder -> {}); + + RuntimeHints hints2 = new RuntimeHints(); + hints2.reflection().registerType(Long.class, builder -> {}); + hints2.reflection().registerType(Integer.class, builder -> {}); + + assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); + } + + @Test + void sortFieldHints() { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> { + builder.withField("first"); + builder.withField("second"); + }); + RuntimeHints hints2 = new RuntimeHints(); + hints2.reflection().registerType(Integer.class, builder -> { + builder.withField("second"); + builder.withField("first"); + }); + assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); + } + + @Test + void sortConstructorHints() { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> { + builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE); + builder.withConstructor(List.of(TypeReference.of(String.class), + TypeReference.of(Integer.class)), ExecutableMode.INVOKE); + }); + + RuntimeHints hints2 = new RuntimeHints(); + hints2.reflection().registerType(Integer.class, builder -> { + builder.withConstructor(List.of(TypeReference.of(String.class), + TypeReference.of(Integer.class)), ExecutableMode.INVOKE); + builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE); + }); + assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); + } + + @Test + void sortMethodHints() { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> { + builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE); + builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE); + }); + + RuntimeHints hints2 = new RuntimeHints(); + hints2.reflection().registerType(Integer.class, builder -> { + builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE); + builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE); + }); + assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); + } + + } + + + @Nested + class JniHints { + + // TODO + + } + + + @Nested + class ResourceHintsTests { + + @Test + void empty() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + assertEquals("{}", hints); + } + + @Test + void registerExactMatch() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern("com/example/test.properties"); + hints.resources().registerPattern("com/example/another.properties"); + assertEquals(""" + { + "resources": [ + { "glob": "/" }, + { "glob": "com"}, + { "glob": "com/example"}, + { "glob": "com/example/another.properties"}, + { "glob": "com/example/test.properties"} + ] + }""", hints); + } + + @Test + void registerWildcardAtTheBeginningPattern() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern("*.properties"); + assertEquals(""" + { + "resources": [ + { "glob": "*.properties"}, + { "glob": "/"} + ] + }""", hints); + } + + @Test + void registerWildcardInTheMiddlePattern() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern("com/example/*.properties"); + assertEquals(""" + { + "resources": [ + { "glob": "/" }, + { "glob": "com"}, + { "glob": "com/example"}, + { "glob": "com/example/*.properties"} + ] + }""", hints); + } + + @Test + void registerWildcardAtTheEndPattern() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern("static/*"); + assertEquals(""" + { + "resources": [ + { "glob": "/" }, + { "glob": "static"}, + { "glob": "static/*"} + ] + }""", hints); + } + + @Test + void registerPatternWithIncludesAndExcludes() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern(hint -> hint.includes("com/example/*.properties")); + hints.resources().registerPattern(hint -> hint.includes("org/other/*.properties")); + assertEquals(""" + { + "resources": [ + { "glob": "/"}, + { "glob": "com"}, + { "glob": "com/example"}, + { "glob": "com/example/*.properties"}, + { "glob": "org"}, + { "glob": "org/other"}, + { "glob": "org/other/*.properties"} + ] + }""", hints); + } + + @Test + void registerWithReachableTypeCondition() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern(builder -> builder.includes(TypeReference.of("com.example.Test"), "com/example/test.properties")); + assertEquals(""" + { + "resources": [ + { "condition": { "typeReached": "com.example.Test"}, "glob": "/"}, + { "condition": { "typeReached": "com.example.Test"}, "glob": "com"}, + { "condition": { "typeReached": "com.example.Test"}, "glob": "com/example"}, + { "condition": { "typeReached": "com.example.Test"}, "glob": "com/example/test.properties"} + ] + }""", hints); + } + + @Test + void registerType() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerType(String.class); + assertEquals(""" + { + "resources": [ + { "glob": "/" }, + { "glob": "java" }, + { "glob": "java/lang" }, + { "glob": "java/lang/String.class" } + ] + }""", hints); + } + + @Test + void registerResourceBundle() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerResourceBundle("com.example.message2"); + hints.resources().registerResourceBundle("com.example.message"); + assertEquals(""" + { + "bundles": [ + { "name": "com.example.message"}, + { "name": "com.example.message2"} + ] + }""", hints); + } + } + + @Nested + class SerializationHintsTests { + + @Test + void shouldWriteEmptyHint() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + assertEquals("{}", hints); + } + + @Test + void shouldWriteSingleHint() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.serialization().registerType(TypeReference.of(String.class)); + assertEquals(""" + { + "serialization": [ + { "type": "java.lang.String" } + ] + } + """, hints); + } + + @Test + void shouldWriteMultipleHints() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.serialization() + .registerType(TypeReference.of(Environment.class)) + .registerType(TypeReference.of(String.class)); + assertEquals(""" + { + "serialization": [ + { "type": "java.lang.String" }, + { "type": "org.springframework.core.env.Environment" } + ] + } + """, hints); + } + + @Test + void shouldWriteSingleHintWithCondition() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.serialization().registerType(TypeReference.of(String.class), + builder -> builder.onReachableType(TypeReference.of("org.example.Test"))); + assertEquals(""" + { + "serialization": [ + { "condition": { "typeReached": "org.example.Test" }, "type": "java.lang.String" } + ] + } + """, hints); + } + + } + + @Nested + class ProxyHintsTests { + + @Test + void empty() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + assertEquals("{}", hints); + } + + @Test + void shouldWriteOneEntry() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(Function.class); + assertEquals(""" + { + "reflection": [ + { + "type": { + "proxy": ["java.util.function.Function"] + } + } + ] + } + """, hints); + } + + @Test + void shouldWriteMultipleEntries() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(Function.class) + .registerJdkProxy(Function.class, Consumer.class); + assertEquals(""" + { + "reflection": [ + { + "type": { "proxy": ["java.util.function.Function"] } + }, + { + "type": { "proxy": ["java.util.function.Function", "java.util.function.Consumer"] } + } + ] + } + """, hints); + } + + @Test + void shouldWriteEntriesInNaturalOrder() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(Supplier.class) + .registerJdkProxy(Function.class); + assertEquals(""" + { + "reflection": [ + { + "type": { "proxy": ["java.util.function.Function"] } + }, + { + "type": { "proxy": ["java.util.function.Supplier"] } + } + ] + } + """, hints); + } + + @Test + void shouldWriteInnerClass() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(InnerInterface.class); + assertEquals(""" + { + "reflection": [ + { + "type": { "proxy": ["org.springframework.aot.nativex.RuntimeHintsWriterTests$InnerInterface"] } + } + ] + } + """, hints); + } + + @Test + void shouldWriteCondition() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(builder -> builder.proxiedInterfaces(Function.class) + .onReachableType(TypeReference.of("org.example.Test"))); + assertEquals(""" + { + "reflection": [ + { + "type": { "proxy": ["java.util.function.Function"] }, + "condition": { "typeReached": "org.example.Test" } + } + ] + } + """, hints); + } + + } + + private void assertEquals(String expectedString, RuntimeHints hints) throws JSONException { + String json = writeJson(hints); + JSONAssert.assertEquals(expectedString, json, JSONCompareMode.LENIENT); + Set validationMessages = JSON_SCHEMA.validate(json, InputFormat.JSON, executionContext -> + executionContext.getExecutionConfig().setFormatAssertionsEnabled(true)); + assertThat(validationMessages).isEmpty(); + } + + private String writeJson(RuntimeHints hints) { + StringWriter out = new StringWriter(); + BasicJsonWriter writer = new BasicJsonWriter(out, "\t"); + new RuntimeHintsWriter().write(writer, hints); + return out.toString(); + } + + + static class InnerClass { + + } + + interface InnerInterface { + + } + +} diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/SerializationHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/SerializationHintsWriterTests.java deleted file mode 100644 index bef4922248..0000000000 --- a/spring-core/src/test/java/org/springframework/aot/nativex/SerializationHintsWriterTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.aot.nativex; - -import java.io.StringWriter; - -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.aot.hint.SerializationHints; -import org.springframework.aot.hint.TypeReference; -import org.springframework.core.env.Environment; - -/** - * Tests for {@link SerializationHintsWriter}. - * - * @author Sebastien Deleuze - */ -class SerializationHintsWriterTests { - - @Test - void shouldWriteEmptyHint() throws JSONException { - SerializationHints hints = new SerializationHints(); - assertEquals("[]", hints); - } - - @Test - void shouldWriteSingleHint() throws JSONException { - SerializationHints hints = new SerializationHints().registerType(TypeReference.of(String.class)); - assertEquals(""" - [ - { "name": "java.lang.String" } - ]""", hints); - } - - @Test - void shouldWriteMultipleHints() throws JSONException { - SerializationHints hints = new SerializationHints() - .registerType(TypeReference.of(Environment.class)) - .registerType(TypeReference.of(String.class)); - assertEquals(""" - [ - { "name": "java.lang.String" }, - { "name": "org.springframework.core.env.Environment" } - ]""", hints); - } - - @Test - void shouldWriteSingleHintWithCondition() throws JSONException { - SerializationHints hints = new SerializationHints().registerType(TypeReference.of(String.class), - builder -> builder.onReachableType(TypeReference.of("org.example.Test"))); - assertEquals(""" - [ - { "condition": { "typeReachable": "org.example.Test" }, "name": "java.lang.String" } - ]""", hints); - } - - private void assertEquals(String expectedString, SerializationHints hints) throws JSONException { - StringWriter out = new StringWriter(); - BasicJsonWriter writer = new BasicJsonWriter(out, "\t"); - SerializationHintsWriter.INSTANCE.write(writer, hints); - JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT); - } - -} diff --git a/spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.0.0.json b/spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.0.0.json new file mode 100644 index 0000000000..bb434fb22e --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.0.0.json @@ -0,0 +1,362 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://www.graalvm.org/reachability-metadata-schema-v1.0.0.json", + "title": "JSON schema for the reachability metadata used by GraalVM Native Image", + "type": "object", + "default": {}, + "properties": { + "comment": { + "title": "A comment applying to the whole file (e.g., generation date, author, etc.)", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "default": "" + }, + "reflection": { + "title": "Metadata to ensure elements are reachable through reflection", + "$ref": "#/$defs/reflection" + }, + "jni": { + "title": "Metadata to ensure elements are reachable through JNI", + "$ref": "#/$defs/reflection" + }, + "serialization": { + "title": "Metadata for types that are serialized or deserialized at run time. The types must extend 'java.io.Serializable'.", + "type": "array", + "default": [], + "items": { + "title": "Enables serializing and deserializing objects of the class specified by ", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the type's inclusion in the serialization metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the class should be registered for serialization", + "$ref": "#/$defs/condition" + }, + "type": { + "title": "Type descriptor of the class that should be registered for serialization", + "$ref": "#/$defs/type" + }, + "customTargetConstructorClass": { + "title": "Fully qualified name of the class whose constructor should be used to serialize the class specified by ", + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "resources": { + "title": "Metadata to ensure resources are available", + "type": "array", + "default": [], + "items": { + "title": "Resource that should be available", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the resource's inclusion in the metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the resource should be registered for runtime access", + "$ref": "#/$defs/condition" + }, + "module": { + "title": "Module containing the resource", + "type": "string", + "default": "" + }, + "glob": { + "title": "Resource name or pattern matching multiple resources (accepts * and ** wildcards)", + "type": "string" + } + }, + "required": [ + "glob" + ], + "additionalProperties": false + } + }, + "bundles": { + "title": "Metadata to ensure resource bundles are available", + "type": "array", + "default": [], + "items": { + "title": "Resource bundle that should be available", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the resource bundle's inclusion in the metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the resource bundle should be registered for runtime access", + "$ref": "#/$defs/condition" + }, + "name": { + "title": "Name of the resource bundle", + "type": "string" + }, + "locales": { + "title": "List of locales that should be registered for this resource bundle", + "type": "array", + "default": [], + "items": { + "type": "string" + } + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + } + }, + "required": [], + "additionalProperties": false, + + "$defs": { + "reflection": { + "type": "array", + "default": [], + "items": { + "title": "Elements that should be registered for reflection for a specified type", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the element's inclusion", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the class should be registered for reflection", + "$ref": "#/$defs/condition" + }, + "type": { + "title": "Type descriptor of the class that should be registered for reflection", + "$ref": "#/$defs/type" + }, + "methods": { + "title": "List of methods that should be registered for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Method descriptor of the method that should be registered for reflection", + "$ref": "#/$defs/method" + } + }, + "fields": { + "title": "List of class fields that can be read or written to for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Field descriptor of the field that should be registered for reflection", + "$ref": "#/$defs/field" + } + }, + "allDeclaredMethods": { + "title": "Register all declared methods from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "allDeclaredFields": { + "title": "Register all declared fields from the type for reflective access", + "type": "boolean", + "default": false + }, + "allDeclaredConstructors": { + "title": "Register all declared constructors from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "allPublicMethods": { + "title": "Register all public methods from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "allPublicFields": { + "title": "Register all public fields from the type for reflective access", + "type": "boolean", + "default": false + }, + "allPublicConstructors": { + "title": "Register all public constructors from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "unsafeAllocated": { + "title": "Allow objects of this class to be instantiated with a call to jdk.internal.misc.Unsafe#allocateInstance or JNI's AllocObject", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + }, + "jni": { + "type": "array", + "default": [], + "items": { + "title": "Elements that should be registered for JNI for a specified type", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the element's inclusion", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the class should be registered for JNI", + "$ref": "#/$defs/condition" + }, + "type": { + "title": "Type descriptor of the class that should be registered for JNI", + "$ref": "#/$defs/type" + }, + "methods": { + "title": "List of methods that should be registered for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Method descriptor of the method that should be registered for JNI", + "$ref": "#/$defs/method" + } + }, + "fields": { + "title": "List of class fields that can be read or written to for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Field descriptor of the field that should be registered for JNI", + "$ref": "#/$defs/field" + } + }, + "allDeclaredMethods": { + "title": "Register all declared methods from the type for JNI access", + "type": "boolean", + "default": false + }, + "allDeclaredFields": { + "title": "Register all declared fields from the type for JNI access", + "type": "boolean", + "default": false + }, + "allDeclaredConstructors": { + "title": "Register all declared constructors from the type for JNI access", + "type": "boolean", + "default": false + }, + "allPublicMethods": { + "title": "Register all public methods from the type for JNI access", + "type": "boolean", + "default": false + }, + "allPublicFields": { + "title": "Register all public fields from the type for JNI access", + "type": "boolean", + "default": false + }, + "allPublicConstructors": { + "title": "Register all public constructors from the type for JNI access", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + }, + "reason": { + "type": "string", + "default": [] + }, + "condition": { + "title": "Condition used by GraalVM Native Image metadata files", + "type": "object", + "properties": { + "typeReached": { + "title": "Type descriptor of a class that must be reached in order to enable the corresponding registration", + "$ref": "#/$defs/type" + } + }, + "required": [ + "typeReached" + ], + "additionalProperties": false + }, + "type": { + "title": "Type descriptors used by GraalVM Native Image metadata files", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "proxy": { + "title": "List of interfaces defining the proxy class", + "type": "array", + "default": [], + "items": { + "title": "Fully qualified name of the interface defining the proxy class", + "type": "string" + } + } + }, + "required": [ + "proxy" + ], + "additionalProperties": false + } + ] + }, + "method": { + "title": "Method descriptors used by GraalVM Native Image metadata files", + "type": "object", + "properties": { + "name": { + "title": "Method name that should be registered for this class", + "type": "string" + }, + "parameterTypes": { + "default": [], + "items": { + "title": "List of the method's parameter types", + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + }, + "field": { + "title": "Field descriptors used by GraalVM Native Image metadata files", + "type": "object", + "properties": { + "name": { + "title": "Name of the field that should be registered for reflection", + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + } +} \ No newline at end of file