Expand hints conditions support in RuntimeHints

Prior to this commit, the hints conditions were only supported for the
`TypeHint` case. GraalVM generally expanded this concept to all hints
and we should do the same.

Right now, only the `typeReachable` condition is available but we should
design for possible future additions.

This commit introduces a new `ConditionalHint` contract implemented by
all hints compatible with this approach. The condition information is
also used in all configuration writers as a result.

Closes gh-28126
This commit is contained in:
Brian Clozel
2022-06-02 11:12:09 +02:00
parent 2269c0063a
commit 8af1496b37
29 changed files with 806 additions and 275 deletions

View File

@@ -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"));
}

View File

@@ -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
*/

View File

@@ -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<TypeReference> 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<TypeReference> 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

View File

@@ -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.
* <p>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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<TypeReference> 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<TypeReference> 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<? extends Serializable> type) {
return registerType(TypeReference.of(type));
}
}

View File

@@ -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<TypeReference> 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<TypeReference> 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

View File

@@ -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);
}
}
}

View File

@@ -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<TypeReference> types;
private final List<Builder> resourcePatternHints;
private final List<ResourcePatternHints> resourcePatternHints;
private final Set<String> resourceBundleHints;
private final Set<ResourceBundleHint> 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<ResourcePatternHint> resourcePatterns() {
Stream<ResourcePatternHint> patterns = this.resourcePatternHints.stream().map(Builder::build);
public Stream<ResourcePatternHints> resourcePatterns() {
Stream<ResourcePatternHints> 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<ResourceBundleHint> 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<Builder> resourceHint) {
Builder builder = new Builder().includes(include);
public ResourceHints registerPattern(@Nullable Consumer<ResourcePatternHints.Builder> 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<ResourceBundleHint.Builder> 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();
}

View File

@@ -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.
*
* <p>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<String> includes;
private final String pattern;
private final List<String> 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<String> 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<String> getExcludes() {
return this.excludes;
@Nullable
@Override
public TypeReference getReachableType() {
return this.reachableType;
}
/**
* Builder for {@link ResourcePatternHint}.
*/
public static class Builder {
private final Set<String> includes = new LinkedHashSet<>();
private final Set<String> 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);
}
}

View File

@@ -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<ResourcePatternHint> includes;
private final List<ResourcePatternHint> 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<ResourcePatternHint> getIncludes() {
return this.includes;
}
/**
* Return the exclude patterns to use to identify the resources to match.
* @return the exclude patterns
*/
public List<ResourcePatternHint> getExcludes() {
return this.excludes;
}
/**
* Builder for {@link ResourcePatternHints}.
*/
public static class Builder {
private final Set<ResourcePatternHint> includes = new LinkedHashSet<>();
private final Set<ResourcePatternHint> 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<ResourcePatternHint> 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<ResourcePatternHint> 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);
}
}
}

View File

@@ -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;
}
/**

View File

@@ -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<JavaSerializationHint> 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<JavaSerializationHint> 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<JavaSerializationHint.Builder> 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<? extends Serializable> type, @Nullable Consumer<JavaSerializationHint.Builder> 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<? extends Serializable> type) {
return registerType(type, null);
}
}

View File

@@ -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;
}

View File

@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
*/
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<String, Object> toAttributes(TypeReference typeReference) {
LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
attributes.put("name", typeReference);
return attributes;
}
}

View File

@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
* @see <a href="https://www.graalvm.org/22.1/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
*/
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<BasicJsonWriter> 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) {

View File

@@ -30,9 +30,10 @@ import org.springframework.aot.hint.TypeReference;
*
* @author Sebastien Deleuze
* @author Stephane Nicoll
* @author Brian Clozel
* @since 6.0
* @see <a href="https://www.graalvm.org/22.0/reference-manual/native-image/DynamicProxy/">Dynamic Proxy in Native Image</a>
* @see <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
* @see <a href="https://www.graalvm.org/22.1/reference-manual/native-image/DynamicProxy/">Dynamic Proxy in Native Image</a>
* @see <a href="https://www.graalvm.org/22.1/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
*/
class ProxyHintsWriter {
@@ -44,9 +45,18 @@ class ProxyHintsWriter {
private Map<String, Object> toAttributes(JdkProxyHint hint) {
Map<String, Object> attributes = new LinkedHashMap<>();
handleCondition(attributes, hint);
attributes.put("interfaces", hint.getProxiedInterfaces().stream()
.map(TypeReference::getCanonicalName).toList());
return attributes;
}
private void handleCondition(Map<String, Object> attributes, JdkProxyHint hint) {
if (hint.getReachableType() != null) {
Map<String, Object> conditionAttributes = new LinkedHashMap<>();
conditionAttributes.put("typeReachable", hint.getReachableType());
attributes.put("condition", conditionAttributes);
}
}
}

View File

@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/Resources/">Accessing Resources in Native Images</a>
* @see <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
* @see <a href="https://www.graalvm.org/22.1/reference-manual/native-image/Resources/">Accessing Resources in Native Images</a>
* @see <a href="https://www.graalvm.org/22.1/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
*/
class ResourceHintsWriter {
@@ -53,9 +56,9 @@ class ResourceHintsWriter {
private Map<String, Object> toAttributes(ResourceHints hint) {
Map<String, Object> 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<String, Object> toAttributes(ResourceBundleHint hint) {
Map<String, Object> attributes = new LinkedHashMap<>();
handleCondition(attributes, hint);
attributes.put("name", hint.getBaseName());
return attributes;
}
private Map<String, Object> toAttributes(String pattern) {
private Map<String, Object> toAttributes(ResourcePatternHint hint) {
Map<String, Object> 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<String, Object> attributes, ConditionalHint hint) {
if (hint.getReachableType() != null) {
Map<String, Object> conditionAttributes = new LinkedHashMap<>();
conditionAttributes.put("typeReachable", hint.getReachableType());
attributes.put("condition", conditionAttributes);
}
}
}

View File

@@ -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 <a href="https://www.graalvm.org/22.1/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
*/
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<String, Object> toAttributes(JavaSerializationHint serializationHint) {
LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
handleCondition(attributes, serializationHint);
attributes.put("name", serializationHint.getType());
return attributes;
}
private void handleCondition(Map<String, Object> attributes, ConditionalHint hint) {
if (hint.getReachableType() != null) {
Map<String, Object> conditionAttributes = new LinkedHashMap<>();
conditionAttributes.put("typeReachable", hint.getReachableType());
attributes.put("condition", conditionAttributes);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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<ResourcePatternHint> patternOf(String... includes) {
private Consumer<ResourcePatternHints> 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<ResourcePatternHint> patternOf(List<String> includes, List<String> excludes) {
private Consumer<ResourcePatternHints> patternOf(List<String> includes, List<String> 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);
};
}

View File

@@ -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

View File

@@ -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));
}
}

View File

@@ -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);

View File

@@ -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");

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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));
}