diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java index 96b6755954..379645d047 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java @@ -31,6 +31,7 @@ import org.springframework.aot.generate.GeneratedMethods; import org.springframework.aot.generate.InMemoryGeneratedFiles; import org.springframework.aot.generate.MethodGenerator; import org.springframework.aot.generate.MethodReference; +import org.springframework.aot.hint.ResourcePatternHint; import org.springframework.aot.test.generator.compile.Compiled; import org.springframework.aot.test.generator.compile.TestCompiler; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; @@ -90,6 +91,7 @@ class ConfigurationClassPostProcessorAotContributionTests { assertThat(generationContext.getRuntimeHints().resources().resourcePatterns()) .singleElement() .satisfies(resourceHint -> assertThat(resourceHint.getIncludes()) + .map(ResourcePatternHint::getPattern) .containsOnly( "org/springframework/context/testfixture/context/generator/annotation/ImportConfiguration.class")); } diff --git a/spring-core/src/main/java/org/springframework/aot/generate/GenerationContext.java b/spring-core/src/main/java/org/springframework/aot/generate/GenerationContext.java index 3aadb51b39..2cd1c770bf 100644 --- a/spring-core/src/main/java/org/springframework/aot/generate/GenerationContext.java +++ b/spring-core/src/main/java/org/springframework/aot/generate/GenerationContext.java @@ -16,11 +16,11 @@ package org.springframework.aot.generate; -import org.springframework.aot.hint.JavaSerializationHints; 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; /** * Central interface used for code generation. @@ -67,7 +67,7 @@ public interface GenerationContext { /** * Return the {@link RuntimeHints} being used by the context. Used to record * {@link ReflectionHints reflection}, {@link ResourceHints resource}, - * {@link JavaSerializationHints serialization} and {@link ProxyHints proxy} + * {@link SerializationHints serialization} and {@link ProxyHints proxy} * hints so that the application can run as a native image. * @return the runtime hints */ diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ClassProxyHint.java b/spring-core/src/main/java/org/springframework/aot/hint/ClassProxyHint.java index 8fab0281f8..043e919229 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ClassProxyHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ClassProxyHint.java @@ -21,22 +21,29 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; +import org.springframework.lang.Nullable; + /** * A hint that describes the need for a proxy against a concrete class. * * @author Stephane Nicoll + * @author Brian Clozel * @since 6.0 */ -public final class ClassProxyHint { +public final class ClassProxyHint implements ConditionalHint { private final TypeReference targetClass; private final List proxiedInterfaces; + @Nullable + private final TypeReference reachableType; + private ClassProxyHint(Builder builder) { this.targetClass = builder.targetClass; this.proxiedInterfaces = builder.proxiedInterfaces.stream().distinct().toList(); + this.reachableType = builder.reachableType; } /** @@ -76,6 +83,12 @@ public final class ClassProxyHint { return this.proxiedInterfaces; } + @Nullable + @Override + public TypeReference getReachableType() { + return this.reachableType; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -86,7 +99,8 @@ public final class ClassProxyHint { } ClassProxyHint that = (ClassProxyHint) o; return this.targetClass.equals(that.targetClass) - && this.proxiedInterfaces.equals(that.proxiedInterfaces); + && this.proxiedInterfaces.equals(that.proxiedInterfaces) + && Objects.equals(this.reachableType, that.reachableType); } @Override @@ -104,6 +118,9 @@ public final class ClassProxyHint { private final LinkedList proxiedInterfaces = new LinkedList<>(); + @Nullable + private TypeReference reachableType; + Builder(TypeReference targetClass) { this.targetClass = targetClass; @@ -130,6 +147,18 @@ public final class ClassProxyHint { return this; } + /** + * Make this hint conditional on the fact that the specified type + * can be resolved. + * @param reachableType the type that should be reachable for this + * hint to apply + * @return {@code this}, to facilitate method chaining + */ + public Builder onReachableType(TypeReference reachableType) { + this.reachableType = reachableType; + return this; + } + /** * Create a {@link ClassProxyHint} based on the state of this builder. * @return a class proxy hint diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ConditionalHint.java b/spring-core/src/main/java/org/springframework/aot/hint/ConditionalHint.java new file mode 100644 index 0000000000..c9784ef592 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/ConditionalHint.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2022 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.hint; + +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; + +/** + * Contract for {@link RuntimeHints runtime hints} that only apply + * if the described condition is met. + * + * @author Brian Clozel + * @since 6.0 + */ +public interface ConditionalHint { + + /** + * Return the type that should be reachable for this hint to apply, or + * {@code null} if this hint should always been applied. + * @return the reachable type, if any + */ + @Nullable + TypeReference getReachableType(); + + /** + * Whether the condition described for this hint is met. If it is not, + * the hint does not apply. + *

Instead of checking for actual reachability of a type in the + * application, the classpath is checked for the presence of this + * type as a simple heuristic. + * @param classLoader the current classloader + * @return whether the condition is met and the hint applies + */ + default boolean conditionMatches(ClassLoader classLoader) { + TypeReference reachableType = getReachableType(); + if (reachableType != null) { + return ClassUtils.isPresent(reachableType.getCanonicalName(), classLoader); + } + return true; + } + +} 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 new file mode 100644 index 0000000000..616e965b09 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/JavaSerializationHint.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2022 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.hint; + + +import java.io.Serializable; +import java.util.Objects; + +import org.springframework.lang.Nullable; + +/** + * A hint that describes the need for Java serialization at runtime. + * + * @author Brian Clozel + * @since 6.0 + */ +public class JavaSerializationHint implements ConditionalHint { + + private final TypeReference type; + + @Nullable + private final TypeReference reachableType; + + JavaSerializationHint(Builder builder) { + this.type = builder.type; + this.reachableType = builder.reachableType; + } + + /** + * Return the {@link TypeReference type} that needs to be serialized using + * Java serialization at runtime. + * @return a {@link Serializable} type + */ + public TypeReference getType() { + return this.type; + } + + @Override + @Nullable + public TypeReference getReachableType() { + return this.reachableType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + JavaSerializationHint that = (JavaSerializationHint) o; + return this.type.equals(that.type) + && Objects.equals(this.reachableType, that.reachableType); + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.reachableType); + } + + + /** + * Builder for {@link JavaSerializationHint}. + */ + public static class Builder { + + private final TypeReference type; + + @Nullable + private TypeReference reachableType; + + + Builder(TypeReference type) { + this.type = type; + } + + /** + * Make this hint conditional on the fact that the specified type + * can be resolved. + * @param reachableType the type that should be reachable for this + * hint to apply + * @return {@code this}, to facilitate method chaining + */ + public Builder onReachableType(TypeReference reachableType) { + this.reachableType = reachableType; + return this; + } + + /** + * Create a {@link JavaSerializationHint} based on the state of this builder. + * @return a java serialization hint + */ + JavaSerializationHint build() { + return new JavaSerializationHint(this); + } + + } +} diff --git a/spring-core/src/main/java/org/springframework/aot/hint/JavaSerializationHints.java b/spring-core/src/main/java/org/springframework/aot/hint/JavaSerializationHints.java deleted file mode 100644 index b317569995..0000000000 --- a/spring-core/src/main/java/org/springframework/aot/hint/JavaSerializationHints.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2002-2022 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.hint; - -import java.io.Serializable; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.stream.Stream; - -/** - * Gather the need for Java serialization at runtime. - * - * @author Stephane Nicoll - * @since 6.0 - * @see Serializable - */ -public class JavaSerializationHints { - - private final Set types; - - - public JavaSerializationHints() { - this.types = new LinkedHashSet<>(); - } - - /** - * Return the {@link TypeReference types} that need to be serialized using - * Java serialization at runtime. - * @return a stream of {@link Serializable} types - */ - public Stream types() { - return this.types.stream(); - } - - /** - * Register that the type defined by the specified {@link TypeReference} - * need to be serialized using java serialization. - * @param type the type to register - * @return {@code this}, to facilitate method chaining - */ - public JavaSerializationHints registerType(TypeReference type) { - this.types.add(type); - return this; - } - - /** - * Register that the specified type need to be serialized using java - * serialization. - * @param type the type to register - * @return {@code this}, to facilitate method chaining - */ - public JavaSerializationHints registerType(Class type) { - return registerType(TypeReference.of(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 546b0ae6bb..c9f21c481c 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 @@ -22,20 +22,27 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; +import org.springframework.lang.Nullable; + /** * A hint that describes the need of a JDK {@link Proxy}, that is an * interfaces-based proxy. * * @author Stephane Nicoll + * @author Brian Clozel * @since 6.0 */ -public final class JdkProxyHint { +public final class JdkProxyHint implements ConditionalHint { private final List proxiedInterfaces; + @Nullable + private final TypeReference reachableType; + private JdkProxyHint(Builder builder) { this.proxiedInterfaces = List.copyOf(builder.proxiedInterfaces); + this.reachableType = builder.reachableType; } /** @@ -64,6 +71,12 @@ public final class JdkProxyHint { return this.proxiedInterfaces; } + @Nullable + @Override + public TypeReference getReachableType() { + return this.reachableType; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -73,7 +86,8 @@ public final class JdkProxyHint { return false; } JdkProxyHint that = (JdkProxyHint) o; - return this.proxiedInterfaces.equals(that.proxiedInterfaces); + return this.proxiedInterfaces.equals(that.proxiedInterfaces) + && Objects.equals(this.reachableType, that.reachableType); } @Override @@ -89,6 +103,10 @@ public final class JdkProxyHint { private final LinkedList proxiedInterfaces; + @Nullable + private TypeReference reachableType; + + Builder() { this.proxiedInterfaces = new LinkedList<>(); } @@ -113,6 +131,18 @@ public final class JdkProxyHint { return this; } + /** + * Make this hint conditional on the fact that the specified type + * can be resolved. + * @param reachableType the type that should be reachable for this + * hint to apply + * @return {@code this}, to facilitate method chaining + */ + public Builder onReachableType(TypeReference reachableType) { + this.reachableType = reachableType; + 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/ResourceBundleHint.java b/spring-core/src/main/java/org/springframework/aot/hint/ResourceBundleHint.java index 21efdfa289..55a2010225 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ResourceBundleHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourceBundleHint.java @@ -16,21 +16,29 @@ package org.springframework.aot.hint; +import java.util.Objects; import java.util.ResourceBundle; +import org.springframework.lang.Nullable; + /** - * A hint that describes the need to access to a {@link ResourceBundle}. + * A hint that describes the need to access a {@link ResourceBundle}. * * @author Stephane Nicoll + * @author Brian Clozel * @since 6.0 */ -public class ResourceBundleHint { +public final class ResourceBundleHint implements ConditionalHint { private final String baseName; + @Nullable + private TypeReference reachableType; - ResourceBundleHint(String baseName) { - this.baseName = baseName; + + ResourceBundleHint(Builder builder) { + this.baseName = builder.baseName; + this.reachableType = builder.reachableType; } /** @@ -41,4 +49,70 @@ public class ResourceBundleHint { return this.baseName; } + @Nullable + @Override + public TypeReference getReachableType() { + return this.reachableType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ResourceBundleHint that = (ResourceBundleHint) o; + return this.baseName.equals(that.baseName) + && Objects.equals(this.reachableType, that.reachableType); + } + + @Override + public int hashCode() { + return Objects.hash(this.baseName, this.reachableType); + } + + /** + * Builder for {@link ResourceBundleHint}. + */ + public static class Builder { + + private String baseName; + + @Nullable + private TypeReference reachableType; + + /** + * Make this hint conditional on the fact that the specified type + * can be resolved. + * @param reachableType the type that should be reachable for this + * hint to apply + * @return {@code this}, to facilitate method chaining + */ + public Builder onReachableType(TypeReference reachableType) { + this.reachableType = reachableType; + return this; + } + + /** + * Use the the {@code baseName} of the resource bundle. + * @return {@code this}, to facilitate method chaining + */ + public Builder baseName(String baseName) { + this.baseName = baseName; + return this; + } + + /** + * Creates a {@link ResourceBundleHint} based on the state of this + * builder. + * @return a resource bundle hint + */ + ResourceBundleHint build() { + return new ResourceBundleHint(this); + } + + } + } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ResourceHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ResourceHints.java index 76cf9355bd..7f47d580e5 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ResourceHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourceHints.java @@ -24,7 +24,6 @@ import java.util.Set; import java.util.function.Consumer; import java.util.stream.Stream; -import org.springframework.aot.hint.ResourcePatternHint.Builder; import org.springframework.lang.Nullable; /** @@ -37,9 +36,9 @@ public class ResourceHints { private final Set types; - private final List resourcePatternHints; + private final List resourcePatternHints; - private final Set resourceBundleHints; + private final Set resourceBundleHints; public ResourceHints() { @@ -50,10 +49,10 @@ public class ResourceHints { /** * Return the resources that should be made available at runtime. - * @return a stream of {@link ResourcePatternHint} + * @return a stream of {@link ResourcePatternHints} */ - public Stream resourcePatterns() { - Stream patterns = this.resourcePatternHints.stream().map(Builder::build); + public Stream resourcePatterns() { + Stream patterns = this.resourcePatternHints.stream(); return (this.types.isEmpty() ? patterns : Stream.concat(Stream.of(typesPatternResourceHint()), patterns)); } @@ -63,22 +62,21 @@ public class ResourceHints { * @return a stream of {@link ResourceBundleHint} */ public Stream resourceBundles() { - return this.resourceBundleHints.stream().map(ResourceBundleHint::new); + return this.resourceBundleHints.stream(); } /** * Register that the resources matching the specified pattern should be * made available at runtime. - * @param include a pattern of the resources to include * @param resourceHint a builder to further customize the resource pattern * @return {@code this}, to facilitate method chaining */ - public ResourceHints registerPattern(String include, @Nullable Consumer resourceHint) { - Builder builder = new Builder().includes(include); + public ResourceHints registerPattern(@Nullable Consumer resourceHint) { + ResourcePatternHints.Builder builder = new ResourcePatternHints.Builder(); if (resourceHint != null) { resourceHint.accept(builder); } - this.resourcePatternHints.add(builder); + this.resourcePatternHints.add(builder.build()); return this; } @@ -89,7 +87,7 @@ public class ResourceHints { * @return {@code this}, to facilitate method chaining */ public ResourceHints registerPattern(String include) { - return registerPattern(include, null); + return registerPattern(builder -> builder.includes(include)); } /** @@ -117,15 +115,30 @@ public class ResourceHints { * Register that the resource bundle with the specified base name should * be made available at runtime. * @param baseName the base name of the resource bundle + * @param resourceHint a builder to further customize the resource bundle * @return {@code this}, to facilitate method chaining */ - public ResourceHints registerResourceBundle(String baseName) { - this.resourceBundleHints.add(baseName); + public ResourceHints registerResourceBundle(String baseName, @Nullable Consumer resourceHint) { + ResourceBundleHint.Builder builder = new ResourceBundleHint.Builder().baseName(baseName); + if (resourceHint != null) { + resourceHint.accept(builder); + } + this.resourceBundleHints.add(builder.build()); return this; } - private ResourcePatternHint typesPatternResourceHint() { - Builder builder = new Builder(); + /** + * Register that the resource bundle with the specified base name should + * be made available at runtime. + * @param baseName the base name of the resource bundle + * @return {@code this}, to facilitate method chaining + */ + public ResourceHints registerResourceBundle(String baseName) { + return registerResourceBundle(baseName, null); + } + + private ResourcePatternHints typesPatternResourceHint() { + ResourcePatternHints.Builder builder = new ResourcePatternHints.Builder(); this.types.forEach(type -> builder.includes(toIncludePattern(type))); return builder.build(); } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java index 891c0808fa..234b90f97e 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java @@ -16,89 +16,61 @@ package org.springframework.aot.hint; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; +import java.util.Objects; + +import org.springframework.lang.Nullable; /** * A hint that describes resources that should be made available at runtime. - * *

The patterns may be a simple path which has a one-to-one mapping to a * resource on the classpath, or alternatively may contain the special * {@code *} character to indicate a wildcard search. * * @author Stephane Nicoll + * @author Brian Clozel * @since 6.0 */ -public final class ResourcePatternHint { +public final class ResourcePatternHint implements ConditionalHint { - private final List includes; + private final String pattern; - private final List excludes; + @Nullable + private final TypeReference reachableType; - - private ResourcePatternHint(Builder builder) { - this.includes = new ArrayList<>(builder.includes); - this.excludes = new ArrayList<>(builder.excludes); + ResourcePatternHint(String pattern, @Nullable TypeReference reachableType) { + this.pattern = pattern; + this.reachableType = reachableType; } /** - * Return the include patterns to use to identify the resources to match. - * @return the include patterns + * Return the pattern to use for identifying the resources to match. + * @return the patterns */ - public List getIncludes() { - return this.includes; + public String getPattern() { + return this.pattern; } - /** - * Return the exclude patterns to use to identify the resources to match. - * @return the exclude patterns - */ - public List getExcludes() { - return this.excludes; + @Nullable + @Override + public TypeReference getReachableType() { + return this.reachableType; } - - /** - * Builder for {@link ResourcePatternHint}. - */ - public static class Builder { - - private final Set includes = new LinkedHashSet<>(); - - private final Set excludes = new LinkedHashSet<>(); - - - /** - * Includes the resources matching the specified pattern. - * @param includes the include patterns - * @return {@code this}, to facilitate method chaining - */ - public Builder includes(String... includes) { - this.includes.addAll(Arrays.asList(includes)); - return this; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - /** - * Exclude resources matching the specified pattern. - * @param excludes the excludes pattern - * @return {@code this}, to facilitate method chaining - */ - public Builder excludes(String... excludes) { - this.excludes.addAll(Arrays.asList(excludes)); - return this; - } - - /** - * Creates a {@link ResourcePatternHint} based on the state of this - * builder. - * @return a resource pattern hint - */ - ResourcePatternHint build() { - return new ResourcePatternHint(this); + if (o == null || getClass() != o.getClass()) { + return false; } + ResourcePatternHint that = (ResourcePatternHint) o; + return this.pattern.equals(that.pattern) + && Objects.equals(this.reachableType, that.reachableType); + } + @Override + public int hashCode() { + return Objects.hash(this.pattern, this.reachableType); } } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java new file mode 100644 index 0000000000..4a8a759f2d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2022 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.hint; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * A collection of {@link ResourcePatternHint} describing whether + * resources should be made available at runtime through a matching + * mechanism or inclusion/exclusion. + * + * @author Stephane Nicoll + * @author Brian Clozel + * @since 6.0 + */ +public final class ResourcePatternHints { + + private final List includes; + + private final List excludes; + + + private ResourcePatternHints(Builder builder) { + this.includes = new ArrayList<>(builder.includes); + this.excludes = new ArrayList<>(builder.excludes); + } + + /** + * Return the include patterns to use to identify the resources to match. + * @return the include patterns + */ + public List getIncludes() { + return this.includes; + } + + /** + * Return the exclude patterns to use to identify the resources to match. + * @return the exclude patterns + */ + public List getExcludes() { + return this.excludes; + } + + + /** + * Builder for {@link ResourcePatternHints}. + */ + public static class Builder { + + private final Set includes = new LinkedHashSet<>(); + + private final Set excludes = new LinkedHashSet<>(); + + /** + * Includes the resources matching the specified pattern. + * @param reachableType the type that should be reachable for this hint to apply + * @param includes the include patterns + * @return {@code this}, to facilitate method chaining + */ + public Builder includes(TypeReference reachableType, String... includes) { + List newIncludes = Arrays.stream(includes) + .map(include -> new ResourcePatternHint(include, reachableType)).toList(); + this.includes.addAll(newIncludes); + return this; + } + + /** + * Includes the resources matching the specified pattern. + * @param includes the include patterns + * @return {@code this}, to facilitate method chaining + */ + public Builder includes(String... includes) { + return includes(null, includes); + } + + /** + * Exclude resources matching the specified pattern. + * @param reachableType the type that should be reachable for this hint to apply + * @param excludes the excludes pattern + * @return {@code this}, to facilitate method chaining + */ + public Builder excludes(TypeReference reachableType, String... excludes) { + List newExcludes = Arrays.stream(excludes) + .map(include -> new ResourcePatternHint(include, reachableType)).toList(); + this.excludes.addAll(newExcludes); + return this; + } + + /** + * Exclude resources matching the specified pattern. + * @param excludes the excludes pattern + * @return {@code this}, to facilitate method chaining + */ + public Builder excludes(String... excludes) { + return excludes(null, excludes); + } + + /** + * Creates a {@link ResourcePatternHints} based on the state of this + * builder. + * @return a resource pattern hint + */ + ResourcePatternHints build() { + return new ResourcePatternHints(this); + } + + } +} 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 5a0311ac32..672af1bd28 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 @@ -36,7 +36,7 @@ public class RuntimeHints { private final ResourceHints resources = new ResourceHints(); - private final JavaSerializationHints javaSerialization = new JavaSerializationHints(); + private final SerializationHints serialization = new SerializationHints(); private final ProxyHints proxies = new ProxyHints(); @@ -59,10 +59,10 @@ public class RuntimeHints { /** * Provide access to serialization-based hints. - * @return java serialization hints + * @return serialization hints */ - public JavaSerializationHints javaSerialization() { - return this.javaSerialization; + 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 new file mode 100644 index 0000000000..e12856f326 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/SerializationHints.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2022 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.hint; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.springframework.lang.Nullable; + +/** + * Gather the need for Java serialization at runtime. + * + * @author Stephane Nicoll + * @since 6.0 + * @see Serializable + */ +public class SerializationHints { + + private final Set javaSerializationHints; + + + public SerializationHints() { + this.javaSerializationHints = new LinkedHashSet<>(); + } + + /** + * Return the {@link JavaSerializationHint java serialization hints} for types + * that need to be serialized using Java serialization at runtime. + * @return a stream of {@link JavaSerializationHint java serialization hints} + */ + public Stream javaSerialization() { + return this.javaSerializationHints.stream(); + } + + /** + * Register that the type defined by the specified {@link TypeReference} + * need to be serialized using java serialization. + * @param type the type to register + * @param serializationHint a builder to further customize the serialization + * @return {@code this}, to facilitate method chaining + */ + public SerializationHints registerType(TypeReference type, @Nullable Consumer serializationHint) { + JavaSerializationHint.Builder builder = new JavaSerializationHint.Builder(type); + if (serializationHint != null) { + serializationHint.accept(builder); + } + this.javaSerializationHints.add(builder.build()); + return this; + } + + /** + * Register that the type defined by the specified {@link TypeReference} + * need to be serialized using java serialization. + * @param type the type to register + * @return {@code this}, to facilitate method chaining + */ + public SerializationHints registerType(TypeReference type) { + return registerType(type, null); + } + + /** + * Register that the specified type need to be serialized using java + * serialization. + * @param type the type to register + * @param serializationHint a builder to further customize the serialization + * @return {@code this}, to facilitate method chaining + */ + public SerializationHints registerType(Class type, @Nullable Consumer serializationHint) { + return registerType(TypeReference.of(type), serializationHint); + } + + /** + * Register that the specified type need to be serialized using java + * serialization. + * @param type the type to register + * @return {@code this}, to facilitate method chaining + */ + public SerializationHints registerType(Class type) { + return registerType(type, null); + } + +} 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 3536eadf84..81fa6d3f4b 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 @@ -37,7 +37,7 @@ import org.springframework.util.Assert; * @author Stephane Nicoll * @since 6.0 */ -public final class TypeHint { +public final class TypeHint implements ConditionalHint { private final TypeReference type; @@ -81,12 +81,8 @@ public final class TypeHint { return this.type; } - /** - * Return the type that should be reachable for this hint to apply, or - * {@code null} if this hint should always been applied. - * @return the reachable type, if any - */ @Nullable + @Override public TypeReference getReachableType() { return this.reachableType; } diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsWriter.java deleted file mode 100644 index 106801070f..0000000000 --- a/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsWriter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2002-2022 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.Map; - -import org.springframework.aot.hint.JavaSerializationHints; -import org.springframework.aot.hint.TypeReference; - -/** - * Write a {@link JavaSerializationHints} to the JSON output expected by the - * GraalVM {@code native-image} compiler, typically named - * {@code serialization-config.json}. - * - * @author Sebastien Deleuze - * @author Stephane Nicoll - * @since 6.0 - * @see Native Image Build Configuration - */ -class JavaSerializationHintsWriter { - - public static final JavaSerializationHintsWriter INSTANCE = new JavaSerializationHintsWriter(); - - public void write(BasicJsonWriter writer, JavaSerializationHints hints) { - writer.writeArray(hints.types().map(this::toAttributes).toList()); - } - - private Map toAttributes(TypeReference typeReference) { - LinkedHashMap attributes = new LinkedHashMap<>(); - attributes.put("name", typeReference); - return attributes; - } - -} 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 143f708119..192c7c48d0 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 @@ -18,11 +18,11 @@ package org.springframework.aot.nativex; import java.util.function.Consumer; -import org.springframework.aot.hint.JavaSerializationHints; 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,7 +30,7 @@ import org.springframework.aot.hint.RuntimeHints; * @author Sebastien Deleuze * @author Stephane Nicoll * @since 6.0 - * @see Native Image Build Configuration + * @see Native Image Build Configuration */ public abstract class NativeConfigurationWriter { @@ -39,8 +39,8 @@ public abstract class NativeConfigurationWriter { * @param hints the hints to handle */ public void write(RuntimeHints hints) { - if (hints.javaSerialization().types().findAny().isPresent()) { - writeJavaSerializationHints(hints.javaSerialization()); + if (hints.serialization().javaSerialization().findAny().isPresent()) { + writeJavaSerializationHints(hints.serialization()); } if (hints.proxies().jdkProxies().findAny().isPresent()) { writeProxyHints(hints.proxies()); @@ -62,9 +62,9 @@ public abstract class NativeConfigurationWriter { */ protected abstract void writeTo(String fileName, Consumer writer); - private void writeJavaSerializationHints(JavaSerializationHints hints) { + private void writeJavaSerializationHints(SerializationHints hints) { writeTo("serialization-config.json", writer -> - JavaSerializationHintsWriter.INSTANCE.write(writer, hints)); + SerializationHintsWriter.INSTANCE.write(writer, hints)); } private void writeProxyHints(ProxyHints 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 index 9def944c0d..2db093c133 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java @@ -30,9 +30,10 @@ import org.springframework.aot.hint.TypeReference; * * @author Sebastien Deleuze * @author Stephane Nicoll + * @author Brian Clozel * @since 6.0 - * @see Dynamic Proxy in Native Image - * @see Native Image Build Configuration + * @see Dynamic Proxy in Native Image + * @see Native Image Build Configuration */ class ProxyHintsWriter { @@ -44,9 +45,18 @@ class ProxyHintsWriter { private Map toAttributes(JdkProxyHint hint) { Map attributes = new LinkedHashMap<>(); + handleCondition(attributes, hint); attributes.put("interfaces", hint.getProxiedInterfaces().stream() .map(TypeReference::getCanonicalName).toList()); 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/ResourceHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java index 266ffc26be..ee108d5476 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java @@ -25,9 +25,11 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.aot.hint.ConditionalHint; import org.springframework.aot.hint.ResourceBundleHint; import org.springframework.aot.hint.ResourceHints; import org.springframework.aot.hint.ResourcePatternHint; +import org.springframework.aot.hint.ResourcePatternHints; import org.springframework.lang.Nullable; /** @@ -36,9 +38,10 @@ import org.springframework.lang.Nullable; * * @author Sebastien Deleuze * @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 Native Image Build Configuration */ class ResourceHintsWriter { @@ -53,9 +56,9 @@ class ResourceHintsWriter { private Map toAttributes(ResourceHints hint) { Map attributes = new LinkedHashMap<>(); - addIfNotEmpty(attributes, "includes", hint.resourcePatterns().map(ResourcePatternHint::getIncludes) + addIfNotEmpty(attributes, "includes", hint.resourcePatterns().map(ResourcePatternHints::getIncludes) .flatMap(List::stream).distinct().map(this::toAttributes).toList()); - addIfNotEmpty(attributes, "excludes", hint.resourcePatterns().map(ResourcePatternHint::getExcludes) + addIfNotEmpty(attributes, "excludes", hint.resourcePatterns().map(ResourcePatternHints::getExcludes) .flatMap(List::stream).distinct().map(this::toAttributes).toList()); return attributes; } @@ -66,13 +69,15 @@ class ResourceHintsWriter { private Map toAttributes(ResourceBundleHint hint) { Map attributes = new LinkedHashMap<>(); + handleCondition(attributes, hint); attributes.put("name", hint.getBaseName()); return attributes; } - private Map toAttributes(String pattern) { + private Map toAttributes(ResourcePatternHint hint) { Map attributes = new LinkedHashMap<>(); - attributes.put("pattern", patternToRegexp(pattern)); + handleCondition(attributes, hint); + attributes.put("pattern", patternToRegexp(hint.getPattern())); return attributes; } @@ -96,4 +101,12 @@ class ResourceHintsWriter { } } + private void handleCondition(Map attributes, ConditionalHint 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/SerializationHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsWriter.java new file mode 100644 index 0000000000..138f7ff95e --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsWriter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2022 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.Map; + +import org.springframework.aot.hint.ConditionalHint; +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}. + * + * @author Sebastien Deleuze + * @author Stephane Nicoll + * @author Brian Clozel + * @since 6.0 + * @see Native Image Build Configuration + */ +class SerializationHintsWriter { + + public static final SerializationHintsWriter INSTANCE = new SerializationHintsWriter(); + + public void write(BasicJsonWriter writer, SerializationHints hints) { + writer.writeArray(hints.javaSerialization().map(this::toAttributes).toList()); + } + + private Map toAttributes(JavaSerializationHint serializationHint) { + LinkedHashMap attributes = new LinkedHashMap<>(); + handleCondition(attributes, serializationHint); + attributes.put("name", serializationHint.getType()); + return attributes; + } + + private void handleCondition(Map attributes, ConditionalHint hint) { + if (hint.getReachableType() != null) { + Map conditionAttributes = new LinkedHashMap<>(); + conditionAttributes.put("typeReachable", hint.getReachableType()); + attributes.put("condition", conditionAttributes); + } + } + +} diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ClassProxyHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ClassProxyHintTests.java index 91848f6bd0..2e74130c68 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ClassProxyHintTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ClassProxyHintTests.java @@ -32,24 +32,25 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ClassProxyHint}. * * @author Stephane Nicoll + * @author Brian Clozel */ class ClassProxyHintTests { @Test - void equalsWithWithSameInstanceIsTrue() { + void equalsWithSameInstanceIsTrue() { ClassProxyHint hint = ClassProxyHint.of(Properties.class).build(); assertThat(hint).isEqualTo(hint); } @Test - void equalsWithWithSameTargetClassIsTrue() { + void equalsWithSameTargetClassIsTrue() { ClassProxyHint first = ClassProxyHint.of(Properties.class).build(); ClassProxyHint second = ClassProxyHint.of(TypeReference.of(Properties.class)).build(); assertThat(first).isEqualTo(second); } @Test - void equalsWithWithSameProxiedInterfacesIsTrue() { + void equalsWithSameProxiedInterfacesIsTrue() { ClassProxyHint first = ClassProxyHint.of(Properties.class) .proxiedInterfaces(Serializable.class).build(); ClassProxyHint second = ClassProxyHint.of(Properties.class) @@ -58,14 +59,14 @@ class ClassProxyHintTests { } @Test - void equalsWithWithDifferentTargetClassIsFalse() { + void equalsWithDifferentTargetClassIsFalse() { ClassProxyHint first = ClassProxyHint.of(Properties.class).build(); ClassProxyHint second = ClassProxyHint.of(Hashtable.class).build(); assertThat(first).isNotEqualTo(second); } @Test - void equalsWithWithSameProxiedInterfacesDifferentOrderIsFalse() { + void equalsWithSameProxiedInterfacesDifferentOrderIsFalse() { ClassProxyHint first = ClassProxyHint.of(Properties.class) .proxiedInterfaces(Serializable.class, Closeable.class).build(); ClassProxyHint second = ClassProxyHint.of(Properties.class) @@ -75,7 +76,7 @@ class ClassProxyHintTests { } @Test - void equalsWithWithDifferentProxiedInterfacesIsFalse() { + void equalsWithDifferentProxiedInterfacesIsFalse() { ClassProxyHint first = ClassProxyHint.of(Properties.class) .proxiedInterfaces(Serializable.class).build(); ClassProxyHint second = ClassProxyHint.of(Properties.class) @@ -90,4 +91,11 @@ class ClassProxyHintTests { assertThat(first).isNotEqualTo(second); } + @Test + void equalsWithDifferentConditionIsFalse() { + ClassProxyHint first = ClassProxyHint.of(Properties.class).build(); + ClassProxyHint second = ClassProxyHint.of(Properties.class).onReachableType(TypeReference.of("org.example.test")).build(); + assertThat(first).isNotEqualTo(second); + } + } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/JdkProxyHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/JdkProxyHintTests.java index d0095e3083..992a5187df 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/JdkProxyHintTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/JdkProxyHintTests.java @@ -33,13 +33,13 @@ import static org.assertj.core.api.Assertions.assertThat; class JdkProxyHintTests { @Test - void equalsWithWithSameInstanceIsTrue() { + void equalsWithSameInstanceIsTrue() { JdkProxyHint hint = new Builder().proxiedInterfaces(Function.class, Consumer.class).build(); assertThat(hint).isEqualTo(hint); } @Test - void equalsWithWithSameProxiedInterfacesIsTrue() { + void equalsWithSameProxiedInterfacesIsTrue() { JdkProxyHint first = new Builder().proxiedInterfaces(Function.class, Consumer.class).build(); JdkProxyHint second = new Builder().proxiedInterfaces(TypeReference.of(Function.class.getName()), TypeReference.of(Consumer.class)).build(); @@ -47,7 +47,16 @@ class JdkProxyHintTests { } @Test - void equalsWithWithSameProxiedInterfacesDifferentOrderIsFalse() { + void equalsWithSameProxiedInterfacesAndDifferentConditionIsFalse() { + JdkProxyHint first = new Builder().proxiedInterfaces(Function.class, Consumer.class) + .onReachableType(TypeReference.of(String.class)).build(); + JdkProxyHint second = new Builder().proxiedInterfaces(TypeReference.of(Function.class.getName()), + TypeReference.of(Consumer.class)).onReachableType(TypeReference.of(Function.class)).build(); + assertThat(first).isNotEqualTo(second); + } + + @Test + void equalsWithSameProxiedInterfacesDifferentOrderIsFalse() { JdkProxyHint first = new Builder().proxiedInterfaces(Function.class, Consumer.class).build(); JdkProxyHint second = new Builder().proxiedInterfaces(TypeReference.of(Consumer.class), TypeReference.of(Function.class.getName())).build(); @@ -55,7 +64,7 @@ class JdkProxyHintTests { } @Test - void equalsWithWithDifferentProxiedInterfacesIsFalse() { + void equalsWithDifferentProxiedInterfacesIsFalse() { JdkProxyHint first = new Builder().proxiedInterfaces(Function.class).build(); JdkProxyHint second = new Builder().proxiedInterfaces(TypeReference.of(Function.class.getName()), TypeReference.of(Consumer.class)).build(); diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java index a5ee090923..b9936c8197 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java @@ -84,8 +84,8 @@ class ResourceHintsTests { @Test void registerPatternWithIncludesAndExcludes() { - this.resourceHints.registerPattern("com/example/*.properties", - resourceHint -> resourceHint.excludes("com/example/to-ignore.properties")); + this.resourceHints.registerPattern(resourceHint -> + resourceHint.includes("com/example/*.properties").excludes("com/example/to-ignore.properties")); assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(patternOf( List.of("com/example/*.properties"), List.of("com/example/to-ignore.properties"))); @@ -107,7 +107,7 @@ class ResourceHintsTests { } - private Consumer patternOf(String... includes) { + private Consumer patternOf(String... includes) { return patternOf(Arrays.asList(includes), Collections.emptyList()); } @@ -115,10 +115,10 @@ class ResourceHintsTests { return resourceBundleHint -> assertThat(resourceBundleHint.getBaseName()).isEqualTo(baseName); } - private Consumer patternOf(List includes, List excludes) { + private Consumer patternOf(List includes, List excludes) { return pattern -> { - assertThat(pattern.getIncludes()).containsExactlyElementsOf(includes); - assertThat(pattern.getExcludes()).containsExactlyElementsOf(excludes); + assertThat(pattern.getIncludes()).map(ResourcePatternHint::getPattern).containsExactlyElementsOf(includes); + assertThat(pattern.getExcludes()).map(ResourcePatternHint::getPattern).containsExactlyElementsOf(excludes); }; } 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 c969057d7d..cf4e95342c 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 @@ -48,15 +48,16 @@ class RuntimeHintsTests { void resourceHintWithClass() { this.hints.resources().registerType(String.class); assertThat(this.hints.resources().resourcePatterns()).singleElement().satisfies(resourceHint -> { - assertThat(resourceHint.getIncludes()).containsExactly("java/lang/String.class"); + assertThat(resourceHint.getIncludes()).map(ResourcePatternHint::getPattern).containsExactly("java/lang/String.class"); assertThat(resourceHint.getExcludes()).isEmpty(); }); } @Test void javaSerializationHintWithClass() { - this.hints.javaSerialization().registerType(String.class); - assertThat(this.hints.javaSerialization().types()).containsExactly(TypeReference.of(String.class)); + this.hints.serialization().registerType(String.class); + assertThat(this.hints.serialization().javaSerialization().map(JavaSerializationHint::getType)) + .containsExactly(TypeReference.of(String.class)); } @Test diff --git a/spring-core/src/test/java/org/springframework/aot/hint/JavaSerializationHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/SerializationHintsTests.java similarity index 66% rename from spring-core/src/test/java/org/springframework/aot/hint/JavaSerializationHintsTests.java rename to spring-core/src/test/java/org/springframework/aot/hint/SerializationHintsTests.java index bb61475bd3..7a2d7e26f6 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/JavaSerializationHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/SerializationHintsTests.java @@ -23,20 +23,20 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link JavaSerializationHints}. + * Tests for {@link SerializationHints}. * * @author Stephane Nicoll */ -class JavaSerializationHintsTests { +class SerializationHintsTests { - private final JavaSerializationHints javaSerializationHints = new JavaSerializationHints(); + private final SerializationHints serializationHints = new SerializationHints(); @Test void registerTypeTwiceExposesOneHint() { - this.javaSerializationHints.registerType(URL.class); - this.javaSerializationHints.registerType(TypeReference.of(URL.class.getName())); - assertThat(this.javaSerializationHints.types()).singleElement() - .isEqualTo(TypeReference.of(URL.class)); + this.serializationHints.registerType(URL.class); + this.serializationHints.registerType(TypeReference.of(URL.class.getName())); + assertThat(this.serializationHints.javaSerialization()).singleElement() + .extracting(JavaSerializationHint::getType).isEqualTo(TypeReference.of(URL.class)); } } 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 c4308ca9f5..6afb2861a9 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 @@ -32,12 +32,12 @@ import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; import org.springframework.aot.hint.ExecutableMode; -import org.springframework.aot.hint.JavaSerializationHints; import org.springframework.aot.hint.MemberCategory; 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; import org.springframework.util.MimeType; @@ -66,7 +66,7 @@ public class FileNativeConfigurationWriterTests { void serializationConfig() throws IOException, JSONException { FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir); RuntimeHints hints = new RuntimeHints(); - JavaSerializationHints serializationHints = hints.javaSerialization(); + SerializationHints serializationHints = hints.serialization(); serializationHints.registerType(Integer.class); serializationHints.registerType(Long.class); generator.write(hints); 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 index 53040a0069..3b03bdd8d5 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java @@ -26,6 +26,7 @@ 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}. @@ -41,7 +42,7 @@ public class ProxyHintsWriterTests { } @Test - void one() throws JSONException { + void shouldWriteOneEntry() throws JSONException { ProxyHints hints = new ProxyHints(); hints.registerJdkProxy(Function.class); assertEquals(""" @@ -51,7 +52,7 @@ public class ProxyHintsWriterTests { } @Test - void two() throws JSONException { + void shouldWriteMultipleEntries() throws JSONException { ProxyHints hints = new ProxyHints(); hints.registerJdkProxy(Function.class); hints.registerJdkProxy(Function.class, Consumer.class); @@ -62,6 +63,17 @@ public class ProxyHintsWriterTests { ]""", 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"); 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 index ecd68b1777..9f950f4558 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java @@ -24,11 +24,13 @@ 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 */ public class ResourceHintsWriterTests { @@ -71,18 +73,32 @@ public class ResourceHintsWriterTests { @Test void registerPatternWithIncludesAndExcludes() throws JSONException { ResourceHints hints = new ResourceHints(); - hints.registerPattern("com/example/*.properties", hint -> hint.excludes("com/example/to-ignore.properties")); - hints.registerPattern("org/example/*.properties", hint -> hint.excludes("org/example/to-ignore.properties")); + 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": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"}, - { "pattern": "\\\\Qorg/example/\\\\E.*\\\\Q.properties\\\\E"} + { "pattern": "\\\\Qorg/other/\\\\E.*\\\\Q.properties\\\\E"} ], "excludes": [ { "pattern": "\\\\Qcom/example/to-ignore.properties\\\\E"}, - { "pattern": "\\\\Qorg/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": "\\\\Qcom/example/test.properties\\\\E"} ] } }""", hints); diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/JavaSerializationHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/SerializationHintsWriterTests.java similarity index 59% rename from spring-core/src/test/java/org/springframework/aot/nativex/JavaSerializationHintsWriterTests.java rename to spring-core/src/test/java/org/springframework/aot/nativex/SerializationHintsWriterTests.java index 4b44a93bab..b10d7a595f 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/JavaSerializationHintsWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/SerializationHintsWriterTests.java @@ -23,26 +23,26 @@ import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; -import org.springframework.aot.hint.JavaSerializationHints; +import org.springframework.aot.hint.SerializationHints; import org.springframework.aot.hint.TypeReference; import org.springframework.core.env.Environment; /** - * Tests for {@link JavaSerializationHintsWriter}. + * Tests for {@link SerializationHintsWriter}. * * @author Sebastien Deleuze */ -public class JavaSerializationHintsWriterTests { +public class SerializationHintsWriterTests { @Test - void empty() throws JSONException { - JavaSerializationHints hints = new JavaSerializationHints(); + void shouldWriteEmptyHint() throws JSONException { + SerializationHints hints = new SerializationHints(); assertEquals("[]", hints); } @Test - void one() throws JSONException { - JavaSerializationHints hints = new JavaSerializationHints().registerType(TypeReference.of(String.class)); + void shouldWriteSingleHint() throws JSONException { + SerializationHints hints = new SerializationHints().registerType(TypeReference.of(String.class)); assertEquals(""" [ { "name": "java.lang.String" } @@ -50,8 +50,8 @@ public class JavaSerializationHintsWriterTests { } @Test - void two() throws JSONException { - JavaSerializationHints hints = new JavaSerializationHints() + void shouldWriteMultipleHints() throws JSONException { + SerializationHints hints = new SerializationHints() .registerType(TypeReference.of(String.class)) .registerType(TypeReference.of(Environment.class)); assertEquals(""" @@ -61,10 +61,20 @@ public class JavaSerializationHintsWriterTests { ]""", hints); } - private void assertEquals(String expectedString, JavaSerializationHints hints) throws JSONException { + @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"); - JavaSerializationHintsWriter.INSTANCE.write(writer, hints); + SerializationHintsWriter.INSTANCE.write(writer, hints); JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.NON_EXTENSIBLE); } diff --git a/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderRuntimeHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderRuntimeHintsRegistrarTests.java index 47868ae0bf..114e9fefe6 100644 --- a/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderRuntimeHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderRuntimeHintsRegistrarTests.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ResourcePatternHint; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.TypeHint; @@ -48,7 +49,7 @@ class SpringFactoriesLoaderRuntimeHintsRegistrarTests { @Test void resourceLocationHasHints() { assertThat(this.hints.resources().resourcePatterns()) - .anySatisfy(hint -> assertThat(hint.getIncludes()) + .anySatisfy(hint -> assertThat(hint.getIncludes()).map(ResourcePatternHint::getPattern) .contains(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION)); }