From 4562189125d2894e27aaac122591aac44f2ef1bb Mon Sep 17 00:00:00 2001 From: Laurent Martelli Date: Tue, 4 Jul 2023 12:22:57 +0100 Subject: [PATCH] Switch ImportsContextCustomizer to use MergedAnnotations.search #36211 Use `MergedAnnotations.search` in `ImportsContextCustomizer` rather than needing dedicated search logic. See gh-36211 --- .../context/ImportsContextCustomizer.java | 165 ++++-------------- 1 file changed, 35 insertions(+), 130 deletions(-) diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java index fc4a8babbf..4b8a6a7531 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java @@ -16,13 +16,12 @@ package org.springframework.boot.test.context; -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.util.Collections; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; @@ -42,10 +41,10 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportSelector; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationFilter; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.core.annotation.Order; import org.springframework.core.style.ToStringCreator; import org.springframework.core.type.AnnotationMetadata; @@ -53,6 +52,8 @@ import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.util.ReflectionUtils; +import static org.springframework.core.annotation.AnnotationFilter.packages; + /** * {@link ContextCustomizer} to allow {@code @Import} annotations to be used directly on * test classes. @@ -217,82 +218,41 @@ class ImportsContextCustomizer implements ContextCustomizer { */ static class ContextCustomizerKey { - private static final Class[] NO_IMPORTS = {}; - - private static final Set ANNOTATION_FILTERS; - - static { - Set filters = new HashSet<>(); - filters.add(new JavaLangAnnotationFilter()); - filters.add(new KotlinAnnotationFilter()); - filters.add(new SpockAnnotationFilter()); - filters.add(new JUnitAnnotationFilter()); - ANNOTATION_FILTERS = Collections.unmodifiableSet(filters); - } + private static final AnnotationFilter ANNOTATION_FILTERS = or(packages("java.lang.annotation"), + packages("org.spockframework", "spock"), + or(isEqualTo("kotlin.Metadata"), packages("kotlin.annotation")), packages(("org.junit"))); - private final Set key; + private final Object key; ContextCustomizerKey(Class testClass) { - Set annotations = new HashSet<>(); - Set> seen = new HashSet<>(); - collectClassAnnotations(testClass, annotations, seen); - Set determinedImports = determineImports(annotations, testClass); - this.key = Collections.unmodifiableSet((determinedImports != null) ? determinedImports : annotations); - } - - private void collectClassAnnotations(Class classType, Set annotations, Set> seen) { - if (seen.add(classType)) { - collectElementAnnotations(classType, annotations, seen); - for (Class interfaceType : classType.getInterfaces()) { - collectClassAnnotations(interfaceType, annotations, seen); - } - if (classType.getSuperclass() != null) { - collectClassAnnotations(classType.getSuperclass(), annotations, seen); - } + MergedAnnotations mergedAnnotations = MergedAnnotations + .search(MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .withAnnotationFilter(ANNOTATION_FILTERS) + .from(testClass); + Set determinedImports = determineImports(mergedAnnotations, testClass); + if (determinedImports != null) { + this.key = determinedImports; } - } - - private void collectElementAnnotations(AnnotatedElement element, Set annotations, - Set> seen) { - for (MergedAnnotation mergedAnnotation : MergedAnnotations.from(element, - SearchStrategy.DIRECT)) { - Annotation annotation = mergedAnnotation.synthesize(); - if (!isIgnoredAnnotation(annotation)) { - annotations.add(annotation); - collectClassAnnotations(annotation.annotationType(), annotations, seen); - } + else { + this.key = AnnotatedElementUtils.findAllMergedAnnotations(testClass, + mergedAnnotations.stream().map(MergedAnnotation::getType).collect(Collectors.toSet())); } } - private boolean isIgnoredAnnotation(Annotation annotation) { - for (AnnotationFilter annotationFilter : ANNOTATION_FILTERS) { - if (annotationFilter.isIgnored(annotation)) { - return true; - } - } - return false; - } - - private Set determineImports(Set annotations, Class testClass) { - Set determinedImports = new LinkedHashSet<>(); + private Set determineImports(MergedAnnotations mergedAnnotations, Class testClass) { AnnotationMetadata testClassMetadata = AnnotationMetadata.introspect(testClass); - for (Annotation annotation : annotations) { - for (Class source : getImports(annotation)) { - Set determinedSourceImports = determineImports(source, testClassMetadata); - if (determinedSourceImports == null) { + return mergedAnnotations.stream(Import.class) + .flatMap((ma) -> Stream.of(ma.getClassArray("value"))) + .map((source) -> determineImports(source, testClassMetadata)) + .reduce(new HashSet<>(), (a, b) -> { + if (a == null || b == null) { return null; } - determinedImports.addAll(determinedSourceImports); - } - } - return determinedImports; - } - - private Class[] getImports(Annotation annotation) { - if (annotation instanceof Import importAnnotation) { - return importAnnotation.value(); - } - return NO_IMPORTS; + else { + a.add(b); + return a; + } + }); } private Set determineImports(Class source, AnnotationMetadata metadata) { @@ -340,67 +300,12 @@ class ImportsContextCustomizer implements ContextCustomizer { } - /** - * Filter used to limit considered annotations. - */ - private interface AnnotationFilter { - - boolean isIgnored(Annotation annotation); - - } - - /** - * {@link AnnotationFilter} for {@literal java.lang} annotations. - */ - private static final class JavaLangAnnotationFilter implements AnnotationFilter { - - @Override - public boolean isIgnored(Annotation annotation) { - return AnnotationUtils.isInJavaLangAnnotationPackage(annotation); - } - - } - - /** - * {@link AnnotationFilter} for Kotlin annotations. - */ - private static final class KotlinAnnotationFilter implements AnnotationFilter { - - @Override - public boolean isIgnored(Annotation annotation) { - return "kotlin.Metadata".equals(annotation.annotationType().getName()) - || isInKotlinAnnotationPackage(annotation); - } - - private boolean isInKotlinAnnotationPackage(Annotation annotation) { - return annotation.annotationType().getName().startsWith("kotlin.annotation."); - } - + static AnnotationFilter or(AnnotationFilter... filters) { + return typeName -> Stream.of(filters).anyMatch(filter -> filter.matches(typeName)); } - /** - * {@link AnnotationFilter} for Spock annotations. - */ - private static final class SpockAnnotationFilter implements AnnotationFilter { - - @Override - public boolean isIgnored(Annotation annotation) { - return annotation.annotationType().getName().startsWith("org.spockframework.") - || annotation.annotationType().getName().startsWith("spock."); - } - - } - - /** - * {@link AnnotationFilter} for JUnit annotations. - */ - private static final class JUnitAnnotationFilter implements AnnotationFilter { - - @Override - public boolean isIgnored(Annotation annotation) { - return annotation.annotationType().getName().startsWith("org.junit."); - } - + static AnnotationFilter isEqualTo(String expectedTypeName) { + return typeName -> typeName.equals(expectedTypeName); } }