diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java index 807c41acbe..1469160cc1 100644 --- a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java @@ -26,8 +26,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; import org.springframework.lang.Nullable; -import org.springframework.test.util.MetaAnnotationUtils; -import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor; import org.springframework.util.ClassUtils; /** @@ -155,7 +154,7 @@ abstract class BootstrapUtils { private static Class resolveExplicitTestContextBootstrapper(Class testClass) { Set annotations = new LinkedHashSet<>(); AnnotationDescriptor descriptor = - MetaAnnotationUtils.findAnnotationDescriptor(testClass, BootstrapWith.class); + TestContextAnnotationUtils.findAnnotationDescriptor(testClass, BootstrapWith.class); while (descriptor != null) { annotations.addAll(descriptor.findAllLocalMergedAnnotations()); descriptor = descriptor.next(); @@ -180,7 +179,7 @@ abstract class BootstrapUtils { } private static Class resolveDefaultTestContextBootstrapper(Class testClass) throws Exception { - boolean webApp = (MetaAnnotationUtils.findMergedAnnotation(testClass, webAppConfigurationClass) != null); + boolean webApp = (TestContextAnnotationUtils.findMergedAnnotation(testClass, webAppConfigurationClass) != null); String bootstrapperClassName = (webApp ? DEFAULT_WEB_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME : DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME); return ClassUtils.forName(bootstrapperClassName, BootstrapUtils.class.getClassLoader()); diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/context/TestContextAnnotationUtils.java new file mode 100644 index 0000000000..e8fa944d48 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextAnnotationUtils.java @@ -0,0 +1,664 @@ +/* + * Copyright 2002-2020 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.test.context; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; + +import org.springframework.core.SpringProperties; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotationCollectors; +import org.springframework.core.annotation.MergedAnnotationPredicates; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; +import org.springframework.core.annotation.RepeatableContainers; +import org.springframework.core.style.ToStringCreator; +import org.springframework.lang.Nullable; +import org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ConcurrentLruCache; +import org.springframework.util.ObjectUtils; + +/** + * {@code TestContextAnnotationUtils} is a collection of utility methods that + * complements the standard support already available in {@link AnnotationUtils} + * and {@link AnnotatedElementUtils}. + * + *

Mainly for internal use within the Spring TestContext Framework. + * + *

Whereas {@code AnnotationUtils} and {@code AnnotatedElementUtils} provide + * utilities for getting or finding annotations, + * {@code TestContextAnnotationUtils} goes a step further by providing support + * for determining the root class on which an annotation is declared, + * either directly or indirectly via a composed annotation. This + * additional information is encapsulated in an {@link AnnotationDescriptor}. + * + *

The additional information provided by an {@code AnnotationDescriptor} is + * required by the Spring TestContext Framework in order to be able to + * support class hierarchy traversals for annotations such as + * {@link ContextConfiguration @ContextConfiguration}, + * {@link TestExecutionListeners @TestExecutionListeners}, and + * {@link ActiveProfiles @ActiveProfiles} which offer support for merging and + * overriding various inherited annotation attributes — for + * example, {@link ContextConfiguration#inheritLocations}. + * + * @author Sam Brannen + * @since 5.3, though originally since 4.0 as {@link org.springframework.test.util.MetaAnnotationUtils} + * @see AnnotationUtils + * @see AnnotatedElementUtils + * @see AnnotationDescriptor + */ +public abstract class TestContextAnnotationUtils { + + private static final ConcurrentLruCache, EnclosingConfiguration> cachedEnclosingConfigurationModes = + new ConcurrentLruCache<>(32, TestContextAnnotationUtils::lookUpEnclosingConfiguration); + + + /** + * Find the first annotation of the specified {@code annotationType} within + * the annotation hierarchy above the supplied class, merge that + * annotation's attributes with matching attributes from annotations + * in lower levels of the annotation hierarchy, and synthesize the result back + * into an annotation of the specified {@code annotationType}. + *

In the context of this method, the term "above" means within the + * {@linkplain Class#getSuperclass() superclass} hierarchy or within the + * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of the + * supplied class. The enclosing class hierarchy will only be searched if + * appropriate. + *

{@link org.springframework.core.annotation.AliasFor @AliasFor} semantics + * are fully supported, both within a single annotation and within annotation + * hierarchies. + * @param clazz the class to look for annotations on + * @param annotationType the type of annotation to look for + * @return the merged, synthesized {@code Annotation}, or {@code null} if not found + * @see AnnotatedElementUtils#findMergedAnnotation(java.lang.reflect.AnnotatedElement, Class) + * @see #findAnnotationDescriptor(Class, Class) + * @see #searchEnclosingClass(Class) + */ + @Nullable + public static T findMergedAnnotation(Class clazz, Class annotationType) { + return findMergedAnnotation(clazz, annotationType, TestContextAnnotationUtils::searchEnclosingClass); + } + + @Nullable + private static T findMergedAnnotation(Class clazz, Class annotationType, + Predicate> searchEnclosingClass) { + + AnnotationDescriptor descriptor = + findAnnotationDescriptor(clazz, annotationType, searchEnclosingClass, new HashSet<>()); + return (descriptor != null ? descriptor.synthesizeAnnotation() : null); + } + + /** + * Get all repeatable annotations of the specified {@code annotationType} + * within the annotation hierarchy above the supplied class; and for + * each annotation found, merge that annotation's attributes with matching + * attributes from annotations in lower levels of the annotation hierarchy and + * synthesize the results back into an annotation of the specified {@code annotationType}. + *

This method will find {@link java.lang.annotation.Inherited @Inherited} + * annotations declared on superclasses if the supplied class does not have + * any local declarations of the repeatable annotation. If no inherited + * annotations are found, this method will search within the + * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of the + * supplied class. The enclosing class hierarchy will only be searched if + * appropriate. + *

The container type that holds the repeatable annotations will be looked up + * via {@link java.lang.annotation.Repeatable}. + *

{@link org.springframework.core.annotation.AliasFor @AliasFor} semantics + * are fully supported, both within a single annotation and within annotation + * hierarchies. + * @param clazz the class on which to search for annotations (never {@code null}) + * @param annotationType the annotation type to find (never {@code null}) + * @return the set of all merged repeatable annotations found, or an empty set + * if none were found + * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(java.lang.reflect.AnnotatedElement, Class) + * @see #searchEnclosingClass(Class) + */ + public static Set getMergedRepeatableAnnotations( + Class clazz, Class annotationType) { + + // Present (via @Inherited semantics), directly present, or meta-present? + Set mergedAnnotations = MergedAnnotations.from(clazz, SearchStrategy.INHERITED_ANNOTATIONS) + .stream(annotationType) + .collect(MergedAnnotationCollectors.toAnnotationSet()); + + if (!mergedAnnotations.isEmpty()) { + return mergedAnnotations; + } + + // Declared on an enclosing class of an inner class? + if (searchEnclosingClass(clazz)) { + // Then mimic @Inherited semantics within the enclosing class hierarchy. + return getMergedRepeatableAnnotations(clazz.getEnclosingClass(), annotationType); + } + + return Collections.emptySet(); + } + + /** + * Find the {@link AnnotationDescriptor} for the supplied {@code annotationType} + * on the supplied {@link Class}, traversing its annotations, interfaces, and + * superclasses if no annotation can be found on the given class itself. + *

This method explicitly handles class-level annotations which are not + * declared as {@linkplain java.lang.annotation.Inherited inherited} as + * well as meta-annotations. + *

The algorithm operates as follows: + *

    + *
  1. Search for the annotation on the given class and return a corresponding + * {@code AnnotationDescriptor} if found. + *
  2. Recursively search through all annotations that the given class declares. + *
  3. Recursively search through all interfaces implemented by the given class. + *
  4. Recursively search through the superclass hierarchy of the given class. + *
+ *

In this context, the term recursively means that the search + * process continues by returning to step #1 with the current annotation, + * interface, or superclass as the class to look for annotations on. + * @param clazz the class to look for annotations on + * @param annotationType the type of annotation to look for + * @return the corresponding annotation descriptor if the annotation was found; + * otherwise {@code null} + * @see #findAnnotationDescriptorForTypes(Class, Class...) + */ + @Nullable + public static AnnotationDescriptor findAnnotationDescriptor( + Class clazz, Class annotationType) { + + return findAnnotationDescriptor(clazz, annotationType, TestContextAnnotationUtils::searchEnclosingClass, + new HashSet<>()); + } + + /** + * Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)}, + * avoiding endless recursion by tracking which annotations have already been + * visited. + * @param clazz the class to look for annotations on + * @param annotationType the type of annotation to look for + * @param visited the set of annotations that have already been visited + * @return the corresponding annotation descriptor if the annotation was found; + * otherwise {@code null} + */ + @Nullable + private static AnnotationDescriptor findAnnotationDescriptor( + @Nullable Class clazz, Class annotationType, Predicate> searchEnclosingClass, + Set visited) { + + Assert.notNull(annotationType, "Annotation type must not be null"); + if (clazz == null || Object.class == clazz) { + return null; + } + + // Declared locally? + if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { + return new AnnotationDescriptor<>(clazz, clazz.getAnnotation(annotationType)); + } + + AnnotationDescriptor descriptor = null; + + // Declared on a composed annotation (i.e., as a meta-annotation)? + for (Annotation composedAnn : clazz.getDeclaredAnnotations()) { + Class composedType = composedAnn.annotationType(); + if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) { + descriptor = findAnnotationDescriptor(composedType, annotationType, searchEnclosingClass, visited); + if (descriptor != null) { + return new AnnotationDescriptor<>( + clazz, descriptor.getDeclaringClass(), composedAnn, descriptor.getAnnotation()); + } + } + } + + // Declared on an interface? + for (Class ifc : clazz.getInterfaces()) { + descriptor = findAnnotationDescriptor(ifc, annotationType, searchEnclosingClass, visited); + if (descriptor != null) { + return new AnnotationDescriptor<>(clazz, descriptor.getDeclaringClass(), + descriptor.getComposedAnnotation(), descriptor.getAnnotation()); + } + } + + // Declared on a superclass? + descriptor = findAnnotationDescriptor(clazz.getSuperclass(), annotationType, searchEnclosingClass, visited); + if (descriptor != null) { + return descriptor; + } + + // Declared on an enclosing class of an inner class? + if (searchEnclosingClass.test(clazz)) { + descriptor = findAnnotationDescriptor(clazz.getEnclosingClass(), annotationType, searchEnclosingClass, visited); + if (descriptor != null) { + return descriptor; + } + } + + return null; + } + + /** + * Find the {@link UntypedAnnotationDescriptor} for the first {@link Class} + * in the inheritance hierarchy of the specified {@code clazz} (including + * the specified {@code clazz} itself) which declares at least one of the + * specified {@code annotationTypes}. + *

This method traverses the annotations, interfaces, and superclasses + * of the specified {@code clazz} if no annotation can be found on the given + * class itself. + *

This method explicitly handles class-level annotations which are not + * declared as {@linkplain java.lang.annotation.Inherited inherited} as + * well as meta-annotations. + *

The algorithm operates as follows: + *

    + *
  1. Search for a local declaration of one of the annotation types on + * the given class and return a corresponding {@code UntypedAnnotationDescriptor} + * if found. + *
  2. Recursively search through all annotations that the given class declares. + *
  3. Recursively search through all interfaces implemented by the given class. + *
  4. Recursively search through the superclass hierarchy of the given class. + *
+ *

In this context, the term recursively means that the search + * process continues by returning to step #1 with the current annotation, + * interface, or superclass as the class to look for annotations on. + * @param clazz the class to look for annotations on + * @param annotationTypes the types of annotations to look for + * @return the corresponding annotation descriptor if one of the annotations + * was found; otherwise {@code null} + * @see #findAnnotationDescriptor(Class, Class) + */ + @SuppressWarnings("unchecked") + @Nullable + public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes( + Class clazz, Class... annotationTypes) { + + return findAnnotationDescriptorForTypes(clazz, annotationTypes, new HashSet<>()); + } + + /** + * Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)}, + * avoiding endless recursion by tracking which annotations have already been + * visited. + * @param clazz the class to look for annotations on + * @param annotationTypes the types of annotations to look for + * @param visited the set of annotations that have already been visited + * @return the corresponding annotation descriptor if one of the annotations + * was found; otherwise {@code null} + */ + @Nullable + private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(@Nullable Class clazz, + Class[] annotationTypes, Set visited) { + + assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty"); + if (clazz == null || Object.class == clazz) { + return null; + } + + // Declared locally? + for (Class annotationType : annotationTypes) { + if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { + return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType), annotationTypes); + } + } + + // Declared on a composed annotation (i.e., as a meta-annotation)? + for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + composedAnnotation.annotationType(), annotationTypes, visited); + if (descriptor != null) { + return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), + composedAnnotation, descriptor.getAnnotation(), annotationTypes); + } + } + } + + // Declared on an interface? + for (Class ifc : clazz.getInterfaces()) { + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(ifc, annotationTypes, visited); + if (descriptor != null) { + return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), + descriptor.getComposedAnnotation(), descriptor.getAnnotation(), annotationTypes); + } + } + + // Declared on a superclass? + UntypedAnnotationDescriptor descriptor = + findAnnotationDescriptorForTypes(clazz.getSuperclass(), annotationTypes, visited); + if (descriptor != null) { + return descriptor; + } + + // Declared on an enclosing class of an inner class? + if (searchEnclosingClass(clazz)) { + descriptor = findAnnotationDescriptorForTypes(clazz.getEnclosingClass(), annotationTypes, visited); + if (descriptor != null) { + return descriptor; + } + } + + return null; + } + + /** + * Determine if annotations on the enclosing class of the supplied class + * should be searched by algorithms in {@link TestContextAnnotationUtils}. + * @param clazz the class whose enclosing class should potentially be searched + * @return {@code true} if the supplied class is an inner class whose enclosing + * class should be searched + * @see ClassUtils#isInnerClass(Class) + * @see NestedTestConfiguration @NestedTestConfiguration + */ + public static boolean searchEnclosingClass(Class clazz) { + return (ClassUtils.isInnerClass(clazz) && + getEnclosingConfiguration(clazz) == EnclosingConfiguration.INHERIT); + } + + /** + * Get the {@link EnclosingConfiguration} mode for the supplied class. + * @param clazz the class for which the enclosing configuration mode should + * be resolved + * @return the resolved enclosing configuration mode + */ + private static EnclosingConfiguration getEnclosingConfiguration(Class clazz) { + return cachedEnclosingConfigurationModes.get(clazz); + } + + private static EnclosingConfiguration lookUpEnclosingConfiguration(Class clazz) { + // @NestedTestConfiguration should not be discovered on an enclosing class + // for a nested interface (which is always static), so our predicate simply + // ensures that the candidate class is an inner class. + Predicate> searchEnclosingClass = ClassUtils::isInnerClass; + NestedTestConfiguration nestedTestConfiguration = + findMergedAnnotation(clazz, NestedTestConfiguration.class, searchEnclosingClass); + return (nestedTestConfiguration != null ? nestedTestConfiguration.value() : getDefaultEnclosingConfigurationMode()); + } + + private static EnclosingConfiguration getDefaultEnclosingConfigurationMode() { + String value = SpringProperties.getProperty(NestedTestConfiguration.ENCLOSING_CONFIGURATION_PROPERTY_NAME); + EnclosingConfiguration enclosingConfigurationMode = EnclosingConfiguration.from(value); + return (enclosingConfigurationMode != null ? enclosingConfigurationMode : EnclosingConfiguration.INHERIT); + } + + private static void assertNonEmptyAnnotationTypeArray(Class[] annotationTypes, String message) { + if (ObjectUtils.isEmpty(annotationTypes)) { + throw new IllegalArgumentException(message); + } + for (Class clazz : annotationTypes) { + if (!Annotation.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException("Array elements must be of type Annotation"); + } + } + } + + + /** + * Descriptor for an {@link Annotation}, including the {@linkplain + * #getDeclaringClass() class} on which the annotation is declared + * as well as the actual {@linkplain #getAnnotation() annotation} instance. + *

If the annotation is used as a meta-annotation, the descriptor also includes + * the {@linkplain #getComposedAnnotation() composed annotation} on which the + * annotation is present. In such cases, the root declaring class is + * not directly annotated with the annotation but rather indirectly via the + * composed annotation. + *

Given the following example, if we are searching for the {@code @Transactional} + * annotation on the {@code TransactionalTests} class, then the + * properties of the {@code AnnotationDescriptor} would be as follows. + *

+ *

+	 * @Transactional
+	 * @ContextConfiguration({"/test-datasource.xml", "/repository-config.xml"})
+	 * public class TransactionalTests { }
+	 * 
+ *

Given the following example, if we are searching for the {@code @Transactional} + * annotation on the {@code UserRepositoryTests} class, then the + * properties of the {@code AnnotationDescriptor} would be as follows. + *

+ *

+	 * @Transactional
+	 * @ContextConfiguration({"/test-datasource.xml", "/repository-config.xml"})
+	 * @Retention(RetentionPolicy.RUNTIME)
+	 * public @interface RepositoryTests { }
+	 *
+	 * @RepositoryTests
+	 * public class UserRepositoryTests { }
+	 * 
+ * + * @param the annotation type + */ + public static class AnnotationDescriptor { + + private final Class rootDeclaringClass; + + private final Class declaringClass; + + @Nullable + private final Annotation composedAnnotation; + + private final T annotation; + + private final AnnotationAttributes annotationAttributes; + + public AnnotationDescriptor(Class rootDeclaringClass, T annotation) { + this(rootDeclaringClass, rootDeclaringClass, null, annotation); + } + + public AnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, + @Nullable Annotation composedAnnotation, T annotation) { + + Assert.notNull(rootDeclaringClass, "'rootDeclaringClass' must not be null"); + Assert.notNull(declaringClass, "'declaringClass' must not be null"); + Assert.notNull(annotation, "Annotation must not be null"); + this.rootDeclaringClass = rootDeclaringClass; + this.declaringClass = declaringClass; + this.composedAnnotation = composedAnnotation; + this.annotation = annotation; + this.annotationAttributes = AnnotatedElementUtils.findMergedAnnotationAttributes( + rootDeclaringClass, annotation.annotationType().getName(), false, false); + Assert.state(this.annotationAttributes != null, "No annotation attributes"); + } + + public Class getRootDeclaringClass() { + return this.rootDeclaringClass; + } + + public Class getDeclaringClass() { + return this.declaringClass; + } + + public T getAnnotation() { + return this.annotation; + } + + /** + * Synthesize the merged {@link #getAnnotationAttributes AnnotationAttributes} + * in this descriptor back into an annotation of the target + * {@linkplain #getAnnotationType annotation type}. + * @see #getAnnotationAttributes() + * @see #getAnnotationType() + * @see AnnotationUtils#synthesizeAnnotation(java.util.Map, Class, java.lang.reflect.AnnotatedElement) + */ + public T synthesizeAnnotation() { + return AnnotationUtils.synthesizeAnnotation( + getAnnotationAttributes(), getAnnotationType(), getRootDeclaringClass()); + } + + @SuppressWarnings("unchecked") + public Class getAnnotationType() { + return (Class) this.annotation.annotationType(); + } + + public AnnotationAttributes getAnnotationAttributes() { + return this.annotationAttributes; + } + + @Nullable + public Annotation getComposedAnnotation() { + return this.composedAnnotation; + } + + @Nullable + public Class getComposedAnnotationType() { + return (this.composedAnnotation != null ? this.composedAnnotation.annotationType() : null); + } + + /** + * Find the next {@link AnnotationDescriptor} for the specified + * {@linkplain #getAnnotationType() annotation type} in the hierarchy + * above the {@linkplain #getRootDeclaringClass() root declaring class} + * of this descriptor. + *

If a corresponding annotation is found in the superclass hierarchy + * of the root declaring class, that will be returned. Otherwise, an + * attempt will be made to find a corresponding annotation in the + * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of + * the root declaring class if + * {@linkplain TestContextAnnotationUtils#searchEnclosingClass appropriate}. + * @return the next corresponding annotation descriptor if the annotation + * was found; otherwise {@code null} + */ + @Nullable + public AnnotationDescriptor next() { + // Declared on a superclass? + AnnotationDescriptor descriptor = + findAnnotationDescriptor(getRootDeclaringClass().getSuperclass(), getAnnotationType()); + // Declared on an enclosing class of an inner class? + if (descriptor == null && searchEnclosingClass(getRootDeclaringClass())) { + descriptor = findAnnotationDescriptor(getRootDeclaringClass().getEnclosingClass(), getAnnotationType()); + } + return descriptor; + } + + /** + * Find all annotations of the specified + * {@linkplain #getAnnotationType() annotation type} that are present or + * meta-present on the {@linkplain #getRootDeclaringClass() root declaring + * class} of this descriptor. + * @return the set of all merged, synthesized {@code Annotations} found, + * or an empty set if none were found + */ + public Set findAllLocalMergedAnnotations() { + SearchStrategy searchStrategy = + (getEnclosingConfiguration(getRootDeclaringClass()) == EnclosingConfiguration.INHERIT ? + SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES : + SearchStrategy.TYPE_HIERARCHY); + return MergedAnnotations.from(getRootDeclaringClass(), searchStrategy, RepeatableContainers.none()) + .stream(getAnnotationType()) + .filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex)) + .collect(MergedAnnotationCollectors.toAnnotationSet()); + } + + /** + * Provide a textual representation of this {@code AnnotationDescriptor}. + */ + @Override + public String toString() { + return new ToStringCreator(this) + .append("rootDeclaringClass", this.rootDeclaringClass.getName()) + .append("declaringClass", this.declaringClass.getName()) + .append("composedAnnotation", this.composedAnnotation) + .append("annotation", this.annotation) + .toString(); + } + } + + + /** + * Untyped extension of {@link AnnotationDescriptor} that is used + * to describe the declaration of one of several candidate annotation types + * where the actual annotation type cannot be predetermined. + */ + public static class UntypedAnnotationDescriptor extends AnnotationDescriptor { + + private final Class[] annotationTypes; + + public UntypedAnnotationDescriptor(Class rootDeclaringClass, Annotation annotation, + Class[] annotationTypes) { + + this(rootDeclaringClass, rootDeclaringClass, null, annotation, annotationTypes); + } + + public UntypedAnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, + @Nullable Annotation composedAnnotation, Annotation annotation, + Class[] annotationTypes) { + + super(rootDeclaringClass, declaringClass, composedAnnotation, annotation); + assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty"); + this.annotationTypes = annotationTypes; + } + + /** + * Throws an {@link UnsupportedOperationException} since the type of annotation + * represented by an {@code UntypedAnnotationDescriptor} is unknown. + */ + @Override + public Annotation synthesizeAnnotation() { + throw new UnsupportedOperationException( + "synthesizeAnnotation() is unsupported in UntypedAnnotationDescriptor"); + } + + /** + * Find the next {@link UntypedAnnotationDescriptor} for the specified + * annotation types in the hierarchy above the + * {@linkplain #getRootDeclaringClass() root declaring class} of this + * descriptor. + *

If one of the corresponding annotations is found in the superclass + * hierarchy of the root declaring class, that will be returned. Otherwise, + * an attempt will be made to find a corresponding annotation in the + * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of + * the root declaring class if + * {@linkplain TestContextAnnotationUtils#searchEnclosingClass appropriate}. + * @return the next corresponding annotation descriptor if one of the + * annotations was found; otherwise {@code null} + * @see AnnotationDescriptor#next() + */ + @Override + @Nullable + public UntypedAnnotationDescriptor next() { + // Declared on a superclass? + UntypedAnnotationDescriptor descriptor = + findAnnotationDescriptorForTypes(getRootDeclaringClass().getSuperclass(), this.annotationTypes); + // Declared on an enclosing class of an inner class? + if (descriptor == null && searchEnclosingClass(getRootDeclaringClass())) { + descriptor = findAnnotationDescriptorForTypes(getRootDeclaringClass().getEnclosingClass(), this.annotationTypes); + } + return descriptor; + } + + /** + * Throws an {@link UnsupportedOperationException} since the type of annotation + * represented by an {@code UntypedAnnotationDescriptor} is unknown. + */ + @Override + public Set findAllLocalMergedAnnotations() { + throw new UnsupportedOperationException( + "findAllLocalMergedAnnotations() is unsupported in UntypedAnnotationDescriptor"); + } + + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java index 02e014aa8b..1ca7bda328 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java @@ -24,9 +24,9 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.style.ToStringCreator; import org.springframework.jdbc.datasource.init.ScriptUtils; import org.springframework.lang.Nullable; +import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.test.context.jdbc.SqlConfig.ErrorMode; import org.springframework.test.context.jdbc.SqlConfig.TransactionMode; -import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.util.Assert; /** @@ -100,7 +100,7 @@ class MergedSqlConfig { enforceCommentPrefixAliases(localAttributes); // Get global attributes, if any. - SqlConfig globalSqlConfig = MetaAnnotationUtils.findMergedAnnotation(testClass, SqlConfig.class); + SqlConfig globalSqlConfig = TestContextAnnotationUtils.findMergedAnnotation(testClass, SqlConfig.class); // Use local attributes only? if (globalSqlConfig == null) { diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java index 00028337fd..845b67bbe5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java @@ -34,6 +34,7 @@ import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.SqlConfig.ErrorMode; import org.springframework.test.context.jdbc.SqlConfig.TransactionMode; @@ -41,7 +42,6 @@ import org.springframework.test.context.jdbc.SqlMergeMode.MergeMode; import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.transaction.TestContextTransactionUtils; import org.springframework.test.context.util.TestContextResourceUtils; -import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; @@ -166,7 +166,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen */ @Nullable private SqlMergeMode getSqlMergeModeFor(Class clazz) { - return MetaAnnotationUtils.findMergedAnnotation(clazz, SqlMergeMode.class); + return TestContextAnnotationUtils.findMergedAnnotation(clazz, SqlMergeMode.class); } /** @@ -181,7 +181,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen * Get the {@code @Sql} annotations declared on the supplied class. */ private Set getSqlAnnotationsFor(Class clazz) { - return MetaAnnotationUtils.getMergedRepeatableAnnotations(clazz, Sql.class); + return TestContextAnnotationUtils.getMergedRepeatableAnnotations(clazz, Sql.class); } /** diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDirtiesContextTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDirtiesContextTestExecutionListener.java index f274fc30c9..055029b397 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDirtiesContextTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDirtiesContextTestExecutionListener.java @@ -29,7 +29,7 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.annotation.DirtiesContext.HierarchyMode; import org.springframework.test.annotation.DirtiesContext.MethodMode; import org.springframework.test.context.TestContext; -import org.springframework.test.util.MetaAnnotationUtils; +import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.util.Assert; /** @@ -97,7 +97,7 @@ public abstract class AbstractDirtiesContextTestExecutionListener extends Abstra Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null"); DirtiesContext methodAnn = AnnotatedElementUtils.findMergedAnnotation(testMethod, DirtiesContext.class); - DirtiesContext classAnn = MetaAnnotationUtils.findMergedAnnotation(testClass, DirtiesContext.class); + DirtiesContext classAnn = TestContextAnnotationUtils.findMergedAnnotation(testClass, DirtiesContext.class); boolean methodAnnotated = (methodAnn != null); boolean classAnnotated = (classAnn != null); MethodMode methodMode = (methodAnnotated ? methodAnn.methodMode() : null); @@ -134,7 +134,7 @@ public abstract class AbstractDirtiesContextTestExecutionListener extends Abstra Class testClass = testContext.getTestClass(); Assert.notNull(testClass, "The test class of the supplied TestContext must not be null"); - DirtiesContext dirtiesContext = MetaAnnotationUtils.findMergedAnnotation(testClass, DirtiesContext.class); + DirtiesContext dirtiesContext = TestContextAnnotationUtils.findMergedAnnotation(testClass, DirtiesContext.class); boolean classAnnotated = (dirtiesContext != null); ClassMode classMode = (classAnnotated ? dirtiesContext.classMode() : null); diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java index 21e3ca26b3..06fee14ec5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java @@ -44,12 +44,12 @@ import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.SmartContextLoader; import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestContextAnnotationUtils; +import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor; import org.springframework.test.context.TestContextBootstrapper; import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.TestExecutionListeners.MergeMode; -import org.springframework.test.util.MetaAnnotationUtils; -import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -116,7 +116,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot boolean usingDefaults = false; AnnotationDescriptor descriptor = - MetaAnnotationUtils.findAnnotationDescriptor(clazz, annotationType); + TestContextAnnotationUtils.findAnnotationDescriptor(clazz, annotationType); // Use defaults? if (descriptor == null) { @@ -257,12 +257,12 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot Class testClass = getBootstrapContext().getTestClass(); CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getCacheAwareContextLoaderDelegate(); - if (MetaAnnotationUtils.findAnnotationDescriptorForTypes( + if (TestContextAnnotationUtils.findAnnotationDescriptorForTypes( testClass, ContextConfiguration.class, ContextHierarchy.class) == null) { return buildDefaultMergedContextConfiguration(testClass, cacheAwareContextLoaderDelegate); } - if (MetaAnnotationUtils.findAnnotationDescriptor(testClass, ContextHierarchy.class) != null) { + if (TestContextAnnotationUtils.findAnnotationDescriptor(testClass, ContextHierarchy.class) != null) { Map> hierarchyMap = ContextLoaderUtils.buildContextHierarchyMap(testClass); MergedContextConfiguration parentConfig = null; diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java index fbd827dfbb..6dc8d39091 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java @@ -28,12 +28,12 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfilesResolver; -import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; -import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor; +import static org.springframework.test.context.TestContextAnnotationUtils.findAnnotationDescriptor; /** * Utility methods for working with {@link ActiveProfiles @ActiveProfiles} and diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java index c35ece1889..db082c4720 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java @@ -27,20 +27,19 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.SmartContextLoader; -import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; -import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor; +import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.context.TestContextAnnotationUtils.UntypedAnnotationDescriptor; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import static org.springframework.core.annotation.AnnotationUtils.getAnnotation; import static org.springframework.core.annotation.AnnotationUtils.isAnnotationDeclaredLocally; -import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor; -import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptorForTypes; +import static org.springframework.test.context.TestContextAnnotationUtils.findAnnotationDescriptor; +import static org.springframework.test.context.TestContextAnnotationUtils.findAnnotationDescriptorForTypes; /** * Utility methods for resolving {@link ContextConfigurationAttributes} from the @@ -245,18 +244,12 @@ abstract class ContextLoaderUtils { annotationType.getName(), testClass.getName())); List attributesList = new ArrayList<>(); - resolveContextConfigurationAttributes(attributesList, descriptor); - return attributesList; - } - - private static void resolveContextConfigurationAttributes(List attributesList, - @Nullable AnnotationDescriptor descriptor) { - - if (descriptor != null) { + while (descriptor != null) { convertContextConfigToConfigAttributesAndAddToList(descriptor.synthesizeAnnotation(), descriptor.getRootDeclaringClass(), attributesList); - resolveContextConfigurationAttributes(attributesList, descriptor.next()); + descriptor = descriptor.next(); } + return attributesList; } /** diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java b/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java index da65af7d78..4f1dd1f31e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -24,11 +24,11 @@ import org.apache.commons.logging.LogFactory; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfilesResolver; -import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor; +import static org.springframework.test.context.TestContextAnnotationUtils.findAnnotationDescriptor; /** * Default implementation of the {@link ActiveProfilesResolver} strategy that diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java index 0061b1c07a..df82bef780 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java @@ -25,7 +25,7 @@ import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.lang.Nullable; import org.springframework.test.context.TestConstructor; import org.springframework.test.context.TestConstructor.AutowireMode; -import org.springframework.test.util.MetaAnnotationUtils; +import org.springframework.test.context.TestContextAnnotationUtils; /** * Utility methods for working with {@link TestConstructor @TestConstructor}. @@ -134,7 +134,7 @@ public abstract class TestConstructorUtils { AutowireMode autowireMode = null; // Is the test class annotated with @TestConstructor? - TestConstructor testConstructor = MetaAnnotationUtils.findMergedAnnotation(testClass, TestConstructor.class); + TestConstructor testConstructor = TestContextAnnotationUtils.findMergedAnnotation(testClass, TestConstructor.class); if (testConstructor != null) { autowireMode = testConstructor.autowireMode(); } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java index 193d144195..8c75f86336 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java @@ -44,9 +44,9 @@ import org.springframework.core.env.PropertySources; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePropertySource; +import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.util.TestContextResourceUtils; -import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -308,7 +308,7 @@ public abstract class TestPropertySourceUtils { } // Declared on an enclosing class of an inner class? - if (MetaAnnotationUtils.searchEnclosingClass(clazz)) { + if (TestContextAnnotationUtils.searchEnclosingClass(clazz)) { findRepeatableAnnotations(clazz.getEnclosingClass(), annotationType, listOfLists, aggregateIndex); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java index f1b08d6294..ff134b3d9f 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java @@ -35,8 +35,8 @@ import org.springframework.lang.Nullable; import org.springframework.test.annotation.Commit; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.test.context.support.AbstractTestExecutionListener; -import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; @@ -167,7 +167,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis @Nullable private TransactionAttribute findTransactionAttributeInEnclosingClassHierarchy(Class clazz) { - if (MetaAnnotationUtils.searchEnclosingClass(clazz)) { + if (TestContextAnnotationUtils.searchEnclosingClass(clazz)) { return findTransactionAttribute(clazz.getEnclosingClass()); } return null; @@ -401,7 +401,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis */ protected final boolean isDefaultRollback(TestContext testContext) throws Exception { Class testClass = testContext.getTestClass(); - Rollback rollback = MetaAnnotationUtils.findMergedAnnotation(testClass, Rollback.class); + Rollback rollback = TestContextAnnotationUtils.findMergedAnnotation(testClass, Rollback.class); boolean rollbackPresent = (rollback != null); if (rollbackPresent) { diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java index 3087ca55e9..6db3a3351d 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java @@ -19,9 +19,9 @@ package org.springframework.test.context.web; import org.springframework.lang.Nullable; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.test.context.TestContextBootstrapper; import org.springframework.test.context.support.DefaultTestContextBootstrapper; -import org.springframework.test.util.MetaAnnotationUtils; /** * Web-specific implementation of the {@link TestContextBootstrapper} SPI. @@ -73,7 +73,7 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper { @Nullable private static WebAppConfiguration getWebAppConfiguration(Class testClass) { - return MetaAnnotationUtils.findMergedAnnotation(testClass, WebAppConfiguration.class); + return TestContextAnnotationUtils.findMergedAnnotation(testClass, WebAppConfiguration.class); } } diff --git a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java index 01beb1b6cb..6da1e6da85 100644 --- a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java @@ -17,36 +17,21 @@ package org.springframework.test.util; import java.lang.annotation.Annotation; -import java.util.Collections; import java.util.HashSet; import java.util.Set; -import java.util.function.Predicate; -import org.springframework.core.SpringProperties; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.annotation.MergedAnnotationCollectors; -import org.springframework.core.annotation.MergedAnnotationPredicates; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; -import org.springframework.core.annotation.RepeatableContainers; import org.springframework.core.style.ToStringCreator; import org.springframework.lang.Nullable; -import org.springframework.test.context.NestedTestConfiguration; -import org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ConcurrentLruCache; import org.springframework.util.ObjectUtils; /** * {@code MetaAnnotationUtils} is a collection of utility methods that complements * the standard support already available in {@link AnnotationUtils}. * - *

Mainly for internal use within the framework. - * *

Whereas {@code AnnotationUtils} provides utilities for getting or * finding an annotation, {@code MetaAnnotationUtils} goes a step further * by providing support for determining the root class on which an @@ -68,96 +53,12 @@ import org.springframework.util.ObjectUtils; * @since 4.0 * @see AnnotationUtils * @see AnnotationDescriptor + * @deprecated as of Spring Framework 5.3 in favor of + * {@link org.springframework.test.context.TestContextAnnotationUtils} */ +@Deprecated public abstract class MetaAnnotationUtils { - private static final ConcurrentLruCache, EnclosingConfiguration> cachedEnclosingConfigurationModes = - new ConcurrentLruCache<>(32, MetaAnnotationUtils::lookUpEnclosingConfiguration); - - - /** - * Find the first annotation of the specified {@code annotationType} within - * the annotation hierarchy above the supplied class, merge that - * annotation's attributes with matching attributes from annotations - * in lower levels of the annotation hierarchy, and synthesize the result back - * into an annotation of the specified {@code annotationType}. - *

In the context of this method, the term "above" means within the - * {@linkplain Class#getSuperclass() superclass} hierarchy or within the - * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of the - * supplied class. The enclosing class hierarchy will only be searched if - * appropriate. - *

{@link org.springframework.core.annotation.AliasFor @AliasFor} semantics - * are fully supported, both within a single annotation and within annotation - * hierarchies. - * @param clazz the class to look for annotations on - * @param annotationType the type of annotation to look for - * @return the merged, synthesized {@code Annotation}, or {@code null} if not found - * @since 5.3 - * @see AnnotatedElementUtils#findMergedAnnotation(java.lang.reflect.AnnotatedElement, Class) - * @see #findAnnotationDescriptor(Class, Class) - * @see #searchEnclosingClass(Class) - */ - @Nullable - public static T findMergedAnnotation(Class clazz, Class annotationType) { - return findMergedAnnotation(clazz, annotationType, MetaAnnotationUtils::searchEnclosingClass); - } - - @Nullable - private static T findMergedAnnotation(Class clazz, Class annotationType, - Predicate> searchEnclosingClass) { - - AnnotationDescriptor descriptor = - findAnnotationDescriptor(clazz, annotationType, searchEnclosingClass, new HashSet<>()); - return (descriptor != null ? descriptor.synthesizeAnnotation() : null); - } - - /** - * Get all repeatable annotations of the specified {@code annotationType} - * within the annotation hierarchy above the supplied class; and for - * each annotation found, merge that annotation's attributes with matching - * attributes from annotations in lower levels of the annotation hierarchy and - * synthesize the results back into an annotation of the specified {@code annotationType}. - *

This method will find {@link java.lang.annotation.Inherited @Inherited} - * annotations declared on superclasses if the supplied class does not have - * any local declarations of the repeatable annotation. If no inherited - * annotations are found, this method will search within the - * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of the - * supplied class. The enclosing class hierarchy will only be searched if - * appropriate. - *

The container type that holds the repeatable annotations will be looked up - * via {@link java.lang.annotation.Repeatable}. - *

{@link org.springframework.core.annotation.AliasFor @AliasFor} semantics - * are fully supported, both within a single annotation and within annotation - * hierarchies. - * @param clazz the class on which to search for annotations (never {@code null}) - * @param annotationType the annotation type to find (never {@code null}) - * @return the set of all merged repeatable annotations found, or an empty set - * if none were found - * @since 5.3 - * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(java.lang.reflect.AnnotatedElement, Class) - * @see #searchEnclosingClass(Class) - */ - public static Set getMergedRepeatableAnnotations( - Class clazz, Class annotationType) { - - // Present (via @Inherited semantics), directly present, or meta-present? - Set mergedAnnotations = MergedAnnotations.from(clazz, SearchStrategy.INHERITED_ANNOTATIONS) - .stream(annotationType) - .collect(MergedAnnotationCollectors.toAnnotationSet()); - - if (!mergedAnnotations.isEmpty()) { - return mergedAnnotations; - } - - // Declared on an enclosing class of an inner class? - if (searchEnclosingClass(clazz)) { - // Then mimic @Inherited semantics within the enclosing class hierarchy. - return getMergedRepeatableAnnotations(clazz.getEnclosingClass(), annotationType); - } - - return Collections.emptySet(); - } - /** * Find the {@link AnnotationDescriptor} for the supplied {@code annotationType} * on the supplied {@link Class}, traversing its annotations, interfaces, and @@ -186,8 +87,7 @@ public abstract class MetaAnnotationUtils { public static AnnotationDescriptor findAnnotationDescriptor( Class clazz, Class annotationType) { - return findAnnotationDescriptor(clazz, annotationType, MetaAnnotationUtils::searchEnclosingClass, - new HashSet<>()); + return findAnnotationDescriptor(clazz, new HashSet<>(), annotationType); } /** @@ -195,15 +95,14 @@ public abstract class MetaAnnotationUtils { * avoiding endless recursion by tracking which annotations have already been * visited. * @param clazz the class to look for annotations on - * @param annotationType the type of annotation to look for * @param visited the set of annotations that have already been visited + * @param annotationType the type of annotation to look for * @return the corresponding annotation descriptor if the annotation was found; * otherwise {@code null} */ @Nullable private static AnnotationDescriptor findAnnotationDescriptor( - @Nullable Class clazz, Class annotationType, Predicate> searchEnclosingClass, - Set visited) { + @Nullable Class clazz, Set visited, Class annotationType) { Assert.notNull(annotationType, "Annotation type must not be null"); if (clazz == null || Object.class == clazz) { @@ -215,13 +114,11 @@ public abstract class MetaAnnotationUtils { return new AnnotationDescriptor<>(clazz, clazz.getAnnotation(annotationType)); } - AnnotationDescriptor descriptor = null; - // Declared on a composed annotation (i.e., as a meta-annotation)? for (Annotation composedAnn : clazz.getDeclaredAnnotations()) { Class composedType = composedAnn.annotationType(); if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) { - descriptor = findAnnotationDescriptor(composedType, annotationType, searchEnclosingClass, visited); + AnnotationDescriptor descriptor = findAnnotationDescriptor(composedType, visited, annotationType); if (descriptor != null) { return new AnnotationDescriptor<>( clazz, descriptor.getDeclaringClass(), composedAnn, descriptor.getAnnotation()); @@ -231,7 +128,7 @@ public abstract class MetaAnnotationUtils { // Declared on an interface? for (Class ifc : clazz.getInterfaces()) { - descriptor = findAnnotationDescriptor(ifc, annotationType, searchEnclosingClass, visited); + AnnotationDescriptor descriptor = findAnnotationDescriptor(ifc, visited, annotationType); if (descriptor != null) { return new AnnotationDescriptor<>(clazz, descriptor.getDeclaringClass(), descriptor.getComposedAnnotation(), descriptor.getAnnotation()); @@ -239,20 +136,7 @@ public abstract class MetaAnnotationUtils { } // Declared on a superclass? - descriptor = findAnnotationDescriptor(clazz.getSuperclass(), annotationType, searchEnclosingClass, visited); - if (descriptor != null) { - return descriptor; - } - - // Declared on an enclosing class of an inner class? - if (searchEnclosingClass.test(clazz)) { - descriptor = findAnnotationDescriptor(clazz.getEnclosingClass(), annotationType, searchEnclosingClass, visited); - if (descriptor != null) { - return descriptor; - } - } - - return null; + return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType); } /** @@ -289,7 +173,7 @@ public abstract class MetaAnnotationUtils { public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes( Class clazz, Class... annotationTypes) { - return findAnnotationDescriptorForTypes(clazz, annotationTypes, new HashSet<>()); + return findAnnotationDescriptorForTypes(clazz, new HashSet<>(), annotationTypes); } /** @@ -297,14 +181,15 @@ public abstract class MetaAnnotationUtils { * avoiding endless recursion by tracking which annotations have already been * visited. * @param clazz the class to look for annotations on - * @param annotationTypes the types of annotations to look for * @param visited the set of annotations that have already been visited + * @param annotationTypes the types of annotations to look for * @return the corresponding annotation descriptor if one of the annotations * was found; otherwise {@code null} */ + @SuppressWarnings("unchecked") @Nullable private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(@Nullable Class clazz, - Class[] annotationTypes, Set visited) { + Set visited, Class... annotationTypes) { assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty"); if (clazz == null || Object.class == clazz) { @@ -314,7 +199,7 @@ public abstract class MetaAnnotationUtils { // Declared locally? for (Class annotationType : annotationTypes) { if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { - return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType), annotationTypes); + return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType)); } } @@ -322,81 +207,25 @@ public abstract class MetaAnnotationUtils { for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( - composedAnnotation.annotationType(), annotationTypes, visited); + composedAnnotation.annotationType(), visited, annotationTypes); if (descriptor != null) { return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), - composedAnnotation, descriptor.getAnnotation(), annotationTypes); + composedAnnotation, descriptor.getAnnotation()); } } } // Declared on an interface? for (Class ifc : clazz.getInterfaces()) { - UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(ifc, annotationTypes, visited); + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(ifc, visited, annotationTypes); if (descriptor != null) { return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), - descriptor.getComposedAnnotation(), descriptor.getAnnotation(), annotationTypes); + descriptor.getComposedAnnotation(), descriptor.getAnnotation()); } } // Declared on a superclass? - UntypedAnnotationDescriptor descriptor = - findAnnotationDescriptorForTypes(clazz.getSuperclass(), annotationTypes, visited); - if (descriptor != null) { - return descriptor; - } - - // Declared on an enclosing class of an inner class? - if (searchEnclosingClass(clazz)) { - descriptor = findAnnotationDescriptorForTypes(clazz.getEnclosingClass(), annotationTypes, visited); - if (descriptor != null) { - return descriptor; - } - } - - return null; - } - - /** - * Determine if annotations on the enclosing class of the supplied class - * should be searched by algorithms in {@link MetaAnnotationUtils}. - * @param clazz the class whose enclosing class should potentially be searched - * @return {@code true} if the supplied class is an inner class whose enclosing - * class should be searched - * @since 5.3 - * @see ClassUtils#isInnerClass(Class) - * @see NestedTestConfiguration @NestedTestConfiguration - */ - public static boolean searchEnclosingClass(Class clazz) { - return (ClassUtils.isInnerClass(clazz) && - getEnclosingConfiguration(clazz) == EnclosingConfiguration.INHERIT); - } - - /** - * Get the {@link EnclosingConfiguration} mode for the supplied class. - * @param clazz the class for which the enclosing configuration mode should - * be resolved - * @return the resolved enclosing configuration mode - * @since 5.3 - */ - private static EnclosingConfiguration getEnclosingConfiguration(Class clazz) { - return cachedEnclosingConfigurationModes.get(clazz); - } - - private static EnclosingConfiguration lookUpEnclosingConfiguration(Class clazz) { - // @NestedTestConfiguration should not be discovered on an enclosing class - // for a nested interface (which is always static), so our predicate simply - // ensures that the candidate class is an inner class. - Predicate> searchEnclosingClass = ClassUtils::isInnerClass; - NestedTestConfiguration nestedTestConfiguration = - findMergedAnnotation(clazz, NestedTestConfiguration.class, searchEnclosingClass); - return (nestedTestConfiguration != null ? nestedTestConfiguration.value() : getDefaultEnclosingConfigurationMode()); - } - - private static EnclosingConfiguration getDefaultEnclosingConfigurationMode() { - String value = SpringProperties.getProperty(NestedTestConfiguration.ENCLOSING_CONFIGURATION_PROPERTY_NAME); - EnclosingConfiguration enclosingConfigurationMode = EnclosingConfiguration.from(value); - return (enclosingConfigurationMode != null ? enclosingConfigurationMode : EnclosingConfiguration.INHERIT); + return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes); } private static void assertNonEmptyAnnotationTypeArray(Class[] annotationTypes, String message) { @@ -532,56 +361,6 @@ public abstract class MetaAnnotationUtils { return (this.composedAnnotation != null ? this.composedAnnotation.annotationType() : null); } - /** - * Find the next {@link AnnotationDescriptor} for the specified - * {@linkplain #getAnnotationType() annotation type} in the hierarchy - * above the {@linkplain #getRootDeclaringClass() root declaring class} - * of this descriptor. - *

If a corresponding annotation is found in the superclass hierarchy - * of the root declaring class, that will be returned. Otherwise, an - * attempt will be made to find a corresponding annotation in the - * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of - * the root declaring class if - * {@linkplain MetaAnnotationUtils#searchEnclosingClass appropriate}. - * @return the next corresponding annotation descriptor if the annotation - * was found; otherwise {@code null} - * @since 5.3 - */ - @Nullable - @SuppressWarnings("unchecked") - public AnnotationDescriptor next() { - Class annotationType = (Class) getAnnotationType(); - // Declared on a superclass? - AnnotationDescriptor descriptor = - findAnnotationDescriptor(getRootDeclaringClass().getSuperclass(), annotationType); - // Declared on an enclosing class of an inner class? - if (descriptor == null && searchEnclosingClass(getRootDeclaringClass())) { - descriptor = findAnnotationDescriptor(getRootDeclaringClass().getEnclosingClass(), annotationType); - } - return descriptor; - } - - /** - * Find all annotations of the specified - * {@linkplain #getAnnotationType() annotation type} that are present or - * meta-present on the {@linkplain #getRootDeclaringClass() root declaring - * class} of this descriptor. - * @return the set of all merged, synthesized {@code Annotations} found, - * or an empty set if none were found - * @since 5.3 - */ - @SuppressWarnings("unchecked") - public Set findAllLocalMergedAnnotations() { - SearchStrategy searchStrategy = - (getEnclosingConfiguration(getRootDeclaringClass()) == EnclosingConfiguration.INHERIT ? - SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES : - SearchStrategy.TYPE_HIERARCHY); - return MergedAnnotations.from(getRootDeclaringClass(), searchStrategy, RepeatableContainers.none()) - .stream((Class) getAnnotationType()) - .filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex)) - .collect(MergedAnnotationCollectors.toAnnotationSet()); - } - /** * Provide a textual representation of this {@code AnnotationDescriptor}. */ @@ -604,48 +383,20 @@ public abstract class MetaAnnotationUtils { */ public static class UntypedAnnotationDescriptor extends AnnotationDescriptor { - @Nullable - private final Class[] annotationTypes; - - /** - * Create a new {@plain UntypedAnnotationDescriptor}. - * @deprecated As of Spring Framework 5.3, in favor of - * {@link UntypedAnnotationDescriptor#UntypedAnnotationDescriptor(Class, Annotation, Class[])} - */ - @Deprecated public UntypedAnnotationDescriptor(Class rootDeclaringClass, Annotation annotation) { - this(rootDeclaringClass, annotation, null); + this(rootDeclaringClass, rootDeclaringClass, null, annotation); } - public UntypedAnnotationDescriptor(Class rootDeclaringClass, Annotation annotation, - @Nullable Class[] annotationTypes) { - - this(rootDeclaringClass, rootDeclaringClass, null, annotation, annotationTypes); - } - - /** - * Create a new {@plain UntypedAnnotationDescriptor}. - * @deprecated As of Spring Framework 5.3, in favor of - * {@link UntypedAnnotationDescriptor#UntypedAnnotationDescriptor(Class, Class, Annotation, Annotation, Class[])} - */ - @Deprecated public UntypedAnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, @Nullable Annotation composedAnnotation, Annotation annotation) { - this(rootDeclaringClass, declaringClass, composedAnnotation, annotation, null); - } - - public UntypedAnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, - @Nullable Annotation composedAnnotation, Annotation annotation, - @Nullable Class[] annotationTypes) { - super(rootDeclaringClass, declaringClass, composedAnnotation, annotation); - this.annotationTypes = annotationTypes; } /** * Throws an {@link UnsupportedOperationException} since the type of annotation - * represented by an {@code UntypedAnnotationDescriptor} is unknown. + * represented by the {@link #getAnnotationAttributes AnnotationAttributes} in + * an {@code UntypedAnnotationDescriptor} is unknown. * @since 4.2 */ @Override @@ -653,52 +404,6 @@ public abstract class MetaAnnotationUtils { throw new UnsupportedOperationException( "synthesizeAnnotation() is unsupported in UntypedAnnotationDescriptor"); } - - /** - * Find the next {@link UntypedAnnotationDescriptor} for the specified - * annotation types in the hierarchy above the - * {@linkplain #getRootDeclaringClass() root declaring class} of this - * descriptor. - *

If one of the corresponding annotations is found in the superclass - * hierarchy of the root declaring class, that will be returned. Otherwise, - * an attempt will be made to find a corresponding annotation in the - * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of - * the root declaring class if - * {@linkplain MetaAnnotationUtils#searchEnclosingClass appropriate}. - * @return the next corresponding annotation descriptor if one of the - * annotations was found; otherwise {@code null} - * @since 5.3 - * @see AnnotationDescriptor#next() - */ - @Override - @Nullable - public UntypedAnnotationDescriptor next() { - if (ObjectUtils.isEmpty(this.annotationTypes)) { - throw new UnsupportedOperationException( - "next() is unsupported if UntypedAnnotationDescriptor is instantiated without 'annotationTypes'"); - } - - // Declared on a superclass? - UntypedAnnotationDescriptor descriptor = - findAnnotationDescriptorForTypes(getRootDeclaringClass().getSuperclass(), this.annotationTypes); - // Declared on an enclosing class of an inner class? - if (descriptor == null && searchEnclosingClass(getRootDeclaringClass())) { - descriptor = findAnnotationDescriptorForTypes(getRootDeclaringClass().getEnclosingClass(), this.annotationTypes); - } - return descriptor; - } - - /** - * Throws an {@link UnsupportedOperationException} since the type of annotation - * represented by an {@code UntypedAnnotationDescriptor} is unknown. - * @since 5.3 - */ - @Override - public Set findAllLocalMergedAnnotations() { - throw new UnsupportedOperationException( - "findAllLocalMergedAnnotations() is unsupported in UntypedAnnotationDescriptor"); - } - } } diff --git a/spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java similarity index 92% rename from spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java rename to spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java index 6712df08bc..a38664f247 100644 --- a/spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.util; +package org.springframework.test.context; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -22,21 +22,20 @@ import java.lang.annotation.RetentionPolicy; import org.junit.jupiter.api.Test; import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor; +import static org.springframework.test.context.TestContextAnnotationUtils.findAnnotationDescriptor; /** - * Unit tests for {@link MetaAnnotationUtils} that verify support for overridden - * meta-annotation attributes. + * Unit tests for {@link TestContextAnnotationUtils} that verify support for + * overridden meta-annotation attributes. * *

See SPR-10181. * * @author Sam Brannen * @since 4.0 - * @see MetaAnnotationUtilsTests + * @see TestContextAnnotationUtilsTests */ class OverriddenMetaAnnotationAttributesTests { diff --git a/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextAnnotationUtilsTests.java similarity index 97% rename from spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java rename to spring-test/src/test/java/org/springframework/test/context/TestContextAnnotationUtilsTests.java index ea1f512442..5f4f0d5e5f 100644 --- a/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/TestContextAnnotationUtilsTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.util; +package org.springframework.test.context; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; @@ -32,26 +32,24 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.NestedTestConfiguration; -import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; -import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor; +import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.context.TestContextAnnotationUtils.UntypedAnnotationDescriptor; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; -import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor; -import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptorForTypes; -import static org.springframework.test.util.MetaAnnotationUtils.searchEnclosingClass; +import static org.springframework.test.context.TestContextAnnotationUtils.findAnnotationDescriptor; +import static org.springframework.test.context.TestContextAnnotationUtils.findAnnotationDescriptorForTypes; +import static org.springframework.test.context.TestContextAnnotationUtils.searchEnclosingClass; /** - * Unit tests for {@link MetaAnnotationUtils}. + * Unit tests for {@link TestContextAnnotationUtils}. * * @author Sam Brannen * @since 4.0 * @see OverriddenMetaAnnotationAttributesTests */ -class MetaAnnotationUtilsTests { +class TestContextAnnotationUtilsTests { @Nested @DisplayName("searchEnclosingClass() tests") @@ -398,7 +396,7 @@ class MetaAnnotationUtilsTests { assertThat(descriptor.getRootDeclaringClass()).isEqualTo(startClass); assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); assertThat(((ContextConfiguration) descriptor.getAnnotation()).value()).isEqualTo(new Class[] {}); - assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).isEqualTo(new Class[] {MetaAnnotationUtilsTests.class}); + assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).isEqualTo(new Class[] {TestContextAnnotationUtilsTests.class}); assertThat(descriptor.getComposedAnnotation()).isNotNull(); assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaConfig.class); } @@ -619,7 +617,7 @@ class MetaAnnotationUtilsTests { class MetaConfigWithDefaultAttributesTestCase { } - @MetaConfig(classes = MetaAnnotationUtilsTests.class) + @MetaConfig(classes = TestContextAnnotationUtilsTests.class) class MetaConfigWithOverriddenAttributesTestCase { }