diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/HintType.java b/spring-core-test/src/main/java/org/springframework/aot/agent/HintType.java index f2311b30fe6..9103442be11 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/HintType.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/HintType.java @@ -16,7 +16,6 @@ package org.springframework.aot.agent; -import org.springframework.aot.hint.JavaSerializationHint; import org.springframework.aot.hint.JdkProxyHint; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.ResourceBundleHint; @@ -51,7 +50,9 @@ public enum HintType { /** * Java serialization hint, as described by {@link org.springframework.aot.hint.JavaSerializationHint}. */ - JAVA_SERIALIZATION(JavaSerializationHint.class), + @Deprecated(since = "7.0.5", forRemoval = true) + @SuppressWarnings("removal") + JAVA_SERIALIZATION(org.springframework.aot.hint.JavaSerializationHint.class), /** * JDK proxies hint, as described by {@link org.springframework.aot.hint.ProxyHints#jdkProxyHints()}. diff --git a/spring-core/src/main/java/org/springframework/aot/hint/JavaSerializationHint.java b/spring-core/src/main/java/org/springframework/aot/hint/JavaSerializationHint.java index 3c2d8654b09..9431b3be7e1 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/JavaSerializationHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/JavaSerializationHint.java @@ -27,7 +27,9 @@ import org.jspecify.annotations.Nullable; * * @author Brian Clozel * @since 6.0 + * @deprecated in favor of {@link TypeHint} */ +@Deprecated(since = "7.0.6", forRemoval = true) public final class JavaSerializationHint implements ConditionalHint { private final TypeReference type; diff --git a/spring-core/src/main/java/org/springframework/aot/hint/JdkProxyHint.java b/spring-core/src/main/java/org/springframework/aot/hint/JdkProxyHint.java index 989d5b57a05..912c0bc1890 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/JdkProxyHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/JdkProxyHint.java @@ -37,10 +37,13 @@ public final class JdkProxyHint implements ConditionalHint { private final @Nullable TypeReference reachableType; + private final @Nullable Boolean serializable; + private JdkProxyHint(Builder builder) { this.proxiedInterfaces = List.copyOf(builder.proxiedInterfaces); this.reachableType = builder.reachableType; + this.serializable = builder.serializable; } /** @@ -75,11 +78,21 @@ public final class JdkProxyHint implements ConditionalHint { return this.reachableType; } + /** + * Return whether to register this proxy for Java serialization. + * @return whether to register this proxy for Java serialization. + * @since 7.0.6 + */ + public @Nullable Boolean getSerializable() { + return this.serializable; + } + @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof JdkProxyHint that && this.proxiedInterfaces.equals(that.proxiedInterfaces) && - Objects.equals(this.reachableType, that.reachableType))); + Objects.equals(this.reachableType, that.reachableType) && + Objects.equals(this.serializable, that.serializable))); } @Override @@ -97,6 +110,8 @@ public final class JdkProxyHint implements ConditionalHint { private @Nullable TypeReference reachableType; + private @Nullable Boolean serializable; + Builder() { this.proxiedInterfaces = new LinkedList<>(); } @@ -131,6 +146,17 @@ public final class JdkProxyHint implements ConditionalHint { return this; } + /** + * Specify if this proxy should be registered for Java serialization. + * @param serializable whether to register this proxy for Java serialization. + * @return {@code this}, to facilitate method chaining + * @since 7.0.6 + */ + public Builder javaSerialization(@Nullable Boolean serializable) { + this.serializable = serializable; + return this; + } + /** * Create a {@link JdkProxyHint} based on the state of this builder. * @return a JDK proxy hint diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java index 6b84dc52306..a5020d13a0b 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java @@ -231,6 +231,17 @@ public class ReflectionHints { typeHint -> typeHint.withMethod(method.getName(), mapParameters(method), mode)); } + /** + * Register the need for Java Serialization on the specified type. + * @param type the type that should be serializable + * @return {@code this}, to facilitate method chaining + * @since 7.0.6 + */ + public ReflectionHints registerJavaSerialization(Class type) { + return registerType(TypeReference.of(type), + typeHint -> typeHint.withJavaSerialization(true)); + } + private List mapParameters(Executable executable) { return TypeReference.listOf(executable.getParameterTypes()); } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/RuntimeHints.java b/spring-core/src/main/java/org/springframework/aot/hint/RuntimeHints.java index 54e6a9c26f6..ea6ddd1b33f 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/RuntimeHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/RuntimeHints.java @@ -37,6 +37,7 @@ public class RuntimeHints { private final ResourceHints resources = new ResourceHints(); + @SuppressWarnings("removal") private final SerializationHints serialization = new SerializationHints(); private final ProxyHints proxies = new ProxyHints(); @@ -63,7 +64,10 @@ public class RuntimeHints { /** * Provide access to serialization-based hints. * @return serialization hints + * @deprecated in favor of {@link #reflection()} */ + @SuppressWarnings("removal") + @Deprecated(since = "7.0.6", forRemoval = true) public SerializationHints serialization() { return this.serialization; } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/SerializationHints.java b/spring-core/src/main/java/org/springframework/aot/hint/SerializationHints.java index e946f60ea1d..69b6035ad41 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/SerializationHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/SerializationHints.java @@ -30,7 +30,10 @@ import org.jspecify.annotations.Nullable; * @author Stephane Nicoll * @since 6.0 * @see Serializable + * @deprecated in favor of {@link ReflectionHints} */ +@Deprecated(since = "7.0.6", forRemoval = true) +@SuppressWarnings("removal") public class SerializationHints { private final Set javaSerializationHints; diff --git a/spring-core/src/main/java/org/springframework/aot/hint/TypeHint.java b/spring-core/src/main/java/org/springframework/aot/hint/TypeHint.java index 7838a963866..fe0540ec8e1 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/TypeHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/TypeHint.java @@ -53,6 +53,8 @@ public final class TypeHint implements ConditionalHint { private final Set memberCategories; + private final @Nullable Boolean serializable; + private TypeHint(Builder builder) { this.type = builder.type; @@ -61,6 +63,7 @@ public final class TypeHint implements ConditionalHint { this.fields = builder.fields.stream().map(FieldHint::new).collect(Collectors.toSet()); this.constructors = builder.constructors.values().stream().map(ExecutableHint.Builder::build).collect(Collectors.toSet()); this.methods = builder.methods.values().stream().map(ExecutableHint.Builder::build).collect(Collectors.toSet()); + this.serializable = builder.serializable; } /** @@ -120,6 +123,15 @@ public final class TypeHint implements ConditionalHint { return this.memberCategories; } + /** + * Return whether to register this type for Java serialization. + * @return whether to register this type for Java serialization. + * @since 7.0.6 + */ + public @Nullable Boolean getSerializable() { + return this.serializable; + } + @Override public String toString() { return TypeHint.class.getSimpleName() + "[type=" + this.type + "]"; @@ -153,6 +165,8 @@ public final class TypeHint implements ConditionalHint { private final Set memberCategories = new HashSet<>(); + private @Nullable Boolean serializable; + Builder(TypeReference type) { this.type = type; } @@ -260,6 +274,17 @@ public final class TypeHint implements ConditionalHint { return this; } + /** + * Specify if this type should be registered for Java serialization. + * @param serializable whether to register this type for Java serialization. + * @return {@code this}, to facilitate method chaining + * @since 7.0.6 + */ + public Builder withJavaSerialization(@Nullable Boolean serializable) { + this.serializable = serializable; + return this; + } + /** * Create a {@link TypeHint} based on the state of this builder. * @return a type hint diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java index 887bdb7eb30..14a3fd735ff 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java @@ -23,6 +23,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.Predicate; @@ -297,6 +298,30 @@ public class ReflectionHintsPredicates { return new FieldHintPredicate(field); } + /** + * Return a predicate that checks whether Java serialization is configured according to the given flag. + * @param type the type to check + * @param serializable the expected serializable flag + * @return the {@link RuntimeHints} predicate + * @since 7.0.6 + */ + public Predicate onJavaSerialization(Class type, @Nullable Boolean serializable) { + Assert.notNull(type, "'type' must not be null"); + return new SerializationdHintPredicate(TypeReference.of(type), serializable); + } + + /** + * Return a predicate that checks whether Java serialization is configured according to the given flag. + * @param typeReference the type reference to check + * @param serializable the expected serializable flag + * @return the {@link RuntimeHints} predicate + * @since 7.0.6 + */ + public Predicate onJavaSerialization(TypeReference typeReference, @Nullable Boolean serializable) { + Assert.notNull(typeReference, "'typeReference' must not be null"); + return new SerializationdHintPredicate(typeReference, serializable); + } + public static class TypeHintPredicate implements Predicate { @@ -509,4 +534,26 @@ public class ReflectionHintsPredicates { } } + + public static class SerializationdHintPredicate implements Predicate { + + private final TypeReference typeReference; + + private final @Nullable Boolean serializable; + + SerializationdHintPredicate(TypeReference typeReference, @Nullable Boolean serializable) { + this.typeReference = typeReference; + this.serializable = serializable; + } + + @Override + public boolean test(RuntimeHints runtimeHints) { + TypeHint typeHint = runtimeHints.reflection().getTypeHint(this.typeReference); + if (typeHint == null) { + return false; + } + return Objects.equals(typeHint.getSerializable(), this.serializable); + } + } + } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/RuntimeHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/RuntimeHintsPredicates.java index 0bfa80e6968..73208f065e3 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/RuntimeHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/RuntimeHintsPredicates.java @@ -46,6 +46,7 @@ public abstract class RuntimeHintsPredicates { private static final ResourceHintsPredicates resource = new ResourceHintsPredicates(); + @SuppressWarnings("removal") private static final SerializationHintsPredicates serialization = new SerializationHintsPredicates(); private static final ProxyHintsPredicates proxies = new ProxyHintsPredicates(); @@ -73,7 +74,10 @@ public abstract class RuntimeHintsPredicates { /** * Return a predicate generator for {@link SerializationHints serialization hints}. * @return the predicate generator + * @deprecated in favor of {@link #reflection()} */ + @Deprecated(since = "7.0.6", forRemoval = true) + @SuppressWarnings("removal") public static SerializationHintsPredicates serialization() { return serialization; } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/SerializationHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/SerializationHintsPredicates.java index e1c4440d270..c36a117cab9 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/SerializationHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/SerializationHintsPredicates.java @@ -29,7 +29,10 @@ import org.springframework.util.Assert; * * @author Stephane Nicoll * @since 6.0 + * @deprecated in favor of {@link ReflectionHintsPredicates} */ +@Deprecated(since = "7.0.6", forRemoval = true) +@SuppressWarnings("removal") public class SerializationHintsPredicates { SerializationHintsPredicates() { 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 f9ada2f414c..b2a583f056d 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 @@ -44,12 +44,17 @@ public abstract class NativeConfigurationWriter { } private boolean hasAnyHint(RuntimeHints hints) { - return (hints.serialization().javaSerializationHints().findAny().isPresent() || - hints.proxies().jdkProxyHints().findAny().isPresent() || + return (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()); + hints.jni().typeHints().findAny().isPresent() || + hasAnyDeprecatedHint(hints)); + } + + @SuppressWarnings("removal") + private boolean hasAnyDeprecatedHint(RuntimeHints hints) { + return hints.serialization().javaSerializationHints().findAny().isPresent(); } /** diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsAttributes.java b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsAttributes.java index 96a905fe7f6..aea5594676b 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsAttributes.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsAttributes.java @@ -22,6 +22,7 @@ import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -32,6 +33,7 @@ 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.JavaSerializationHint; import org.springframework.aot.hint.JdkProxyHint; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; @@ -62,16 +64,28 @@ class ReflectionHintsAttributes { }; public List> reflection(RuntimeHints hints) { - List> reflectionHints = new ArrayList<>(); - reflectionHints.addAll(hints.reflection().typeHints() - .sorted(Comparator.comparing(TypeHint::getType)) - .map(this::toAttributes).toList()); + List> reflectionHints = new ArrayList<>(reflectionHints(hints)); reflectionHints.addAll(hints.proxies().jdkProxyHints() .sorted(JDK_PROXY_HINT_COMPARATOR) .map(this::toAttributes).toList()); return reflectionHints; } + @SuppressWarnings("removal") + private List> reflectionHints(RuntimeHints hints) { + Map> allTypeHints = hints.reflection().typeHints() + .map(this::toAttributes).collect(Collectors.toMap((attributes -> (TypeReference) Objects.requireNonNull(attributes.get("type"))), + attributes -> attributes)); + hints.serialization().javaSerializationHints().forEach(hint -> { + allTypeHints.merge(hint.getType(), toAttributes(hint), + (currentAttributes, newAttributes) -> { + handleSerializable(currentAttributes, true); + return currentAttributes; + }); + }); + return allTypeHints.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).map(Map.Entry::getValue).toList(); + } + public List> jni(RuntimeHints hints) { List> jniHints = new ArrayList<>(); jniHints.addAll(hints.jni().typeHints() @@ -88,6 +102,16 @@ class ReflectionHintsAttributes { handleFields(attributes, hint.fields()); handleExecutables(attributes, Stream.concat( hint.constructors(), hint.methods()).sorted().toList()); + handleSerializable(attributes, hint.getSerializable()); + return attributes; + } + + @SuppressWarnings("removal") + private Map toAttributes(JavaSerializationHint serializationHint) { + LinkedHashMap attributes = new LinkedHashMap<>(); + handleCondition(attributes, serializationHint); + attributes.put("type", serializationHint.getType()); + handleSerializable(attributes, true); return attributes; } @@ -148,7 +172,14 @@ class ReflectionHintsAttributes { Map attributes = new LinkedHashMap<>(); handleCondition(attributes, hint); attributes.put("type", Map.of("proxy", hint.getProxiedInterfaces())); + handleSerializable(attributes, hint.getSerializable()); return attributes; } + private void handleSerializable(Map attributes, @Nullable Boolean serializable) { + if (serializable != null) { + attributes.put("serializable", serializable); + } + } + } diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java index 8e573f013df..020beebf093 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java @@ -16,6 +16,7 @@ package org.springframework.aot.nativex; +import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; @@ -35,8 +36,9 @@ import org.springframework.aot.hint.ResourcePatternHints; * @author Stephane Nicoll * @author Brian Clozel * @since 6.0 - * @see Accessing Resources in Native Images - * @see Native Image Build Configuration + * @see Accessing Resources in Native Images + * @see Accessing Resource Bundles in Native Images + * @see Native Image Build Configuration */ class ResourceHintsAttributes { @@ -48,22 +50,21 @@ class ResourceHintsAttributes { public List> resources(ResourceHints hint) { - return hint.resourcePatternHints() + List> resourceHints = new ArrayList<>(); + resourceHints.addAll(hint.resourcePatternHints() .map(ResourcePatternHints::getIncludes).flatMap(List::stream).distinct() .sorted(RESOURCE_PATTERN_HINT_COMPARATOR) - .map(this::toAttributes).toList(); - } - - public List> resourceBundles(ResourceHints hint) { - return hint.resourceBundleHints() + .map(this::toAttributes).toList()); + resourceHints.addAll(hint.resourceBundleHints() .sorted(RESOURCE_BUNDLE_HINT_COMPARATOR) - .map(this::toAttributes).toList(); + .map(this::toAttributes).toList()); + return resourceHints; } private Map toAttributes(ResourceBundleHint hint) { Map attributes = new LinkedHashMap<>(); handleCondition(attributes, hint); - attributes.put("name", hint.getBaseName()); + attributes.put("bundle", hint.getBaseName()); return attributes; } 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 index 586c91b87a0..9e4d95622aa 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/RuntimeHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/RuntimeHintsWriter.java @@ -28,8 +28,9 @@ import org.springframework.core.SpringVersion; * GraalVM {@code native-image} compiler, typically named {@code reachability-metadata.json}. * * @author Brian Clozel + * @author Stephane Nicoll * @since 7.0 - * @see GraalVM Reachability Metadata + * @see GraalVM Reachability Metadata */ class RuntimeHintsWriter { @@ -51,14 +52,6 @@ class RuntimeHintsWriter { 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/SerializationHintsAttributes.java b/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsAttributes.java deleted file mode 100644 index 6ae28697fd3..00000000000 --- a/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsAttributes.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2002-present 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.List; -import java.util.Map; - -import org.springframework.aot.hint.ConditionalHint; -import org.springframework.aot.hint.JavaSerializationHint; -import org.springframework.aot.hint.SerializationHints; - -/** - * 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 - * @see Native Image Build Configuration - */ -class SerializationHintsAttributes { - - private static final Comparator JAVA_SERIALIZATION_HINT_COMPARATOR = - Comparator.comparing(JavaSerializationHint::getType); - - public List> toAttributes(SerializationHints hints) { - return hints.javaSerializationHints() - .sorted(JAVA_SERIALIZATION_HINT_COMPARATOR) - .map(this::toAttributes).toList(); - } - - private Map toAttributes(JavaSerializationHint serializationHint) { - LinkedHashMap attributes = new LinkedHashMap<>(); - handleCondition(attributes, serializationHint); - attributes.put("type", serializationHint.getType()); - return attributes; - } - - private void handleCondition(Map attributes, ConditionalHint hint) { - if (hint.getReachableType() != null) { - Map conditionAttributes = new LinkedHashMap<>(); - conditionAttributes.put("typeReached", hint.getReachableType()); - attributes.put("condition", conditionAttributes); - } - } - -} diff --git a/spring-core/src/main/kotlin/org/springframework/aot/hint/SerializationHintsExtensions.kt b/spring-core/src/main/kotlin/org/springframework/aot/hint/SerializationHintsExtensions.kt index 442bf640c85..e3f494946e7 100644 --- a/spring-core/src/main/kotlin/org/springframework/aot/hint/SerializationHintsExtensions.kt +++ b/spring-core/src/main/kotlin/org/springframework/aot/hint/SerializationHintsExtensions.kt @@ -25,5 +25,7 @@ import java.io.Serializable * @author Sebastien Deleuze * @since 6.0.5 */ +@Suppress("removal", "DEPRECATION") +@Deprecated(level = DeprecationLevel.WARNING, message = "Use ReflectionHints") inline fun SerializationHints.registerType(noinline serializationHint: (JavaSerializationHint.Builder) -> Unit = {}) = registerType(T::class.java, serializationHint::invoke) diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ProxyHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ProxyHintsTests.java index 13a06063333..e7372b289af 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ProxyHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ProxyHintsTests.java @@ -80,6 +80,18 @@ class ProxyHintsTests { assertThat(this.proxyHints.jdkProxyHints()).singleElement().satisfies(proxiedInterfaces(Function.class)); } + @Test + void registerJdkProxyWithJavaSerialization() { + this.proxyHints.registerJdkProxy(hint -> { + hint.proxiedInterfaces(TypeReference.of("com.example.Test")); + hint.javaSerialization(true); + }); + assertThat(this.proxyHints.jdkProxyHints()).singleElement().satisfies(hint -> { + assertThat(hint.getProxiedInterfaces()).containsExactly(TypeReference.of("com.example.Test")); + assertThat(hint.getSerializable()).isTrue(); + }); + } + private static Consumer springProxy(String proxiedInterface) { return builder -> builder.proxiedInterfaces(toTypeReferences( diff --git a/spring-core/src/test/java/org/springframework/aot/hint/RuntimeHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/RuntimeHintsTests.java index 6abb8183f24..4a796025e4d 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/RuntimeHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/RuntimeHintsTests.java @@ -53,6 +53,8 @@ class RuntimeHintsTests { } @Test + @Deprecated + @SuppressWarnings("removal") void javaSerializationHintWithClass() { this.hints.serialization().registerType(String.class); assertThat(this.hints.serialization().javaSerializationHints().map(JavaSerializationHint::getType)) diff --git a/spring-core/src/test/java/org/springframework/aot/hint/SerializationHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/SerializationHintsTests.java index 33dce1a3bb0..a068b849d97 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/SerializationHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/SerializationHintsTests.java @@ -27,6 +27,8 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Stephane Nicoll */ +@Deprecated +@SuppressWarnings("removal") class SerializationHintsTests { private final SerializationHints serializationHints = new SerializationHints(); diff --git a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java index d736bd8835b..8804723d225 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java @@ -435,6 +435,36 @@ class ReflectionHintsPredicatesTests { } + @Nested + class JavaSerialization { + + @Test + void javaSerializationMatchesRegisteredClass() { + runtimeHints.reflection().registerJavaSerialization(SampleClass.class); + assertPredicateMatches(reflection.onJavaSerialization(SampleClass.class, true)); + } + + @Test + void javaSerializationMatchesRegisteredTypeReference() { + runtimeHints.reflection().registerType(TypeReference.of(SampleClass.class), + type -> type.withJavaSerialization(false)); + assertPredicateMatches(reflection.onJavaSerialization(TypeReference.of(SampleClass.class), false)); + } + + @Test + void javaSerializationDoesNotMatchOnMissingType() { + runtimeHints.reflection().registerJavaSerialization(SampleClass.class); + assertPredicateDoesNotMatch(reflection.onJavaSerialization(Integer.class, true)); + } + + @Test + void javaSerializationDoesNotMatchOnInvalidSerializationFlag() { + runtimeHints.reflection().registerJavaSerialization(SampleClass.class); + assertPredicateDoesNotMatch(reflection.onJavaSerialization(SampleClass.class, false)); + } + + } + private void assertPredicateMatches(Predicate predicate) { assertThat(predicate).accepts(this.runtimeHints); } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/predicate/SerializationHintsPredicatesTests.java b/spring-core/src/test/java/org/springframework/aot/hint/predicate/SerializationHintsPredicatesTests.java index f7bb3021505..55c32da409e 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/predicate/SerializationHintsPredicatesTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/predicate/SerializationHintsPredicatesTests.java @@ -28,6 +28,8 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Stephane Nicoll */ +@Deprecated +@SuppressWarnings("removal") class SerializationHintsPredicatesTests { private final SerializationHintsPredicates serialization = new SerializationHintsPredicates(); 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 6a41c771219..8406cba5330 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 @@ -35,7 +35,6 @@ 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; import org.springframework.aot.hint.TypeReference; import org.springframework.core.codec.StringDecoder; @@ -47,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Sebastien Deleuze * @author Janne Valkealahti * @author Sam Brannen + * @author Stephane Nicoll */ class FileNativeConfigurationWriterTests { @@ -66,15 +66,15 @@ class FileNativeConfigurationWriterTests { void serializationConfig() throws IOException, JSONException { FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir); RuntimeHints hints = new RuntimeHints(); - SerializationHints serializationHints = hints.serialization(); - serializationHints.registerType(Integer.class); - serializationHints.registerType(Long.class); + ReflectionHints reflectionHints = hints.reflection(); + reflectionHints.registerJavaSerialization(Integer.class); + reflectionHints.registerJavaSerialization(Long.class); generator.write(hints); assertEquals(""" { - "serialization": [ - { "type": "java.lang.Integer" }, - { "type": "java.lang.Long" } + "reflection": [ + { "type": "java.lang.Integer", "serializable": true }, + { "type": "java.lang.Long", "serializable": true } ] } """); 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 index 0bde1da1a6f..aaa63ed5262 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/RuntimeHintsWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/RuntimeHintsWriterTests.java @@ -65,7 +65,7 @@ class RuntimeHintsWriterTests { 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); + JSON_SCHEMA = jsonSchemaFactory.getSchema(SchemaLocation.of("https://www.graalvm.org/reachability-metadata-schema-v1.2.0.json"), config); } @Nested @@ -89,7 +89,8 @@ class RuntimeHintsWriterTests { .withField("DEFAULT_CHARSET") .withField("defaultCharset") .withField("aScore") - .withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ExecutableMode.INVOKE)); + .withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ExecutableMode.INVOKE) + .withJavaSerialization(true)); assertEquals(""" { "reflection": [ @@ -110,7 +111,8 @@ class RuntimeHintsWriterTests { ], "methods": [ { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] } - ] + ], + "serializable": true } ] } @@ -204,6 +206,23 @@ class RuntimeHintsWriterTests { """, hints); } + @Test + void serializationEnabled() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> builder.withJavaSerialization(true)); + + assertEquals(""" + { + "reflection": [ + { + "type": "java.lang.Integer", + "serializable": true + } + ] + } + """, hints); + } + @Test void ignoreLambda() throws JSONException { Runnable anonymousRunnable = () -> {}; @@ -409,15 +428,17 @@ class RuntimeHintsWriterTests { hints.resources().registerResourceBundle("com.example.message"); assertEquals(""" { - "bundles": [ - { "name": "com.example.message"}, - { "name": "com.example.message2"} + "resources": [ + { "bundle": "com.example.message"}, + { "bundle": "com.example.message2"} ] }""", hints); } } @Nested + @Deprecated(forRemoval = true) + @SuppressWarnings("removal") class SerializationHintsTests { @Test @@ -432,8 +453,8 @@ class RuntimeHintsWriterTests { hints.serialization().registerType(TypeReference.of(String.class)); assertEquals(""" { - "serialization": [ - { "type": "java.lang.String" } + "reflection": [ + { "type": "java.lang.String", "serializable": true } ] } """, hints); @@ -447,9 +468,9 @@ class RuntimeHintsWriterTests { .registerType(TypeReference.of(String.class)); assertEquals(""" { - "serialization": [ - { "type": "java.lang.String" }, - { "type": "org.springframework.core.env.Environment" } + "reflection": [ + { "type": "java.lang.String", "serializable": true }, + { "type": "org.springframework.core.env.Environment", "serializable": true } ] } """, hints); @@ -462,8 +483,8 @@ class RuntimeHintsWriterTests { builder -> builder.onReachableType(TypeReference.of("org.example.Test"))); assertEquals(""" { - "serialization": [ - { "condition": { "typeReached": "org.example.Test" }, "type": "java.lang.String" } + "reflection": [ + { "condition": { "typeReached": "org.example.Test" }, "type": "java.lang.String", "serializable": true } ] } """, hints); @@ -567,6 +588,27 @@ class RuntimeHintsWriterTests { """, hints); } + @Test + void shouldWriteSerialization() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(hint -> { + hint.proxiedInterfaces(Function.class); + hint.javaSerialization(true); + }); + assertEquals(""" + { + "reflection": [ + { + "type": { + "proxy": ["java.util.function.Function"] + }, + "serializable": true + } + ] + } + """, hints); + } + } private void assertEquals(String expectedString, RuntimeHints hints) throws JSONException { diff --git a/spring-core/src/test/kotlin/org/springframework/aot/hint/SerializationHintsExtensionsTests.kt b/spring-core/src/test/kotlin/org/springframework/aot/hint/SerializationHintsExtensionsTests.kt index 84d20a9dfba..61f905c2422 100644 --- a/spring-core/src/test/kotlin/org/springframework/aot/hint/SerializationHintsExtensionsTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/aot/hint/SerializationHintsExtensionsTests.kt @@ -27,6 +27,7 @@ import java.util.function.Consumer * * @author Sebastien Deleuze */ +@Suppress("REMOVAL", "DEPRECATION") class SerializationHintsExtensionsTests { private val serializationHints = mockk() 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 deleted file mode 100644 index bb434fb22e2..00000000000 --- a/spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.0.0.json +++ /dev/null @@ -1,362 +0,0 @@ -{ - "$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 diff --git a/spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.2.0.json b/spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.2.0.json new file mode 100644 index 00000000000..f0390927411 --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.2.0.json @@ -0,0 +1,469 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.2.0.json", + "title": "JSON schema for the reachability metadata used by GraalVM Native Image", + "version": "1.2.0", + "type": "object", + "default": {}, + "properties": { + "comment": { + "title": "Comment(s) 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 accessible via reflection", + "type": "array", + "default": [], + "items": { + "title": "Elements that should be registered for reflection for a specified type", + "type": "object", + "required": [ + "type" + ], + "properties": { + "reason": { + "$ref": "#/$defs/reason" + }, + "condition": { + "$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", + "type": "object", + "properties": { + "name": { + "title": "Name of the field that should be registered for reflection", + "type": "string", + "pattern": "^[^.;\\[/]+$" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "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 + }, + "serializable": { + "title": "Allow objects of this class to be serialized and deserialized", + "type": "boolean", + "default": false + }, + "jniAccessible": { + "title": "Register the type, including all registered fields and methods, for runtime JNI access", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + }, + "resources": { + "title": "Metadata to ensure resources are accessible", + "type": "array", + "default": [], + "items": { + "oneOf": [ + { + "title": "Resource that should be accessible", + "type": "object", + "properties": { + "reason": { + "$ref": "#/$defs/reason" + }, + "condition": { + "$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 + }, + { + "title": "Resource bundle that should be available", + "type": "object", + "properties": { + "reason": { + "$ref": "#/$defs/reason" + }, + "condition": { + "$ref": "#/$defs/condition" + }, + "module": { + "title": "Module containing the resource bundle", + "type": "string", + "default": "" + }, + "bundle": { + "title": "Resource bundle name", + "type": "string" + } + }, + "required": [ + "bundle" + ], + "additionalProperties": false + } + ] + } + }, + "foreign": { + "properties": { + "downcalls": { + "default": [], + "items": { + "properties": { + "reason": { + "$ref": "#/$defs/reason" + }, + "condition": { + "$ref": "#/$defs/condition" + }, + "returnType": { + "type": "string", + "title": "Memory layout definition (allows canonical layouts; see `java.lang.foreign.Linker`)" + }, + "parameterTypes": { + "default": [], + "items": { + "type": "string", + "title": "Memory layout definition (allows canonical layouts; see `java.lang.foreign.Linker`)" + }, + "type": "array", + "title": "List of the function descriptor's parameter types" + }, + "options": { + "type": "object", + "title": "Linker options (see `java.lang.foreign.Linker.Option`)", + "properties": { + "captureCallState": { + "type": "boolean", + "title": "Specifies whether a call state should be captured. The specific states to capture are determined at run time. See also: `java.lang.foreign.Linker.Option.captureCallState`" + }, + "critical": { + "type": "object", + "title": "See `java.lang.foreign.Linker.Option.critical`", + "properties": { + "allowHeapAccess": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "firstVariadicArg": { + "type": "integer", + "title": "See `java.lang.foreign.Linker.Option.firstVariadicArg`" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": [ + "returnType", + "parameterTypes" + ], + "type": "object", + "title": "Function descriptor to be registered for a downcall" + }, + "type": "array", + "title": "List of function descriptors that should be registered for downcalls" + }, + "upcalls": { + "default": [], + "items": { + "properties": { + "reason": { + "$ref": "#/$defs/reason" + }, + "condition": { + "$ref": "#/$defs/condition" + }, + "returnType": { + "type": "string", + "title": "Memory layout definition (allows canonical layouts; see `java.lang.foreign.Linker`)" + }, + "parameterTypes": { + "default": [], + "items": { + "type": "string", + "title": "Memory layout definition (allows canonical layouts; see `java.lang.foreign.Linker`)" + }, + "type": "array", + "title": "List of the function descriptor's parameter types" + }, + "options": { + "type": "object", + "title": "Linker options (see `java.lang.foreign.Linker.Option`)", + "description": "Currently, no linker options are allowed for upcalls. This may change in the future.", + "properties": {}, + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": [ + "returnType", + "parameterTypes" + ], + "type": "object", + "title": "Function descriptor to be registered for an upcall" + }, + "type": "array", + "title": "List of function descriptors that should be registered for upcalls" + }, + "directUpcalls": { + "default": [], + "items": { + "properties": { + "reason": { + "$ref": "#/$defs/reason" + }, + "condition": { + "$ref": "#/$defs/condition" + }, + "class": { + "$ref": "#/$defs/className", + "title": "Fully-qualified class name (e.g., `org.package.OuterClass$InnerClass`)" + }, + "method": { + "type": "string", + "title": "Method name" + }, + "returnType": { + "type": "string", + "title": "Memory layout definition (allows canonical layouts; see `java.lang.foreign.Linker`)" + }, + "parameterTypes": { + "default": [], + "items": { + "type": "string", + "title": "Memory layout definition (allows canonical layouts; see `java.lang.foreign.Linker`)" + }, + "type": "array", + "title": "List of the function descriptor's parameter types" + }, + "options": { + "type": "object", + "title": "Linker options (see `java.lang.foreign.Linker.Option`)", + "description": "Currently, no linker options are allowed for direct upcalls. This may change in the future.", + "properties": {}, + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": [ + "class", + "method" + ], + "type": "object", + "title": "Java method and function descriptor to be registered for a direct upcall" + }, + "type": "array", + "title": "List of Java methods and function descriptors that should be registered for direct upcalls" + } + }, + "type": "object", + "additionalProperties": false, + "title": "JSON schema for the FFM API configuration used by GraalVM Native Image", + "description": "For a description and examples of writing an FFM API configuration, see: https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/FFM-API.md" + } + }, + "required": [], + "additionalProperties": false, + "$defs": { + "className": { + "type": "string", + "pattern": "^[^.;\\[/]+(\\.[^.;\\[/]+)*$" + }, + "typeName": { + "type": "string", + "pattern": "^[^.;\\[/]+(\\.[^.;\\[/]+)*(\\[])*$" + }, + "reason": { + "title": "Reason(s) for including this element (e.g., needed for establishing connections, or needed by method X#Y for task Z)", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "default": "" + }, + "condition": { + "title": "Condition used to determine if the element will be accessible at run time", + "type": "object", + "properties": { + "typeReached": { + "title": "Type descriptor of a class that must be reached in order to allow access to the element", + "$ref": "#/$defs/type" + } + }, + "required": [ + "typeReached" + ], + "additionalProperties": false + }, + "type": { + "title": "Type descriptors used by GraalVM Native Image metadata files", + "oneOf": [ + { + "$ref": "#/$defs/typeName" + }, + { + "type": "object", + "properties": { + "proxy": { + "title": "A list of interfaces defining the proxy class", + "type": "array", + "items": { + "title": "Fully-qualified name of the interface defining the proxy class", + "$ref": "#/$defs/className" + } + }, + "lambda": { + "title": "Lambda class descriptor", + "type": "object", + "properties": { + "declaringClass": { + "title": "The class in which the lambda class is defined", + "$ref": "#/$defs/className" + }, + "declaringMethod": { + "title": "The method in which the lambda class is defined", + "$ref": "#/$defs/method" + }, + "interfaces": { + "title": "Non-empty list of interfaces implemented by the lambda class", + "type": "array", + "items": { + "title": "Fully-qualified name of the interface implemented by the lambda class", + "$ref": "#/$defs/className" + }, + "minItems": 1 + } + }, + "required": [ + "declaringClass", + "interfaces" + ], + "additionalProperties": false + } + }, + "oneOf": [ + { + "required": [ + "proxy" + ] + }, + { + "required": [ + "lambda" + ] + } + ], + "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", + "pattern": "^[^.;\\[/]+$" + }, + "parameterTypes": { + "items": { + "title": "List of the method's parameter types", + "$ref": "#/$defs/typeName" + }, + "type": "array" + } + }, + "required": [ + "name", + "parameterTypes" + ], + "additionalProperties": false + } + } +} \ No newline at end of file