Support arbitrary meta-annotation levels in the TCF

Prior to this commit, the findAnnotationDescriptor() and
findAnnotationDescriptorForTypes() methods in MetaAnnotationUtils only
supported a single level of meta-annotations. In particular, this kept
the following annotations from being used as meta-annotations on
meta-annotations:

- @ContextConfiguration
- @ContextHierarchy
- @ActiveProfiles
- @TestExecutionListeners

This commit alters the search algorithms used in MetaAnnotationUtils so
that arbitrary levels of meta-annotations are now supported for the
aforementioned test-related annotations.

Issue: SPR-11470
This commit is contained in:
Sam Brannen
2014-02-23 01:05:15 +01:00
parent 2c8f25a14c
commit f8950960f2
9 changed files with 542 additions and 70 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -17,6 +17,8 @@
package org.springframework.test.context;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
@@ -29,20 +31,22 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
/**
* {@code MetaAnnotationUtils} is a collection of utility methods that complements
* support already available in {@link AnnotationUtils}.
* the standard support already available in {@link AnnotationUtils}.
*
* <p>Whereas {@code AnnotationUtils} only provides utilities for <em>getting</em>
* or <em>finding</em> an annotation, {@code MetaAnnotationUtils} provides
* additional support for determining the <em>root class</em> on which an
* <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
* annotation is declared, either directly or 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 in the <em>Spring TestContext Framework</em> in order to be able to
* support class hierarchy traversals for <em>inherited</em> annotations such as
* 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}.
* {@link ActiveProfiles @ActiveProfiles} which offer support for merging and
* overriding various <em>inherited</em> annotation attributes (e.g., {@link
* ContextConfiguration#inheritLocations}).
*
* @author Sam Brannen
* @since 4.0
@@ -57,7 +61,7 @@ abstract class MetaAnnotationUtils {
/**
* Find the {@link AnnotationDescriptor} for the supplied {@code annotationType}
* from the supplied {@link Class}, traversing its annotations and superclasses
* on the supplied {@link Class}, traversing its annotations and superclasses
* if no annotation can be found on the given class itself.
*
* <p>This method explicitly handles class-level annotations which are not
@@ -66,23 +70,22 @@ abstract class MetaAnnotationUtils {
*
* <p>The algorithm operates as follows:
* <ol>
* <li>Search for a local declaration of the annotation on the given class
* and return a corresponding {@code AnnotationDescriptor} if found.
* <li>Search through all annotations that the given class declares,
* returning an {@code AnnotationDescriptor} for the first matching
* candidate, if any.
* <li>Proceed with introspection of the superclass hierarchy of the given
* class by returning to step #1 with the superclass as the class to look
* for annotations on.
* <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 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 or
* superclass as the class to look for annotations on.
*
* <p>If the supplied {@code clazz} is an interface, only the interface
* itself will be checked; the inheritance hierarchy for interfaces will not
* be traversed.
*
* @param clazz the class to look for annotations on
* @param annotationType the annotation class to look for, both locally and
* as a meta-annotation
* @param annotationType the type of annotation to look for
* @return the corresponding annotation descriptor if the annotation was found;
* otherwise {@code null}
* @see AnnotationUtils#findAnnotationDeclaringClass(Class, Class)
@@ -90,6 +93,22 @@ abstract class MetaAnnotationUtils {
*/
public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
Class<T> annotationType) {
return findAnnotationDescriptor(clazz, new HashSet<Annotation>(), annotationType);
}
/**
* 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 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}
*/
private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
Set<Annotation> visited, Class<T> annotationType) {
Assert.notNull(annotationType, "Annotation type must not be null");
@@ -103,29 +122,30 @@ abstract class MetaAnnotationUtils {
}
// Declared on a composed annotation (i.e., as a meta-annotation)?
if (!Annotation.class.isAssignableFrom(clazz)) {
for (Annotation composedAnnotation : clazz.getAnnotations()) {
T annotation = composedAnnotation.annotationType().getAnnotation(annotationType);
if (annotation != null) {
return new AnnotationDescriptor<T>(clazz, composedAnnotation, annotation);
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
if (visited.add(composedAnnotation)) {
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(),
visited, annotationType);
if (descriptor != null) {
return new AnnotationDescriptor<T>(clazz, descriptor.getDeclaringClass(), composedAnnotation,
descriptor.getAnnotation());
}
}
}
// Declared on a superclass?
return findAnnotationDescriptor(clazz.getSuperclass(), annotationType);
return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType);
}
/**
* 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}, or {@code null} if none of the
* specified annotation types could be found.
* specified {@code annotationTypes}.
*
* <p>This method traverses the annotations 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>.
@@ -135,21 +155,20 @@ abstract class MetaAnnotationUtils {
* <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>Search through all annotations that the given class declares,
* returning an {@code UntypedAnnotationDescriptor} for the first matching
* candidate, if any.
* <li>Proceed with introspection of the superclass hierarchy of the given
* class by returning to step #1 with the superclass as the class to look
* for annotations on.
* <li>Recursively search through all annotations that the given class declares.
* <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 or
* superclass as the class to look for annotations on.
*
* <p>If the supplied {@code clazz} is an interface, only the interface
* itself will be checked; the inheritance hierarchy for interfaces will not
* be traversed.
*
* @param clazz the class to look for annotations on
* @param annotationTypes the types of annotations to look for, both locally
* and as meta-annotations
* @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 AnnotationUtils#findAnnotationDeclaringClassForTypes(java.util.List, Class)
@@ -158,6 +177,23 @@ abstract class MetaAnnotationUtils {
@SuppressWarnings("unchecked")
public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class<?> clazz,
Class<? extends Annotation>... annotationTypes) {
return findAnnotationDescriptorForTypes(clazz, new HashSet<Annotation>(), annotationTypes);
}
/**
* 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 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")
private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class<?> clazz,
Set<Annotation> visited, Class<? extends Annotation>... annotationTypes) {
assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty");
@@ -173,19 +209,19 @@ abstract class MetaAnnotationUtils {
}
// Declared on a composed annotation (i.e., as a meta-annotation)?
if (!Annotation.class.isAssignableFrom(clazz)) {
for (Annotation composedAnnotation : clazz.getAnnotations()) {
for (Class<? extends Annotation> annotationType : annotationTypes) {
Annotation annotation = composedAnnotation.annotationType().getAnnotation(annotationType);
if (annotation != null) {
return new UntypedAnnotationDescriptor(clazz, composedAnnotation, annotation);
}
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
if (visited.add(composedAnnotation)) {
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(
composedAnnotation.annotationType(), visited, annotationTypes);
if (descriptor != null) {
return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), composedAnnotation,
descriptor.getAnnotation());
}
}
}
// Declared on a superclass?
return findAnnotationDescriptorForTypes(clazz.getSuperclass(), annotationTypes);
return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes);
}
@@ -254,16 +290,16 @@ abstract class MetaAnnotationUtils {
public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
this(rootDeclaringClass, null, annotation);
this(rootDeclaringClass, rootDeclaringClass, null, annotation);
}
public AnnotationDescriptor(Class<?> rootDeclaringClass, Annotation composedAnnotation, T annotation) {
public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
Annotation composedAnnotation, T annotation) {
Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null");
Assert.notNull(annotation, "annotation must not be null");
this.rootDeclaringClass = rootDeclaringClass;
this.declaringClass = (composedAnnotation != null) ? composedAnnotation.annotationType()
: rootDeclaringClass;
this.declaringClass = declaringClass;
this.composedAnnotation = composedAnnotation;
this.annotation = annotation;
this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass,
@@ -322,12 +358,13 @@ abstract class MetaAnnotationUtils {
*/
public static class UntypedAnnotationDescriptor extends AnnotationDescriptor<Annotation> {
public UntypedAnnotationDescriptor(Class<?> declaringClass, Annotation annotation) {
super(declaringClass, annotation);
public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Annotation annotation) {
this(rootDeclaringClass, rootDeclaringClass, null, annotation);
}
public UntypedAnnotationDescriptor(Class<?> declaringClass, Annotation composedAnnotation, Annotation annotation) {
super(declaringClass, composedAnnotation, annotation);
public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
Annotation composedAnnotation, Annotation annotation) {
super(rootDeclaringClass, declaringClass, composedAnnotation, annotation);
}
}