Add RuntimeHints predicates generator

The `RuntimeHints` API allows to describe hints for the reflection,
proxies and resources behavior at runtime. The need for a particular
behavior can be covered by several types of hints, at different levels.
This knowledge can be important in several cases:

* before contributing additional hints, infrastructure can check if an
  existing hint already covers the behavior
* this can be used in test suites and test infrastructure

This commit adds a new RuntimeHintsPredicates that generates `Predicate`
instances for testing `RuntimeHints` against a desired runtime behavior
for reflection, resources or proxies.

Closes gh-28555
This commit is contained in:
Brian Clozel 2022-06-10 17:04:54 +02:00
parent 100ce9642a
commit 9c9b2356ce
13 changed files with 1318 additions and 55 deletions

View File

@ -28,8 +28,8 @@ import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedMethods; import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints; 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.Compiled;
import org.springframework.aot.test.generator.compile.TestCompiler; import org.springframework.aot.test.generator.compile.TestCompiler;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
@ -277,15 +277,8 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
} }
private void assertHasMethodInvokeHints(Class<?> beanType, String... methodNames) { private void assertHasMethodInvokeHints(Class<?> beanType, String... methodNames) {
assertThat(this.hints.reflection().getTypeHint(beanType)).satisfies(typeHint -> { assertThat(methodNames).allMatch(methodName ->
for (String methodName : methodNames) { RuntimeHintsPredicates.reflection().onMethod(beanType, methodName).invoke().test(this.hints));
assertThat(typeHint.methods()).anySatisfy(methodHint -> {
assertThat(methodHint.getName()).isEqualTo(methodName);
assertThat(methodHint.getModes())
.containsExactly(ExecutableMode.INVOKE);
});
}
});
} }
@Test @Test

View File

@ -29,6 +29,7 @@ import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.InMemoryGeneratedFiles; import org.springframework.aot.generate.InMemoryGeneratedFiles;
import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsPredicates;
import org.springframework.aot.hint.TypeReference; import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.annotation.Reflective; import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
@ -96,8 +97,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
void shouldRegisterAnnotation() { void shouldRegisterAnnotation() {
process(SampleMethodMetaAnnotatedBean.class); process(SampleMethodMetaAnnotatedBean.class);
RuntimeHints runtimeHints = this.generationContext.getRuntimeHints(); RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
assertThat(runtimeHints.reflection().getTypeHint(SampleInvoker.class)).satisfies(typeHint -> assertThat(RuntimeHintsPredicates.reflection().onType(SampleInvoker.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(runtimeHints);
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS));
assertThat(runtimeHints.proxies().jdkProxies()).isEmpty(); assertThat(runtimeHints.proxies().jdkProxies()).isEmpty();
} }
@ -105,11 +105,8 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
void shouldRegisterAnnotationAndProxyWithAliasFor() { void shouldRegisterAnnotationAndProxyWithAliasFor() {
process(SampleMethodMetaAnnotatedBeanWithAlias.class); process(SampleMethodMetaAnnotatedBeanWithAlias.class);
RuntimeHints runtimeHints = this.generationContext.getRuntimeHints(); RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
assertThat(runtimeHints.reflection().getTypeHint(RetryInvoker.class)).satisfies(typeHint -> assertThat(RuntimeHintsPredicates.reflection().onType(RetryInvoker.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(runtimeHints);
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS)); assertThat(RuntimeHintsPredicates.proxies().forInterfaces(RetryInvoker.class, SynthesizedAnnotation.class)).accepts(runtimeHints);
assertThat(runtimeHints.proxies().jdkProxies()).anySatisfy(jdkProxyHint ->
assertThat(jdkProxyHint.getProxiedInterfaces()).containsExactly(
TypeReference.of(RetryInvoker.class), TypeReference.of(SynthesizedAnnotation.class)));
} }
@Nullable @Nullable
@ -196,7 +193,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
} }
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Reflective @Reflective
@ -206,7 +203,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
} }
@Target({ ElementType.METHOD }) @Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@SampleInvoker @SampleInvoker

View File

@ -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.
* <p>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<RuntimeHints> 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.
* <p>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<RuntimeHints> forInterfaces(TypeReference... interfaces) {
Assert.notEmpty(interfaces, "'interfaces' should not be empty");
return hints -> hints.proxies().jdkProxies().anyMatch(proxyHint ->
proxyHint.getProxiedInterfaces().equals(Arrays.asList(interfaces)));
}
}

View File

@ -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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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<Method> 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.
* <p>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.
* <p>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<RuntimeHints> {
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<RuntimeHints> 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<RuntimeHints> 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<T extends Executable> implements Predicate<RuntimeHints> {
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<T> 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<T> 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<RuntimeHints> 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<Constructor<?>> {
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<RuntimeHints> exactMatch() {
return hints -> hints.reflection().getTypeHint(this.executable.getDeclaringClass()).constructors().anyMatch(executableHint -> {
List<TypeReference> 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<Method> {
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<RuntimeHints> exactMatch() {
return hints -> (hints.reflection().getTypeHint(this.executable.getDeclaringClass()) != null) &&
hints.reflection().getTypeHint(this.executable.getDeclaringClass()).methods().anyMatch(executableHint -> {
List<TypeReference> 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<RuntimeHints> {
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()));
}
}
}

View File

@ -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<String, Pattern> 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<RuntimeHints> 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.
* <p>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<RuntimeHints> 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<RuntimeHints> 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);
}
}

View File

@ -22,6 +22,8 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.springframework.lang.Nullable;
/** /**
* A collection of {@link ResourcePatternHint} describing whether * A collection of {@link ResourcePatternHint} describing whether
* resources should be made available at runtime through a matching * resources should be made available at runtime through a matching
@ -43,6 +45,11 @@ public final class ResourcePatternHints {
this.excludes = new ArrayList<>(builder.excludes); this.excludes = new ArrayList<>(builder.excludes);
} }
private ResourcePatternHints(List<ResourcePatternHint> includes, List<ResourcePatternHint> excludes) {
this.includes = includes;
this.excludes = excludes;
}
/** /**
* Return the include patterns to use to identify the resources to match. * Return the include patterns to use to identify the resources to match.
* @return the include patterns * @return the include patterns
@ -59,6 +66,16 @@ public final class ResourcePatternHints {
return this.excludes; return this.excludes;
} }
ResourcePatternHints merge(ResourcePatternHints resourcePatternHints) {
List<ResourcePatternHint> includes = new ArrayList<>();
includes.addAll(this.includes);
includes.addAll(resourcePatternHints.includes);
List<ResourcePatternHint> excludes = new ArrayList<>();
excludes.addAll(this.excludes);
excludes.addAll(resourcePatternHints.excludes);
return new ResourcePatternHints(includes, excludes);
}
/** /**
* Builder for {@link ResourcePatternHints}. * Builder for {@link ResourcePatternHints}.
@ -75,7 +92,7 @@ public final class ResourcePatternHints {
* @param includes the include patterns (see {@link ResourcePatternHint} documentation) * @param includes the include patterns (see {@link ResourcePatternHint} documentation)
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
*/ */
public Builder includes(TypeReference reachableType, String... includes) { public Builder includes(@Nullable TypeReference reachableType, String... includes) {
List<ResourcePatternHint> newIncludes = Arrays.stream(includes) List<ResourcePatternHint> newIncludes = Arrays.stream(includes)
.map(include -> new ResourcePatternHint(include, reachableType)).toList(); .map(include -> new ResourcePatternHint(include, reachableType)).toList();
this.includes.addAll(newIncludes); this.includes.addAll(newIncludes);

View File

@ -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.
* <p>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:
* <pre class="code">
* Predicate&lt;RuntimeHints&gt; predicate = RuntimeHintsPredicates.reflection().onMethod(MyClass.class, "someMethod").invoke();
* assertThat(predicate).accepts(runtimeHints);
* </pre>
* @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;
}
}

View File

@ -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<RuntimeHints> predicate) {
assertThat(predicate.test(this.runtimeHints)).isTrue();
}
private void assertPredicateDoesNotMatch(Predicate<RuntimeHints> predicate) {
assertThat(predicate.test(this.runtimeHints)).isFalse();
}
}

View File

@ -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<TypeReference> 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<TypeReference> 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<TypeReference> 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<RuntimeHints> predicate) {
assertThat(predicate).accepts(this.runtimeHints);
}
private void assertPredicateDoesNotMatch(Predicate<RuntimeHints> 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() {
}
}
}

View File

@ -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<RuntimeHints> predicate) {
assertThat(predicate.test(this.runtimeHints)).isTrue();
}
private void assertPredicateDoesNotMatch(Predicate<RuntimeHints> predicate) {
assertThat(predicate.test(this.runtimeHints)).isFalse();
}
}

View File

@ -21,8 +21,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsPredicates;
import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
@ -49,16 +49,14 @@ class CoreAnnotationsRuntimeHintsRegistrarTests {
@Test @Test
void aliasForHasHints() { void aliasForHasHints() {
assertThat(this.hints.reflection().getTypeHint(TypeReference.of(AliasFor.class))) assertThat(RuntimeHintsPredicates.reflection().onType(AliasFor.class)
.satisfies(hint -> assertThat(hint.getMemberCategories()) .withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints);
.containsExactly(MemberCategory.INVOKE_DECLARED_METHODS));
} }
@Test @Test
void orderAnnotationHasHints() { void orderAnnotationHasHints() {
assertThat(this.hints.reflection().getTypeHint(TypeReference.of(Order.class))) assertThat(RuntimeHintsPredicates.reflection().onType(Order.class)
.satisfies(hint -> assertThat(hint.getMemberCategories()) .withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints);
.containsExactly(MemberCategory.INVOKE_DECLARED_METHODS));
} }
} }

View File

@ -20,11 +20,9 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ResourcePatternHint;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsPredicates;
import org.springframework.aot.hint.RuntimeHintsRegistrar; 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.DummyFactory;
import org.springframework.core.io.support.MyDummyFactory1; import org.springframework.core.io.support.MyDummyFactory1;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
@ -51,28 +49,19 @@ class SpringFactoriesLoaderRuntimeHintsRegistrarTests {
@Test @Test
void resourceLocationHasHints() { void resourceLocationHasHints() {
assertThat(this.hints.resources().resourcePatterns()) assertThat(RuntimeHintsPredicates.resource().forResource(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION)).accepts(this.hints);
.anySatisfy(hint -> assertThat(hint.getIncludes()).map(ResourcePatternHint::getPattern)
.contains(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION));
} }
@Test @Test
void factoryTypeHasHint() { void factoryTypeHasHint() {
TypeReference type = TypeReference.of(DummyFactory.class); assertThat(RuntimeHintsPredicates.reflection().onType(DummyFactory.class)
assertThat(this.hints.reflection().getTypeHint(type)) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints);
.satisfies(this::expectedHints);
} }
@Test @Test
void factoryImplementationHasHint() { void factoryImplementationHasHint() {
TypeReference type = TypeReference.of(MyDummyFactory1.class); assertThat(RuntimeHintsPredicates.reflection().onType(MyDummyFactory1.class)
assertThat(this.hints.reflection().getTypeHint(type)) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints);
.satisfies(this::expectedHints);
}
private void expectedHints(TypeHint hint) {
assertThat(hint.getMemberCategories())
.containsExactly(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
} }
} }

View File

@ -25,8 +25,8 @@ import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints; 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.Compiled;
import org.springframework.aot.test.generator.compile.TestCompiler; import org.springframework.aot.test.generator.compile.TestCompiler;
import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.beans.testfixture.beans.TestBean;
@ -87,11 +87,8 @@ class InjectionCodeGeneratorTests {
TestBean bean = new TestBean(); TestBean bean = new TestBean();
Field field = ReflectionUtils.findField(bean.getClass(), "age"); Field field = ReflectionUtils.findField(bean.getClass(), "age");
this.generator.generateInjectionCode(field, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); this.generator.generateInjectionCode(field, INSTANCE_VARIABLE, CodeBlock.of("$L", 123));
assertThat(this.hints.reflection().getTypeHint(TestBean.class)) assertThat(RuntimeHintsPredicates.reflection().onField(TestBean.class, "age").allowWrite())
.satisfies(hint -> assertThat(hint.fields()).anySatisfy(fieldHint -> { .accepts(this.hints);
assertThat(fieldHint.getName()).isEqualTo("age");
assertThat(fieldHint.isAllowWrite()).isTrue();
}));
} }
@Test @Test
@ -127,11 +124,8 @@ class InjectionCodeGeneratorTests {
TestBeanWithPrivateMethod bean = new TestBeanWithPrivateMethod(); TestBeanWithPrivateMethod bean = new TestBeanWithPrivateMethod();
Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class); Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class);
this.generator.generateInjectionCode(method, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); this.generator.generateInjectionCode(method, INSTANCE_VARIABLE, CodeBlock.of("$L", 123));
assertThat(this.hints.reflection().getTypeHint(TestBeanWithPrivateMethod.class)) assertThat(RuntimeHintsPredicates.reflection()
.satisfies(hint -> assertThat(hint.methods()).anySatisfy(methodHint -> { .onMethod(TestBeanWithPrivateMethod.class, "setAge").invoke()).accepts(this.hints);
assertThat(methodHint.getName()).isEqualTo("setAge");
assertThat(methodHint.getModes()).contains(ExecutableMode.INVOKE);
}));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")