diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java
index e1ff8ecc3a3..9b53915eb58 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java
@@ -28,8 +28,8 @@ import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedMethods;
-import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsPredicates;
import org.springframework.aot.test.generator.compile.Compiled;
import org.springframework.aot.test.generator.compile.TestCompiler;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -277,15 +277,8 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
}
private void assertHasMethodInvokeHints(Class> beanType, String... methodNames) {
- assertThat(this.hints.reflection().getTypeHint(beanType)).satisfies(typeHint -> {
- for (String methodName : methodNames) {
- assertThat(typeHint.methods()).anySatisfy(methodHint -> {
- assertThat(methodHint.getName()).isEqualTo(methodName);
- assertThat(methodHint.getModes())
- .containsExactly(ExecutableMode.INVOKE);
- });
- }
- });
+ assertThat(methodNames).allMatch(methodName ->
+ RuntimeHintsPredicates.reflection().onMethod(beanType, methodName).invoke().test(this.hints));
}
@Test
diff --git a/spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java b/spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java
index 60516491bb4..548977aa1c3 100644
--- a/spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java
+++ b/spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java
@@ -29,6 +29,7 @@ import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.InMemoryGeneratedFiles;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsPredicates;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
@@ -96,8 +97,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
void shouldRegisterAnnotation() {
process(SampleMethodMetaAnnotatedBean.class);
RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
- assertThat(runtimeHints.reflection().getTypeHint(SampleInvoker.class)).satisfies(typeHint ->
- assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS));
+ assertThat(RuntimeHintsPredicates.reflection().onType(SampleInvoker.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(runtimeHints);
assertThat(runtimeHints.proxies().jdkProxies()).isEmpty();
}
@@ -105,11 +105,8 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
void shouldRegisterAnnotationAndProxyWithAliasFor() {
process(SampleMethodMetaAnnotatedBeanWithAlias.class);
RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
- assertThat(runtimeHints.reflection().getTypeHint(RetryInvoker.class)).satisfies(typeHint ->
- assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS));
- assertThat(runtimeHints.proxies().jdkProxies()).anySatisfy(jdkProxyHint ->
- assertThat(jdkProxyHint.getProxiedInterfaces()).containsExactly(
- TypeReference.of(RetryInvoker.class), TypeReference.of(SynthesizedAnnotation.class)));
+ assertThat(RuntimeHintsPredicates.reflection().onType(RetryInvoker.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(runtimeHints);
+ assertThat(RuntimeHintsPredicates.proxies().forInterfaces(RetryInvoker.class, SynthesizedAnnotation.class)).accepts(runtimeHints);
}
@Nullable
@@ -196,7 +193,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
}
- @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective
@@ -206,7 +203,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
}
- @Target({ ElementType.METHOD })
+ @Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SampleInvoker
diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ProxyHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/ProxyHintsPredicates.java
new file mode 100644
index 00000000000..f4d83126c28
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/aot/hint/ProxyHintsPredicates.java
@@ -0,0 +1,61 @@
+/*
+ * 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.Arrays;
+import java.util.function.Predicate;
+
+import org.springframework.util.Assert;
+
+/**
+ * Generator of {@link ProxyHints} predicates, testing whether the given hints
+ * match the expected behavior for proxies.
+ * @author Brian Clozel
+ * @since 6.0
+ */
+public class ProxyHintsPredicates {
+
+ ProxyHintsPredicates() {
+ }
+
+ /**
+ * Return a predicate that checks whether a {@link org.springframework.aot.hint.JdkProxyHint}
+ * is registered for the given interfaces.
+ *
Note that the order in which interfaces are given matters.
+ * @param interfaces the proxied interfaces
+ * @return the {@link RuntimeHints} predicate
+ * @see java.lang.reflect.Proxy
+ */
+ public Predicate forInterfaces(Class>... interfaces) {
+ Assert.notEmpty(interfaces, "'interfaces' should not be empty");
+ return forInterfaces(Arrays.stream(interfaces).map(TypeReference::of).toArray(TypeReference[]::new));
+ }
+
+ /**
+ * Return a predicate that checks whether a {@link org.springframework.aot.hint.JdkProxyHint}
+ * is registered for the given interfaces.
+ * Note that the order in which interfaces are given matters.
+ * @param interfaces the proxied interfaces as type references
+ * @return the {@link RuntimeHints} predicate
+ * @see java.lang.reflect.Proxy
+ */
+ public Predicate forInterfaces(TypeReference... interfaces) {
+ Assert.notEmpty(interfaces, "'interfaces' should not be empty");
+ return hints -> hints.proxies().jdkProxies().anyMatch(proxyHint ->
+ proxyHint.getProxiedInterfaces().equals(Arrays.asList(interfaces)));
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHintsPredicates.java
new file mode 100644
index 00000000000..1f88ea15029
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHintsPredicates.java
@@ -0,0 +1,380 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.springframework.core.MethodIntrospector;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Generator of {@link ReflectionHints} predicates, testing whether the given hints
+ * match the expected behavior for reflection.
+ * @author Brian Clozel
+ * @since 6.0
+ */
+public class ReflectionHintsPredicates {
+
+ ReflectionHintsPredicates() {
+
+ }
+
+ /**
+ * Return a predicate that checks whether a reflection hint is registered for the given type.
+ * The returned type exposes additional methods that refine the predicate behavior.
+ * @param typeReference the type
+ * @return the {@link RuntimeHints} predicate
+ */
+ public TypeHintPredicate onType(TypeReference typeReference) {
+ Assert.notNull(typeReference, "'typeReference' should not be null");
+ return new TypeHintPredicate(typeReference);
+ }
+
+ /**
+ * Return a predicate that checks whether a reflection hint is registered for the given type.
+ *
The returned type exposes additional methods that refine the predicate behavior.
+ * @param type the type
+ * @return the {@link RuntimeHints} predicate
+ */
+ public TypeHintPredicate onType(Class> type) {
+ Assert.notNull(type, "'type' should not be null");
+ return new TypeHintPredicate(TypeReference.of(type));
+ }
+
+ /**
+ * Return a predicate that checks whether a reflection hint is registered for the given constructor.
+ * By default, both introspection and invocation hints match.
+ *
The returned type exposes additional methods that refine the predicate behavior.
+ * @param constructor the constructor
+ * @return the {@link RuntimeHints} predicate
+ */
+ public ConstructorHintPredicate onConstructor(Constructor> constructor) {
+ Assert.notNull(constructor, "'constructor' should not be null");
+ return new ConstructorHintPredicate(constructor);
+ }
+
+ /**
+ * Return a predicate that checks whether a reflection hint is registered for the given method.
+ * By default, both introspection and invocation hints match.
+ *
The returned type exposes additional methods that refine the predicate behavior.
+ * @param method the method
+ * @return the {@link RuntimeHints} predicate
+ */
+ public MethodHintPredicate onMethod(Method method) {
+ Assert.notNull(method, "'method' should not be null");
+ return new MethodHintPredicate(method);
+ }
+
+ /**
+ * Return a predicate that checks whether a reflection hint is registered for the method that matches the given selector.
+ * This looks up a method on the given type with the expected name, if unique.
+ * By default, both introspection and invocation hints match.
+ *
The returned type exposes additional methods that refine the predicate behavior.
+ * @param type the type holding the method
+ * @param methodName the method name
+ * @return the {@link RuntimeHints} predicate
+ * @throws IllegalArgumentException if the method cannot be found or if multiple methods are found with the same name.
+ */
+ public MethodHintPredicate onMethod(Class> type, String methodName) {
+ Assert.notNull(type, "'type' should not be null");
+ Assert.hasText(methodName, "'methodName' should not be null");
+ return new MethodHintPredicate(getMethod(type, methodName));
+ }
+
+ private Method getMethod(Class> type, String methodName) {
+ ReflectionUtils.MethodFilter selector = method -> methodName.equals(method.getName());
+ Set methods = MethodIntrospector.selectMethods(type, selector);
+ if (methods.size() == 1) {
+ return methods.iterator().next();
+ }
+ else if (methods.size() > 1) {
+ throw new IllegalArgumentException(String.format("Found multiple methods named '%s' on class %s", methodName, type.getName()));
+ }
+ else {
+ throw new IllegalArgumentException("No method named '" + methodName + "' on class " + type.getName());
+ }
+ }
+
+ /**
+ * Return a predicate that checks whether a reflection hint is registered for the field that matches the given selector.
+ * This looks up a field on the given type with the expected name, if present.
+ * By default, unsafe or write access are not considered.
+ * The returned type exposes additional methods that refine the predicate behavior.
+ * @param type the type holding the field
+ * @param fieldName the field name
+ * @return the {@link RuntimeHints} predicate
+ * @throws IllegalArgumentException if a field cannot be found with the given name.
+ */
+ public FieldHintPredicate onField(Class> type, String fieldName) {
+ Assert.notNull(type, "'type' should not be null");
+ Assert.hasText(fieldName, "'fieldName' should not be empty");
+ Field field = ReflectionUtils.findField(type, fieldName);
+ if (field == null) {
+ throw new IllegalArgumentException("No field named '" + fieldName + "' on class " + type.getName());
+ }
+ return new FieldHintPredicate(field);
+ }
+
+ /**
+ * Return a predicate that checks whether a reflection hint is registered for the given field.
+ * By default, unsafe or write access are not considered.
+ *
The returned type exposes additional methods that refine the predicate behavior.
+ * @param field the field
+ * @return the {@link RuntimeHints} predicate
+ */
+ public FieldHintPredicate onField(Field field) {
+ Assert.notNull(field, "'field' should not be null");
+ return new FieldHintPredicate(field);
+ }
+
+ public static class TypeHintPredicate implements Predicate {
+
+ private final TypeReference type;
+
+ TypeHintPredicate(TypeReference type) {
+ this.type = type;
+ }
+
+ @Nullable
+ private TypeHint getTypeHint(RuntimeHints hints) {
+ return hints.reflection().getTypeHint(this.type);
+ }
+
+ @Override
+ public boolean test(RuntimeHints hints) {
+ return getTypeHint(hints) != null;
+ }
+
+
+ /**
+ * Refine the current predicate to only match if the given {@link MemberCategory} is present.
+ * @param memberCategory the member category
+ * @return the refined {@link RuntimeHints} predicate
+ */
+ public Predicate withMemberCategory(MemberCategory memberCategory) {
+ Assert.notNull(memberCategory, "'memberCategory' should not be null");
+ return this.and(hints -> getTypeHint(hints).getMemberCategories().contains(memberCategory));
+ }
+
+ /**
+ * Refine the current predicate to match if any of the given {@link MemberCategory categories} is present.
+ * @param memberCategories the member categories
+ * @return the refined {@link RuntimeHints} predicate
+ */
+ public Predicate withAnyMemberCategory(MemberCategory... memberCategories) {
+ Assert.notEmpty(memberCategories, "'memberCategories' should not be empty");
+ return this.and(hints -> Arrays.stream(memberCategories)
+ .anyMatch(memberCategory -> getTypeHint(hints).getMemberCategories().contains(memberCategory)));
+ }
+
+ }
+
+ public abstract static class ExecutableHintPredicate implements Predicate {
+
+ protected final T executable;
+
+ protected ExecutableMode executableMode = ExecutableMode.INTROSPECT;
+
+ ExecutableHintPredicate(T executable) {
+ this.executable = executable;
+ }
+
+ /**
+ * Refine the current predicate to match for reflection introspection on the current type.
+ * @return the refined {@link RuntimeHints} predicate
+ */
+ public ExecutableHintPredicate introspect() {
+ this.executableMode = ExecutableMode.INTROSPECT;
+ return this;
+ }
+
+ /**
+ * Refine the current predicate to match for reflection invocation on the current type.
+ * @return the refined {@link RuntimeHints} predicate
+ */
+ public ExecutableHintPredicate invoke() {
+ this.executableMode = ExecutableMode.INVOKE;
+ return this;
+ }
+
+ @Override
+ public boolean test(RuntimeHints runtimeHints) {
+ return (new TypeHintPredicate(TypeReference.of(this.executable.getDeclaringClass()))
+ .withAnyMemberCategory(getPublicMemberCategories())
+ .and(hints -> Modifier.isPublic(this.executable.getModifiers())))
+ .or(new TypeHintPredicate(TypeReference.of(this.executable.getDeclaringClass())).withAnyMemberCategory(getDeclaredMemberCategories()))
+ .or(exactMatch()).test(runtimeHints);
+ }
+
+ abstract MemberCategory[] getPublicMemberCategories();
+
+ abstract MemberCategory[] getDeclaredMemberCategories();
+
+ abstract Predicate exactMatch();
+
+ /**
+ * Indicate whether the first {@code ExecutableHint} covers the reflection needs for the other one.
+ * For that, both hints must apply to the same member (same type, name and parameters)
+ * and the configured {@code ExecutableMode} of the first must cover the second.
+ */
+ static boolean includes(ExecutableHint hint, ExecutableHint other) {
+ return hint.getName().equals(other.getName())
+ && hint.getParameterTypes().equals(other.getParameterTypes())
+ && (hint.getModes().contains(ExecutableMode.INVOKE)
+ || !other.getModes().contains(ExecutableMode.INVOKE));
+ }
+ }
+
+ public static class ConstructorHintPredicate extends ExecutableHintPredicate> {
+
+ ConstructorHintPredicate(Constructor> constructor) {
+ super(constructor);
+ }
+
+ MemberCategory[] getPublicMemberCategories() {
+ if (this.executableMode == ExecutableMode.INTROSPECT) {
+ return new MemberCategory[] {MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS,
+ MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS};
+ }
+ return new MemberCategory[] {MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS};
+ }
+
+ MemberCategory[] getDeclaredMemberCategories() {
+ if (this.executableMode == ExecutableMode.INTROSPECT) {
+ return new MemberCategory[] {MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
+ MemberCategory.INVOKE_DECLARED_CONSTRUCTORS};
+ }
+ return new MemberCategory[] {MemberCategory.INVOKE_DECLARED_CONSTRUCTORS};
+ }
+
+ @Override
+ Predicate exactMatch() {
+ return hints -> hints.reflection().getTypeHint(this.executable.getDeclaringClass()).constructors().anyMatch(executableHint -> {
+ List parameters = Arrays.stream(this.executable.getParameterTypes()).map(TypeReference::of).toList();
+ ExecutableHint syntheticHint = ExecutableHint.ofConstructor(parameters)
+ .setModes(this.executableMode).build();
+ return includes(executableHint, syntheticHint);
+ });
+ }
+ }
+
+ public static class MethodHintPredicate extends ExecutableHintPredicate {
+
+
+ MethodHintPredicate(Method method) {
+ super(method);
+ }
+
+ MemberCategory[] getPublicMemberCategories() {
+ if (this.executableMode == ExecutableMode.INTROSPECT) {
+ return new MemberCategory[] {MemberCategory.INTROSPECT_PUBLIC_METHODS,
+ MemberCategory.INVOKE_PUBLIC_METHODS};
+ }
+ return new MemberCategory[] {MemberCategory.INVOKE_PUBLIC_METHODS};
+ }
+
+ MemberCategory[] getDeclaredMemberCategories() {
+
+ if (this.executableMode == ExecutableMode.INTROSPECT) {
+ return new MemberCategory[] {MemberCategory.INTROSPECT_DECLARED_METHODS,
+ MemberCategory.INVOKE_DECLARED_METHODS};
+ }
+ return new MemberCategory[] {MemberCategory.INVOKE_DECLARED_METHODS};
+ }
+
+ @Override
+ Predicate exactMatch() {
+ return hints -> (hints.reflection().getTypeHint(this.executable.getDeclaringClass()) != null) &&
+ hints.reflection().getTypeHint(this.executable.getDeclaringClass()).methods().anyMatch(executableHint -> {
+ List parameters = Arrays.stream(this.executable.getParameterTypes()).map(TypeReference::of).toList();
+ ExecutableHint syntheticHint = ExecutableHint.ofMethod(this.executable.getName(), parameters)
+ .setModes(this.executableMode).build();
+ return includes(executableHint, syntheticHint);
+ });
+ }
+ }
+
+ public static class FieldHintPredicate implements Predicate {
+
+ private final Field field;
+
+ private boolean allowWrite;
+
+ private boolean allowUnsafeAccess;
+
+ FieldHintPredicate(Field field) {
+ this.field = field;
+ }
+
+ /**
+ * Refine the current predicate to match if write access is allowed on the field.
+ * @return the refined {@link RuntimeHints} predicate
+ * @see FieldHint#isAllowWrite()
+ */
+ public FieldHintPredicate allowWrite() {
+ this.allowWrite = true;
+ return this;
+ }
+
+ /**
+ * Refine the current predicate to match if unsafe access is allowed on the field.
+ * @return the refined {@link RuntimeHints} predicate
+ * @see FieldHint#isAllowUnsafeAccess() ()
+ */
+ public FieldHintPredicate allowUnsafeAccess() {
+ this.allowUnsafeAccess = true;
+ return this;
+ }
+
+ @Override
+ public boolean test(RuntimeHints runtimeHints) {
+ TypeHint typeHint = runtimeHints.reflection().getTypeHint(this.field.getDeclaringClass());
+ if (typeHint == null) {
+ return false;
+ }
+ return memberCategoryMatch(typeHint) || exactMatch(typeHint);
+ }
+
+ private boolean memberCategoryMatch(TypeHint typeHint) {
+ if (Modifier.isPublic(this.field.getModifiers())) {
+ return typeHint.getMemberCategories().contains(MemberCategory.PUBLIC_FIELDS)
+ || typeHint.getMemberCategories().contains(MemberCategory.DECLARED_FIELDS);
+ }
+ else {
+ return typeHint.getMemberCategories().contains(MemberCategory.DECLARED_FIELDS);
+ }
+ }
+
+ private boolean exactMatch(TypeHint typeHint) {
+ return typeHint.fields().anyMatch(fieldHint ->
+ this.field.getName().equals(fieldHint.getName())
+ && (!this.allowWrite || this.allowWrite == fieldHint.isAllowWrite())
+ && (!this.allowUnsafeAccess || this.allowUnsafeAccess == fieldHint.isAllowUnsafeAccess()));
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ResourceHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/ResourceHintsPredicates.java
new file mode 100644
index 00000000000..6de4d64979f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourceHintsPredicates.java
@@ -0,0 +1,92 @@
+/*
+ * 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.function.Predicate;
+import java.util.regex.Pattern;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ConcurrentLruCache;
+
+/**
+ * Generator of {@link ResourceHints} predicates, testing whether the given hints
+ * match the expected behavior for resources.
+ * @author Brian Clozel
+ * @since 6.0
+ */
+public class ResourceHintsPredicates {
+
+ private static final ConcurrentLruCache CACHED_RESOURCE_PATTERNS = new ConcurrentLruCache<>(32, Pattern::compile);
+
+ ResourceHintsPredicates() {
+ }
+
+ /**
+ * Return a predicate that checks whether a resource hint is registered for the given bundle name.
+ * @param bundleName the resource bundle name
+ * @return the {@link RuntimeHints} predicate
+ */
+ public Predicate forBundle(String bundleName) {
+ Assert.hasText(bundleName, "resource bundle name should not be empty");
+ return runtimeHints -> runtimeHints.resources().resourceBundles()
+ .anyMatch(bundleHint -> bundleName.equals(bundleHint.getBaseName()));
+ }
+
+ /**
+ * Return a predicate that checks whether a resource hint is registered for the given
+ * resource name, located in the given type's package.
+ * For example, {@code forResource(org.example.MyClass, "myResource.txt")}
+ * will match for {@code "/org/example/myResource.txt"}.
+ * @param type the type's package where to look for the resource
+ * @param resourceName the resource name
+ * @return the {@link RuntimeHints} predicate
+ */
+ public Predicate forResource(TypeReference type, String resourceName) {
+ String absoluteName = resolveAbsoluteResourceName(type, resourceName);
+ return forResource(absoluteName);
+ }
+
+ private String resolveAbsoluteResourceName(TypeReference type, String resourceName) {
+ if (resourceName.startsWith("/")) {
+ return resourceName;
+ }
+ else {
+ return "/" + type.getPackageName().replace('.', '/')
+ + "/" + resourceName;
+ }
+ }
+
+ /**
+ * Return a predicate that checks whether a resource hint is registered for
+ * the given resource name.
+ * @param resourceName the full resource name
+ * @return the {@link RuntimeHints} predicate
+ */
+ public Predicate forResource(String resourceName) {
+ return hints -> hints.resources().resourcePatterns().reduce(ResourcePatternHints::merge)
+ .map(hint -> {
+ boolean isExcluded = hint.getExcludes().stream()
+ .anyMatch(excluded -> CACHED_RESOURCE_PATTERNS.get(excluded.getPattern()).matcher(resourceName).matches());
+ if (isExcluded) {
+ return false;
+ }
+ return hint.getIncludes().stream()
+ .anyMatch(included -> CACHED_RESOURCE_PATTERNS.get(included.getPattern()).matcher(resourceName).matches());
+ }).orElse(false);
+ }
+
+}
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
index 280e1d8feb6..e3568c14f34 100644
--- a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java
+++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java
@@ -22,6 +22,8 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
+import org.springframework.lang.Nullable;
+
/**
* A collection of {@link ResourcePatternHint} describing whether
* resources should be made available at runtime through a matching
@@ -43,6 +45,11 @@ public final class ResourcePatternHints {
this.excludes = new ArrayList<>(builder.excludes);
}
+ private ResourcePatternHints(List includes, List excludes) {
+ this.includes = includes;
+ this.excludes = excludes;
+ }
+
/**
* Return the include patterns to use to identify the resources to match.
* @return the include patterns
@@ -59,6 +66,16 @@ public final class ResourcePatternHints {
return this.excludes;
}
+ ResourcePatternHints merge(ResourcePatternHints resourcePatternHints) {
+ List includes = new ArrayList<>();
+ includes.addAll(this.includes);
+ includes.addAll(resourcePatternHints.includes);
+ List excludes = new ArrayList<>();
+ excludes.addAll(this.excludes);
+ excludes.addAll(resourcePatternHints.excludes);
+ return new ResourcePatternHints(includes, excludes);
+ }
+
/**
* Builder for {@link ResourcePatternHints}.
@@ -75,7 +92,7 @@ public final class ResourcePatternHints {
* @param includes the include patterns (see {@link ResourcePatternHint} documentation)
* @return {@code this}, to facilitate method chaining
*/
- public Builder includes(TypeReference reachableType, String... includes) {
+ public Builder includes(@Nullable TypeReference reachableType, String... includes) {
List newIncludes = Arrays.stream(includes)
.map(include -> new ResourcePatternHint(include, reachableType)).toList();
this.includes.addAll(newIncludes);
diff --git a/spring-core/src/main/java/org/springframework/aot/hint/RuntimeHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/RuntimeHintsPredicates.java
new file mode 100644
index 00000000000..0c7635e823f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/aot/hint/RuntimeHintsPredicates.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+/**
+ * Static generator of predicates that test whether the given {@link RuntimeHints}
+ * instance matches the expected behavior for reflection, resource or proxy generation.
+ * This utility class can be used by {@link RuntimeHintsRegistrar} to conditionally
+ * register hints depending on what's present already. This can also be used as a
+ * testing utility for checking proper registration of hints:
+ *
+ * Predicate<RuntimeHints> predicate = RuntimeHintsPredicates.reflection().onMethod(MyClass.class, "someMethod").invoke();
+ * assertThat(predicate).accepts(runtimeHints);
+ *
+ * @author Brian Clozel
+ * @since 6.0
+ */
+public abstract class RuntimeHintsPredicates {
+
+ private static final ReflectionHintsPredicates reflection = new ReflectionHintsPredicates();
+
+ private static final ResourceHintsPredicates resource = new ResourceHintsPredicates();
+
+ private static final ProxyHintsPredicates proxies = new ProxyHintsPredicates();
+
+
+ private RuntimeHintsPredicates() {
+
+ }
+
+ /**
+ * Return a predicate generator for {@link ReflectionHints reflection hints}.
+ * @return the predicate generator
+ */
+ public static ReflectionHintsPredicates reflection() {
+ return reflection;
+ }
+
+ /**
+ * Return a predicate generator for {@link ResourceHints resource hints}.
+ * @return the predicate generator
+ */
+ public static ResourceHintsPredicates resource() {
+ return resource;
+ }
+
+ /**
+ * Return a predicate generator for {@link ProxyHints proxy hints}.
+ * @return the predicate generator
+ */
+ public static ProxyHintsPredicates proxies() {
+ return proxies;
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ProxyHintsPredicatesTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ProxyHintsPredicatesTests.java
new file mode 100644
index 00000000000..d641e3fe205
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/aot/hint/ProxyHintsPredicatesTests.java
@@ -0,0 +1,76 @@
+/*
+ * 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.function.Predicate;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for {@link ProxyHintsPredicates}.
+ *
+ * @author Brian Clozel
+ */
+class ProxyHintsPredicatesTests {
+
+ private final ProxyHintsPredicates proxy = new ProxyHintsPredicates();
+
+ private RuntimeHints runtimeHints;
+
+ @BeforeEach
+ void setup() {
+ this.runtimeHints = new RuntimeHints();
+ }
+
+ @Test
+ void shouldFailForEmptyInterfacesArray() {
+ assertThatThrownBy(() -> this.proxy.forInterfaces(new Class>[] {})).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void proxyForInterfacesMatchesProxyHint() {
+ this.runtimeHints.proxies().registerJdkProxy(FirstTestInterface.class, SecondTestInterface.class);
+ assertPredicateMatches(this.proxy.forInterfaces(FirstTestInterface.class, SecondTestInterface.class));
+ }
+
+ @Test
+ void proxyForInterfacesDoesNotMatchProxyHintDifferentOrder() {
+ this.runtimeHints.proxies().registerJdkProxy(SecondTestInterface.class, FirstTestInterface.class);
+ assertPredicateDoesNotMatch(this.proxy.forInterfaces(FirstTestInterface.class, SecondTestInterface.class));
+ }
+
+ interface FirstTestInterface {
+
+ }
+
+ interface SecondTestInterface {
+
+ }
+
+ private void assertPredicateMatches(Predicate predicate) {
+ assertThat(predicate.test(this.runtimeHints)).isTrue();
+ }
+
+ private void assertPredicateDoesNotMatch(Predicate predicate) {
+ assertThat(predicate.test(this.runtimeHints)).isFalse();
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsPredicatesTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsPredicatesTests.java
new file mode 100644
index 00000000000..95504552208
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsPredicatesTests.java
@@ -0,0 +1,509 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for {@link ReflectionHintsPredicates}
+ *
+ * @author Brian Clozel
+ */
+class ReflectionHintsPredicatesTests {
+
+ private static Constructor> privateConstructor;
+
+ private static Constructor> publicConstructor;
+
+ private final ReflectionHintsPredicates reflection = new ReflectionHintsPredicates();
+
+ private RuntimeHints runtimeHints;
+
+
+ @BeforeAll
+ static void setupAll() throws Exception {
+ privateConstructor = SampleClass.class.getDeclaredConstructor(String.class);
+ publicConstructor = SampleClass.class.getConstructor();
+ }
+
+ @BeforeEach
+ void setup() {
+ this.runtimeHints = new RuntimeHints();
+ }
+
+ // Reflection on type
+
+ @Test
+ void shouldFailForNullType() {
+ assertThatThrownBy(() -> reflection.onType((TypeReference) null)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void reflectionOnClassShouldMatchIntrospection() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, builder -> {
+ });
+ assertPredicateMatches(reflection.onType(SampleClass.class));
+ }
+
+ @Test
+ void reflectionOnTypeReferenceShouldMatchIntrospection() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, builder -> {
+ });
+ assertPredicateMatches(reflection.onType(TypeReference.of(SampleClass.class)));
+ }
+
+ @Test
+ void reflectionOnDifferentClassShouldNotMatchIntrospection() {
+ this.runtimeHints.reflection().registerType(Integer.class, builder -> {
+ });
+ assertPredicateDoesNotMatch(reflection.onType(TypeReference.of(SampleClass.class)));
+ }
+
+ @Test
+ void typeWithMemberCategoryFailsWithNullCategory() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, builder -> builder.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
+ assertThatThrownBy(() -> reflection.onType(SampleClass.class).withMemberCategory(null)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void typeWithMemberCategoryMatchesCategory() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, builder -> builder.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
+ assertPredicateMatches(reflection.onType(SampleClass.class).withMemberCategory(MemberCategory.INTROSPECT_PUBLIC_METHODS));
+ }
+
+ @Test
+ void typeWithMemberCategoryDoesNotMatchOtherCategory() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, builder -> builder.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
+ assertPredicateDoesNotMatch(reflection.onType(SampleClass.class).withMemberCategory(MemberCategory.INVOKE_PUBLIC_METHODS));
+ }
+
+ @Test
+ void typeWithAnyMemberCategoryFailsWithNullCategories() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, builder -> builder.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
+ assertThatThrownBy(() -> reflection.onType(SampleClass.class).withAnyMemberCategory(new MemberCategory[]{})).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void typeWithAnyMemberCategoryMatchesCategory() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, builder -> builder.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS));
+ assertPredicateMatches(reflection.onType(SampleClass.class).withAnyMemberCategory(MemberCategory.INTROSPECT_PUBLIC_METHODS));
+ }
+
+ @Test
+ void typeWithAnyMemberCategoryDoesNotMatchOtherCategory() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, builder -> builder.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS));
+ assertPredicateDoesNotMatch(reflection.onType(SampleClass.class).withAnyMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS));
+ }
+
+ // Reflection on constructor
+
+ @Test
+ void constructorIntrospectionMatchesConstructorHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withConstructor(Collections.emptyList(), constructorHint -> {
+ }));
+ assertPredicateMatches(reflection.onConstructor(publicConstructor).introspect());
+ }
+
+ @Test
+ void constructorIntrospectionMatchesIntrospectPublicConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS));
+ assertPredicateMatches(reflection.onConstructor(publicConstructor).introspect());
+ }
+
+ @Test
+ void constructorIntrospectionMatchesInvokePublicConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
+ assertPredicateMatches(reflection.onConstructor(publicConstructor).introspect());
+ }
+
+ @Test
+ void constructorIntrospectionMatchesIntrospectDeclaredConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS));
+ assertPredicateMatches(reflection.onConstructor(publicConstructor).introspect());
+ }
+
+ @Test
+ void constructorIntrospectionMatchesInvokeDeclaredConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
+ assertPredicateMatches(reflection.onConstructor(publicConstructor).introspect());
+ }
+
+ @Test
+ void constructorInvocationDoesNotMatchConstructorHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withConstructor(Collections.emptyList(), constructorHint -> {
+ }));
+ assertPredicateDoesNotMatch(reflection.onConstructor(publicConstructor).invoke());
+ }
+
+ @Test
+ void constructorInvocationMatchesConstructorInvocationHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withConstructor(Collections.emptyList(), constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)));
+ assertPredicateMatches(reflection.onConstructor(publicConstructor).invoke());
+ }
+
+ @Test
+ void constructorInvocationDoesNotMatchIntrospectPublicConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS));
+ assertPredicateDoesNotMatch(reflection.onConstructor(publicConstructor).invoke());
+ }
+
+ @Test
+ void constructorInvocationMatchesInvokePublicConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
+ assertPredicateMatches(reflection.onConstructor(publicConstructor).invoke());
+ }
+
+ @Test
+ void constructorInvocationDoesNotMatchIntrospectDeclaredConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS));
+ assertPredicateDoesNotMatch(reflection.onConstructor(publicConstructor).invoke());
+ }
+
+ @Test
+ void constructorInvocationMatchesInvokeDeclaredConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
+ assertPredicateMatches(reflection.onConstructor(publicConstructor).invoke());
+ }
+
+ @Test
+ void privateConstructorIntrospectionMatchesConstructorHint() {
+ List parameterTypes = Collections.singletonList(TypeReference.of(String.class));
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withConstructor(parameterTypes, constructorHint -> {
+ }));
+ assertPredicateMatches(reflection.onConstructor(privateConstructor).introspect());
+ }
+
+ @Test
+ void privateConstructorIntrospectionDoesNotMatchIntrospectPublicConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS));
+ assertPredicateDoesNotMatch(reflection.onConstructor(privateConstructor).introspect());
+ }
+
+ @Test
+ void privateConstructorIntrospectionDoesNotMatchInvokePublicConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
+ assertPredicateDoesNotMatch(reflection.onConstructor(privateConstructor).introspect());
+ }
+
+ @Test
+ void privateConstructorIntrospectionMatchesIntrospectDeclaredConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS));
+ assertPredicateMatches(reflection.onConstructor(privateConstructor).introspect());
+ }
+
+ @Test
+ void privateConstructorIntrospectionMatchesInvokeDeclaredConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
+ assertPredicateMatches(reflection.onConstructor(privateConstructor).introspect());
+ }
+
+ @Test
+ void privateConstructorInvocationDoesNotMatchConstructorHint() {
+ List parameterTypes = Collections.singletonList(TypeReference.of(String.class));
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withConstructor(parameterTypes, constructorHint -> {
+ }));
+ assertPredicateDoesNotMatch(reflection.onConstructor(privateConstructor).invoke());
+ }
+
+ @Test
+ void privateConstructorInvocationMatchesConstructorInvocationHint() {
+ List parameterTypes = Collections.singletonList(TypeReference.of(String.class));
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withConstructor(parameterTypes, constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)));
+ assertPredicateMatches(reflection.onConstructor(privateConstructor).invoke());
+ }
+
+ @Test
+ void privateConstructorInvocationDoesNotMatchIntrospectPublicConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS));
+ assertPredicateDoesNotMatch(reflection.onConstructor(privateConstructor).invoke());
+ }
+
+ @Test
+ void privateConstructorInvocationDoesNotMatchInvokePublicConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
+ assertPredicateDoesNotMatch(reflection.onConstructor(privateConstructor).invoke());
+ }
+
+ @Test
+ void privateConstructorInvocationDoesNotMatchIntrospectDeclaredConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS));
+ assertPredicateDoesNotMatch(reflection.onConstructor(privateConstructor).invoke());
+ }
+
+ @Test
+ void privateConstructorInvocationMatchesInvokeDeclaredConstructors() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
+ assertPredicateMatches(reflection.onConstructor(privateConstructor).invoke());
+ }
+
+ // Reflection on method
+
+ @Test
+ void methodIntrospectionMatchesMethodHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("publicMethod", Collections.emptyList(), methodHint -> {
+ }));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").introspect());
+ }
+
+ @Test
+ void methodIntrospectionMatchesIntrospectPublicMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").introspect());
+ }
+
+ @Test
+ void methodIntrospectionMatchesInvokePublicMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").introspect());
+ }
+
+ @Test
+ void methodIntrospectionMatchesIntrospectDeclaredMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").introspect());
+ }
+
+ @Test
+ void methodIntrospectionMatchesInvokeDeclaredMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").introspect());
+ }
+
+ @Test
+ void methodInvocationDoesNotMatchMethodHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("publicMethod", Collections.emptyList(), methodHint -> {
+ }));
+ assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "publicMethod").invoke());
+ }
+
+ @Test
+ void methodInvocationMatchesMethodInvocationHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("publicMethod", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INVOKE)));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").invoke());
+ }
+
+ @Test
+ void methodInvocationDoesNotMatchIntrospectPublicMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
+ assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "publicMethod").invoke());
+ }
+
+ @Test
+ void methodInvocationMatchesInvokePublicMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").invoke());
+ }
+
+ @Test
+ void methodInvocationDoesNotMatchIntrospectDeclaredMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS));
+ assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "publicMethod").invoke());
+ }
+
+ @Test
+ void methodInvocationMatchesInvokeDeclaredMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").invoke());
+ }
+
+ @Test
+ void privateMethodIntrospectionMatchesMethodHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("privateMethod", Collections.emptyList(), methodHint -> {
+ }));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").introspect());
+ }
+
+ @Test
+ void privateMethodIntrospectionDoesNotMatchIntrospectPublicMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
+ assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "privateMethod").introspect());
+ }
+
+ @Test
+ void privateMethodIntrospectionDoesNotMatchInvokePublicMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
+ assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "privateMethod").introspect());
+ }
+
+ @Test
+ void privateMethodIntrospectionMatchesIntrospectDeclaredMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").introspect());
+ }
+
+ @Test
+ void privateMethodIntrospectionMatchesInvokeDeclaredMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").introspect());
+ }
+
+ @Test
+ void privateMethodInvocationDoesNotMatchMethodHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("privateMethod", Collections.emptyList(), methodHint -> {
+ }));
+ assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "privateMethod").invoke());
+ }
+
+ @Test
+ void privateMethodInvocationMatchesMethodInvocationHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("privateMethod", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INVOKE)));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").invoke());
+ }
+
+ @Test
+ void privateMethodInvocationDoesNotMatchIntrospectPublicMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
+ assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "privateMethod").invoke());
+ }
+
+ @Test
+ void privateMethodInvocationDoesNotMatchInvokePublicMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
+ assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "privateMethod").invoke());
+ }
+
+ @Test
+ void privateMethodInvocationDoesNotMatchIntrospectDeclaredMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS));
+ assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "privateMethod").invoke());
+ }
+
+ @Test
+ void privateMethodInvocationMatchesInvokeDeclaredMethods() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
+ assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").invoke());
+ }
+
+ // Reflection on field
+
+ @Test
+ void shouldFailForMissingField() {
+ assertThatThrownBy(() -> reflection.onField(SampleClass.class, "missingField")).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void fieldReflectionMatchesFieldHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("publicField", fieldHint -> {
+ }));
+ assertPredicateMatches(reflection.onField(SampleClass.class, "publicField"));
+ }
+
+ @Test
+ void fieldWriteReflectionDoesNotMatchFieldHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("publicField", fieldHint -> {
+ }));
+ assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "publicField").allowWrite());
+ }
+
+ @Test
+ void fieldUnsafeReflectionDoesNotMatchFieldHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("publicField", fieldHint -> {
+ }));
+ assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "publicField").allowUnsafeAccess());
+ }
+
+ @Test
+ void fieldWriteReflectionMatchesFieldHintWithWrite() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint ->
+ typeHint.withField("publicField", fieldHint -> fieldHint.allowWrite(true)));
+ assertPredicateMatches(reflection.onField(SampleClass.class, "publicField").allowWrite());
+ }
+
+ @Test
+ void fieldUnsafeReflectionMatchesFieldHintWithUnsafe() {
+ this.runtimeHints.reflection().registerType(SampleClass.class,
+ typeHint -> typeHint.withField("publicField", fieldHint -> fieldHint.allowUnsafeAccess(true)));
+ assertPredicateMatches(reflection.onField(SampleClass.class, "publicField").allowUnsafeAccess());
+ }
+
+ @Test
+ void fieldReflectionMatchesPublicFieldsHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.PUBLIC_FIELDS));
+ assertPredicateMatches(reflection.onField(SampleClass.class, "publicField"));
+ }
+
+ @Test
+ void fieldReflectionMatchesDeclaredFieldsHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.DECLARED_FIELDS));
+ assertPredicateMatches(reflection.onField(SampleClass.class, "publicField"));
+ }
+
+ @Test
+ void privateFieldReflectionMatchesFieldHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("privateField", fieldHint -> {
+ }));
+ assertPredicateMatches(reflection.onField(SampleClass.class, "privateField"));
+ }
+
+ @Test
+ void privateFieldReflectionDoesNotMatchPublicFieldsHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.PUBLIC_FIELDS));
+ assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "privateField"));
+ }
+
+ @Test
+ void privateFieldReflectionMatchesDeclaredFieldsHint() {
+ this.runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMembers(MemberCategory.DECLARED_FIELDS));
+ assertPredicateMatches(reflection.onField(SampleClass.class, "privateField"));
+ }
+
+
+ private void assertPredicateMatches(Predicate predicate) {
+ assertThat(predicate).accepts(this.runtimeHints);
+ }
+
+ private void assertPredicateDoesNotMatch(Predicate predicate) {
+ assertThat(predicate).rejects(this.runtimeHints);
+ }
+
+
+ static class SampleClass {
+
+ private String privateField;
+
+ public String publicField;
+
+ public SampleClass() {
+
+ }
+
+ private SampleClass(String message) {
+
+ }
+
+ public void publicMethod() {
+
+ }
+
+ private void privateMethod() {
+
+ }
+
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsPredicatesTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsPredicatesTests.java
new file mode 100644
index 00000000000..8b38a0fc6d3
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsPredicatesTests.java
@@ -0,0 +1,88 @@
+/*
+ * 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.function.Predicate;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link ReflectionHintsPredicates}.
+ *
+ * @author Brian Clozel
+ */
+class ResourceHintsPredicatesTests {
+
+ private final ResourceHintsPredicates resources = new ResourceHintsPredicates();
+
+ private RuntimeHints runtimeHints;
+
+
+ @BeforeEach
+ void setup() {
+ this.runtimeHints = new RuntimeHints();
+ }
+
+ @Test
+ void resourcePatternMatchesResourceName() {
+ this.runtimeHints.resources().registerPattern("/test/spring.*");
+ assertPredicateMatches(resources.forResource("/test/spring.properties"));
+ }
+
+ @Test
+ void resourcePatternDoesNotMatchResourceName() {
+ this.runtimeHints.resources().registerPattern("/test/spring.*");
+ assertPredicateDoesNotMatch(resources.forResource("/test/other.properties"));
+ }
+
+ @Test
+ void resourcePatternMatchesTypeAndResourceName() {
+ this.runtimeHints.resources().registerPattern("/org/springframework/aot/hint/spring.*");
+ assertPredicateMatches(resources.forResource(TypeReference.of(getClass()), "spring.properties"));
+ }
+
+ @Test
+ void resourcePatternDoesNotMatchTypeAndResourceName() {
+ this.runtimeHints.resources().registerPattern("/spring.*");
+ assertPredicateDoesNotMatch(resources.forResource(TypeReference.of(getClass()), "spring.properties"));
+ }
+
+ @Test
+ void resourceBundleMatchesBundleName() {
+ this.runtimeHints.resources().registerResourceBundle("spring");
+ assertPredicateMatches(resources.forBundle("spring"));
+ }
+
+ @Test
+ void resourceBundleDoesNotMatchBundleName() {
+ this.runtimeHints.resources().registerResourceBundle("spring");
+ assertPredicateDoesNotMatch(resources.forBundle("other"));
+ }
+
+
+ private void assertPredicateMatches(Predicate predicate) {
+ assertThat(predicate.test(this.runtimeHints)).isTrue();
+ }
+
+ private void assertPredicateDoesNotMatch(Predicate predicate) {
+ assertThat(predicate.test(this.runtimeHints)).isFalse();
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHintsRegistrarTests.java
index cad643cccb4..d21f25fe8c2 100644
--- a/spring-core/src/test/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHintsRegistrarTests.java
+++ b/spring-core/src/test/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHintsRegistrarTests.java
@@ -21,8 +21,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsPredicates;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
-import org.springframework.aot.hint.TypeReference;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.support.SpringFactoriesLoader;
@@ -49,16 +49,14 @@ class CoreAnnotationsRuntimeHintsRegistrarTests {
@Test
void aliasForHasHints() {
- assertThat(this.hints.reflection().getTypeHint(TypeReference.of(AliasFor.class)))
- .satisfies(hint -> assertThat(hint.getMemberCategories())
- .containsExactly(MemberCategory.INVOKE_DECLARED_METHODS));
+ assertThat(RuntimeHintsPredicates.reflection().onType(AliasFor.class)
+ .withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints);
}
@Test
void orderAnnotationHasHints() {
- assertThat(this.hints.reflection().getTypeHint(TypeReference.of(Order.class)))
- .satisfies(hint -> assertThat(hint.getMemberCategories())
- .containsExactly(MemberCategory.INVOKE_DECLARED_METHODS));
+ assertThat(RuntimeHintsPredicates.reflection().onType(Order.class)
+ .withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints);
}
}
diff --git a/spring-core/src/test/java/org/springframework/aot/hint/support/SpringFactoriesLoaderRuntimeHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/support/SpringFactoriesLoaderRuntimeHintsRegistrarTests.java
index bf2150c3730..d6fb1c1eda4 100644
--- a/spring-core/src/test/java/org/springframework/aot/hint/support/SpringFactoriesLoaderRuntimeHintsRegistrarTests.java
+++ b/spring-core/src/test/java/org/springframework/aot/hint/support/SpringFactoriesLoaderRuntimeHintsRegistrarTests.java
@@ -20,11 +20,9 @@ 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.RuntimeHintsPredicates;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
-import org.springframework.aot.hint.TypeHint;
-import org.springframework.aot.hint.TypeReference;
import org.springframework.core.io.support.DummyFactory;
import org.springframework.core.io.support.MyDummyFactory1;
import org.springframework.core.io.support.SpringFactoriesLoader;
@@ -51,28 +49,19 @@ class SpringFactoriesLoaderRuntimeHintsRegistrarTests {
@Test
void resourceLocationHasHints() {
- assertThat(this.hints.resources().resourcePatterns())
- .anySatisfy(hint -> assertThat(hint.getIncludes()).map(ResourcePatternHint::getPattern)
- .contains(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION));
+ assertThat(RuntimeHintsPredicates.resource().forResource(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION)).accepts(this.hints);
}
@Test
void factoryTypeHasHint() {
- TypeReference type = TypeReference.of(DummyFactory.class);
- assertThat(this.hints.reflection().getTypeHint(type))
- .satisfies(this::expectedHints);
+ assertThat(RuntimeHintsPredicates.reflection().onType(DummyFactory.class)
+ .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints);
}
@Test
void factoryImplementationHasHint() {
- TypeReference type = TypeReference.of(MyDummyFactory1.class);
- assertThat(this.hints.reflection().getTypeHint(type))
- .satisfies(this::expectedHints);
- }
-
- private void expectedHints(TypeHint hint) {
- assertThat(hint.getMemberCategories())
- .containsExactly(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
+ assertThat(RuntimeHintsPredicates.reflection().onType(MyDummyFactory1.class)
+ .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints);
}
}
diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/support/InjectionCodeGeneratorTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/support/InjectionCodeGeneratorTests.java
index 9737a01b5cf..e6b0c36ab22 100644
--- a/spring-orm/src/test/java/org/springframework/orm/jpa/support/InjectionCodeGeneratorTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/jpa/support/InjectionCodeGeneratorTests.java
@@ -25,8 +25,8 @@ import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.Test;
-import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsPredicates;
import org.springframework.aot.test.generator.compile.Compiled;
import org.springframework.aot.test.generator.compile.TestCompiler;
import org.springframework.beans.testfixture.beans.TestBean;
@@ -87,11 +87,8 @@ class InjectionCodeGeneratorTests {
TestBean bean = new TestBean();
Field field = ReflectionUtils.findField(bean.getClass(), "age");
this.generator.generateInjectionCode(field, INSTANCE_VARIABLE, CodeBlock.of("$L", 123));
- assertThat(this.hints.reflection().getTypeHint(TestBean.class))
- .satisfies(hint -> assertThat(hint.fields()).anySatisfy(fieldHint -> {
- assertThat(fieldHint.getName()).isEqualTo("age");
- assertThat(fieldHint.isAllowWrite()).isTrue();
- }));
+ assertThat(RuntimeHintsPredicates.reflection().onField(TestBean.class, "age").allowWrite())
+ .accepts(this.hints);
}
@Test
@@ -127,11 +124,8 @@ class InjectionCodeGeneratorTests {
TestBeanWithPrivateMethod bean = new TestBeanWithPrivateMethod();
Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class);
this.generator.generateInjectionCode(method, INSTANCE_VARIABLE, CodeBlock.of("$L", 123));
- assertThat(this.hints.reflection().getTypeHint(TestBeanWithPrivateMethod.class))
- .satisfies(hint -> assertThat(hint.methods()).anySatisfy(methodHint -> {
- assertThat(methodHint.getName()).isEqualTo("setAge");
- assertThat(methodHint.getModes()).contains(ExecutableMode.INVOKE);
- }));
+ assertThat(RuntimeHintsPredicates.reflection()
+ .onMethod(TestBeanWithPrivateMethod.class, "setAge").invoke()).accepts(this.hints);
}
@SuppressWarnings("unchecked")