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:
parent
1ec6843913
commit
8d86d61f9f
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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 — 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">
|
||||
* @Transactional
|
||||
* @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">
|
||||
* @Transactional
|
||||
* @ContextConfiguration({"/test-datasource.xml", "/repository-config.xml"})
|
||||
* @Retention(RetentionPolicy.RUNTIME)
|
||||
* public @interface RepositoryTests { }
|
||||
*
|
||||
* @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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
@ -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 {
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue