Introduce TestContextAnnotationUtils to avoid package cycles

This commit introduces TestContextAnnotationUtils as a replacement for
MetaAnnotationUtils, with dedicated support for honoring the new
@NestedTestConfiguration annotation and related annotation search
semantics.

MetaAnnotationUtils has been reverted to its previous scope and is now
deprecated.

See gh-19930
This commit is contained in:
Sam Brannen 2020-10-23 14:24:33 +02:00
parent 1ec6843913
commit 8d86d61f9f
16 changed files with 740 additions and 382 deletions

View File

@ -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<BootstrapWith> annotations = new LinkedHashSet<>();
AnnotationDescriptor<BootstrapWith> 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());

View File

@ -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}.
*
* <p>Mainly for internal use within the <em>Spring TestContext Framework</em>.
*
* <p>Whereas {@code AnnotationUtils} and {@code AnnotatedElementUtils} provide
* utilities for <em>getting</em> or <em>finding</em> annotations,
* {@code TestContextAnnotationUtils} goes a step further by providing support
* for determining the <em>root class</em> on which an annotation is declared,
* either directly or indirectly via a <em>composed annotation</em>. This
* additional information is encapsulated in an {@link AnnotationDescriptor}.
*
* <p>The additional information provided by an {@code AnnotationDescriptor} is
* required by the <em>Spring TestContext Framework</em> 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 <em>inherited</em> annotation attributes &mdash; 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<Class<?>, EnclosingConfiguration> cachedEnclosingConfigurationModes =
new ConcurrentLruCache<>(32, TestContextAnnotationUtils::lookUpEnclosingConfiguration);
/**
* Find the first annotation of the specified {@code annotationType} within
* the annotation hierarchy <em>above</em> the supplied class, merge that
* annotation's attributes with <em>matching</em> attributes from annotations
* in lower levels of the annotation hierarchy, and synthesize the result back
* into an annotation of the specified {@code annotationType}.
* <p>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.
* <p>{@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 extends Annotation> T findMergedAnnotation(Class<?> clazz, Class<T> annotationType) {
return findMergedAnnotation(clazz, annotationType, TestContextAnnotationUtils::searchEnclosingClass);
}
@Nullable
private static <T extends Annotation> T findMergedAnnotation(Class<?> clazz, Class<T> annotationType,
Predicate<Class<?>> searchEnclosingClass) {
AnnotationDescriptor<T> descriptor =
findAnnotationDescriptor(clazz, annotationType, searchEnclosingClass, new HashSet<>());
return (descriptor != null ? descriptor.synthesizeAnnotation() : null);
}
/**
* Get all <em>repeatable annotations</em> of the specified {@code annotationType}
* within the annotation hierarchy <em>above</em> the supplied class; and for
* each annotation found, merge that annotation's attributes with <em>matching</em>
* attributes from annotations in lower levels of the annotation hierarchy and
* synthesize the results back into an annotation of the specified {@code annotationType}.
* <p>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.
* <p>The container type that holds the repeatable annotations will be looked up
* via {@link java.lang.annotation.Repeatable}.
* <p>{@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 <T extends Annotation> Set<T> getMergedRepeatableAnnotations(
Class<?> clazz, Class<T> annotationType) {
// Present (via @Inherited semantics), directly present, or meta-present?
Set<T> 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.
* <p>This method explicitly handles class-level annotations which are not
* declared as {@linkplain java.lang.annotation.Inherited inherited} <em>as
* well as meta-annotations</em>.
* <p>The algorithm operates as follows:
* <ol>
* <li>Search for the annotation on the given class and return a corresponding
* {@code AnnotationDescriptor} if found.
* <li>Recursively search through all annotations that the given class declares.
* <li>Recursively search through all interfaces implemented by the given class.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>In this context, the term <em>recursively</em> 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 <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
Class<?> clazz, Class<T> 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
* <em>visited</em>.
* @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 <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
@Nullable Class<?> clazz, Class<T> annotationType, Predicate<Class<?>> searchEnclosingClass,
Set<Annotation> 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<T> descriptor = null;
// Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnn : clazz.getDeclaredAnnotations()) {
Class<? extends Annotation> 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}.
* <p>This method traverses the annotations, interfaces, and superclasses
* of the specified {@code clazz} if no annotation can be found on the given
* class itself.
* <p>This method explicitly handles class-level annotations which are not
* declared as {@linkplain java.lang.annotation.Inherited inherited} <em>as
* well as meta-annotations</em>.
* <p>The algorithm operates as follows:
* <ol>
* <li>Search for a local declaration of one of the annotation types on
* the given class and return a corresponding {@code UntypedAnnotationDescriptor}
* if found.
* <li>Recursively search through all annotations that the given class declares.
* <li>Recursively search through all interfaces implemented by the given class.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>In this context, the term <em>recursively</em> 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<? extends Annotation>... 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
* <em>visited</em>.
* @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<? extends Annotation>[] annotationTypes, Set<Annotation> visited) {
assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty");
if (clazz == null || Object.class == clazz) {
return null;
}
// Declared locally?
for (Class<? extends Annotation> 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<Class<?>> 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 <em>declared</em>
* as well as the actual {@linkplain #getAnnotation() annotation} instance.
* <p>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 <em>root declaring class</em> is
* not directly annotated with the annotation but rather indirectly via the
* composed annotation.
* <p>Given the following example, if we are searching for the {@code @Transactional}
* annotation <em>on</em> the {@code TransactionalTests} class, then the
* properties of the {@code AnnotationDescriptor} would be as follows.
* <ul>
* <li>rootDeclaringClass: {@code TransactionalTests} class object</li>
* <li>declaringClass: {@code TransactionalTests} class object</li>
* <li>composedAnnotation: {@code null}</li>
* <li>annotation: instance of the {@code Transactional} annotation</li>
* </ul>
* <p><pre style="code">
* &#064;Transactional
* &#064;ContextConfiguration({"/test-datasource.xml", "/repository-config.xml"})
* public class TransactionalTests { }
* </pre>
* <p>Given the following example, if we are searching for the {@code @Transactional}
* annotation <em>on</em> the {@code UserRepositoryTests} class, then the
* properties of the {@code AnnotationDescriptor} would be as follows.
* <ul>
* <li>rootDeclaringClass: {@code UserRepositoryTests} class object</li>
* <li>declaringClass: {@code RepositoryTests} class object</li>
* <li>composedAnnotation: instance of the {@code RepositoryTests} annotation</li>
* <li>annotation: instance of the {@code Transactional} annotation</li>
* </ul>
* <p><pre style="code">
* &#064;Transactional
* &#064;ContextConfiguration({"/test-datasource.xml", "/repository-config.xml"})
* &#064;Retention(RetentionPolicy.RUNTIME)
* public &#064;interface RepositoryTests { }
*
* &#064;RepositoryTests
* public class UserRepositoryTests { }
* </pre>
*
* @param <T> the annotation type
*/
public static class AnnotationDescriptor<T extends Annotation> {
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<T> getAnnotationType() {
return (Class<T>) this.annotation.annotationType();
}
public AnnotationAttributes getAnnotationAttributes() {
return this.annotationAttributes;
}
@Nullable
public Annotation getComposedAnnotation() {
return this.composedAnnotation;
}
@Nullable
public Class<? extends Annotation> 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.
* <p>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<T> next() {
// Declared on a superclass?
AnnotationDescriptor<T> 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 <strong>all</strong> 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<T> 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();
}
}
/**
* <em>Untyped</em> 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<Annotation> {
private final Class<? extends Annotation>[] annotationTypes;
public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Annotation annotation,
Class<? extends Annotation>[] annotationTypes) {
this(rootDeclaringClass, rootDeclaringClass, null, annotation, annotationTypes);
}
public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
@Nullable Annotation composedAnnotation, Annotation annotation,
Class<? extends Annotation>[] 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.
* <p>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<Annotation> findAllLocalMergedAnnotations() {
throw new UnsupportedOperationException(
"findAllLocalMergedAnnotations() is unsupported in UntypedAnnotationDescriptor");
}
}
}

View File

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

View File

@ -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<Sql> getSqlAnnotationsFor(Class<?> clazz) {
return MetaAnnotationUtils.getMergedRepeatableAnnotations(clazz, Sql.class);
return TestContextAnnotationUtils.getMergedRepeatableAnnotations(clazz, Sql.class);
}
/**

View File

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

View File

@ -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<TestExecutionListeners> 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<String, List<ContextConfigurationAttributes>> hierarchyMap =
ContextLoaderUtils.buildContextHierarchyMap(testClass);
MergedContextConfiguration parentConfig = null;

View File

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

View File

@ -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<ContextConfigurationAttributes> attributesList = new ArrayList<>();
resolveContextConfigurationAttributes(attributesList, descriptor);
return attributesList;
}
private static void resolveContextConfigurationAttributes(List<ContextConfigurationAttributes> attributesList,
@Nullable AnnotationDescriptor<ContextConfiguration> descriptor) {
if (descriptor != null) {
while (descriptor != null) {
convertContextConfigToConfigAttributesAndAddToList(descriptor.synthesizeAnnotation(),
descriptor.getRootDeclaringClass(), attributesList);
resolveContextConfigurationAttributes(attributesList, descriptor.next());
descriptor = descriptor.next();
}
return attributesList;
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}.
*
* <p>Mainly for internal use within the framework.
*
* <p>Whereas {@code AnnotationUtils} provides utilities for <em>getting</em> or
* <em>finding</em> an annotation, {@code MetaAnnotationUtils} goes a step further
* by providing support for determining the <em>root class</em> 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<Class<?>, EnclosingConfiguration> cachedEnclosingConfigurationModes =
new ConcurrentLruCache<>(32, MetaAnnotationUtils::lookUpEnclosingConfiguration);
/**
* Find the first annotation of the specified {@code annotationType} within
* the annotation hierarchy <em>above</em> the supplied class, merge that
* annotation's attributes with <em>matching</em> attributes from annotations
* in lower levels of the annotation hierarchy, and synthesize the result back
* into an annotation of the specified {@code annotationType}.
* <p>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.
* <p>{@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 extends Annotation> T findMergedAnnotation(Class<?> clazz, Class<T> annotationType) {
return findMergedAnnotation(clazz, annotationType, MetaAnnotationUtils::searchEnclosingClass);
}
@Nullable
private static <T extends Annotation> T findMergedAnnotation(Class<?> clazz, Class<T> annotationType,
Predicate<Class<?>> searchEnclosingClass) {
AnnotationDescriptor<T> descriptor =
findAnnotationDescriptor(clazz, annotationType, searchEnclosingClass, new HashSet<>());
return (descriptor != null ? descriptor.synthesizeAnnotation() : null);
}
/**
* Get all <em>repeatable annotations</em> of the specified {@code annotationType}
* within the annotation hierarchy <em>above</em> the supplied class; and for
* each annotation found, merge that annotation's attributes with <em>matching</em>
* attributes from annotations in lower levels of the annotation hierarchy and
* synthesize the results back into an annotation of the specified {@code annotationType}.
* <p>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.
* <p>The container type that holds the repeatable annotations will be looked up
* via {@link java.lang.annotation.Repeatable}.
* <p>{@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 <T extends Annotation> Set<T> getMergedRepeatableAnnotations(
Class<?> clazz, Class<T> annotationType) {
// Present (via @Inherited semantics), directly present, or meta-present?
Set<T> 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 <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
Class<?> clazz, Class<T> 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
* <em>visited</em>.
* @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 <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
@Nullable Class<?> clazz, Class<T> annotationType, Predicate<Class<?>> searchEnclosingClass,
Set<Annotation> visited) {
@Nullable Class<?> clazz, Set<Annotation> visited, Class<T> 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<T> descriptor = null;
// Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnn : clazz.getDeclaredAnnotations()) {
Class<? extends Annotation> composedType = composedAnn.annotationType();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) {
descriptor = findAnnotationDescriptor(composedType, annotationType, searchEnclosingClass, visited);
AnnotationDescriptor<T> 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<T> 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<? extends Annotation>... 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
* <em>visited</em>.
* @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<? extends Annotation>[] annotationTypes, Set<Annotation> visited) {
Set<Annotation> visited, Class<? extends Annotation>... 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<? extends Annotation> 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<Class<?>> 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.
* <p>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<T> next() {
Class<T> annotationType = (Class<T>) getAnnotationType();
// Declared on a superclass?
AnnotationDescriptor<T> 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 <strong>all</strong> 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<T> 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<T>) 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<Annotation> {
@Nullable
private final Class<? extends Annotation>[] 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<? extends Annotation>[] 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<? extends Annotation>[] 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.
* <p>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<Annotation> findAllLocalMergedAnnotations() {
throw new UnsupportedOperationException(
"findAllLocalMergedAnnotations() is unsupported in UntypedAnnotationDescriptor");
}
}
}

View File

@ -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.
*
* <p>See <a href="https://jira.spring.io/browse/SPR-10181">SPR-10181</a>.
*
* @author Sam Brannen
* @since 4.0
* @see MetaAnnotationUtilsTests
* @see TestContextAnnotationUtilsTests
*/
class OverriddenMetaAnnotationAttributesTests {

View File

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