Skip to content

Commit f895096

Browse files
committed
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
1 parent 2c8f25a commit f895096

File tree

9 files changed

+542
-70
lines changed

9 files changed

+542
-70
lines changed

spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java

Lines changed: 89 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
1717
package org.springframework.test.context;
1818

1919
import java.lang.annotation.Annotation;
20+
import java.util.HashSet;
21+
import java.util.Set;
2022

2123
import org.springframework.core.annotation.AnnotatedElementUtils;
2224
import org.springframework.core.annotation.AnnotationAttributes;
@@ -29,20 +31,22 @@
2931

3032
/**
3133
* {@code MetaAnnotationUtils} is a collection of utility methods that complements
32-
* support already available in {@link AnnotationUtils}.
34+
* the standard support already available in {@link AnnotationUtils}.
3335
*
34-
* <p>Whereas {@code AnnotationUtils} only provides utilities for <em>getting</em>
35-
* or <em>finding</em> an annotation, {@code MetaAnnotationUtils} provides
36-
* additional support for determining the <em>root class</em> on which an
36+
* <p>Whereas {@code AnnotationUtils} provides utilities for <em>getting</em> or
37+
* <em>finding</em> an annotation, {@code MetaAnnotationUtils} goes a step further
38+
* by providing support for determining the <em>root class</em> on which an
3739
* annotation is declared, either directly or via a <em>composed annotation</em>.
3840
* This additional information is encapsulated in an {@link AnnotationDescriptor}.
3941
*
4042
* <p>The additional information provided by an {@code AnnotationDescriptor} is
41-
* required in the <em>Spring TestContext Framework</em> in order to be able to
42-
* support class hierarchy traversals for <em>inherited</em> annotations such as
43+
* required by the <em>Spring TestContext Framework</em> in order to be able to
44+
* support class hierarchy traversals for annotations such as
4345
* {@link ContextConfiguration @ContextConfiguration},
4446
* {@link TestExecutionListeners @TestExecutionListeners}, and
45-
* {@link ActiveProfiles @ActiveProfiles}.
47+
* {@link ActiveProfiles @ActiveProfiles} which offer support for merging and
48+
* overriding various <em>inherited</em> annotation attributes (e.g., {@link
49+
* ContextConfiguration#inheritLocations}).
4650
*
4751
* @author Sam Brannen
4852
* @since 4.0
@@ -57,7 +61,7 @@ private MetaAnnotationUtils() {
5761

5862
/**
5963
* Find the {@link AnnotationDescriptor} for the supplied {@code annotationType}
60-
* from the supplied {@link Class}, traversing its annotations and superclasses
64+
* on the supplied {@link Class}, traversing its annotations and superclasses
6165
* if no annotation can be found on the given class itself.
6266
*
6367
* <p>This method explicitly handles class-level annotations which are not
@@ -66,30 +70,45 @@ private MetaAnnotationUtils() {
6670
*
6771
* <p>The algorithm operates as follows:
6872
* <ol>
69-
* <li>Search for a local declaration of the annotation on the given class
70-
* and return a corresponding {@code AnnotationDescriptor} if found.
71-
* <li>Search through all annotations that the given class declares,
72-
* returning an {@code AnnotationDescriptor} for the first matching
73-
* candidate, if any.
74-
* <li>Proceed with introspection of the superclass hierarchy of the given
75-
* class by returning to step #1 with the superclass as the class to look
76-
* for annotations on.
73+
* <li>Search for the annotation on the given class and return a corresponding
74+
* {@code AnnotationDescriptor} if found.
75+
* <li>Recursively search through all annotations that the given class declares.
76+
* <li>Recursively search through the superclass hierarchy of the given class.
7777
* </ol>
7878
*
79+
* <p>In this context, the term <em>recursively</em> means that the search
80+
* process continues by returning to step #1 with the current annotation or
81+
* superclass as the class to look for annotations on.
82+
*
7983
* <p>If the supplied {@code clazz} is an interface, only the interface
8084
* itself will be checked; the inheritance hierarchy for interfaces will not
8185
* be traversed.
8286
*
8387
* @param clazz the class to look for annotations on
84-
* @param annotationType the annotation class to look for, both locally and
85-
* as a meta-annotation
88+
* @param annotationType the type of annotation to look for
8689
* @return the corresponding annotation descriptor if the annotation was found;
8790
* otherwise {@code null}
8891
* @see AnnotationUtils#findAnnotationDeclaringClass(Class, Class)
8992
* @see #findAnnotationDescriptorForTypes(Class, Class...)
9093
*/
9194
public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
9295
Class<T> annotationType) {
96+
return findAnnotationDescriptor(clazz, new HashSet<Annotation>(), annotationType);
97+
}
98+
99+
/**
100+
* Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)},
101+
* avoiding endless recursion by tracking which annotations have already been
102+
* <em>visited</em>.
103+
*
104+
* @param clazz the class to look for annotations on
105+
* @param visited the set of annotations that have already been visited
106+
* @param annotationType the type of annotation to look for
107+
* @return the corresponding annotation descriptor if the annotation was found;
108+
* otherwise {@code null}
109+
*/
110+
private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
111+
Set<Annotation> visited, Class<T> annotationType) {
93112

94113
Assert.notNull(annotationType, "Annotation type must not be null");
95114

@@ -103,29 +122,30 @@ public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescr
103122
}
104123

105124
// Declared on a composed annotation (i.e., as a meta-annotation)?
106-
if (!Annotation.class.isAssignableFrom(clazz)) {
107-
for (Annotation composedAnnotation : clazz.getAnnotations()) {
108-
T annotation = composedAnnotation.annotationType().getAnnotation(annotationType);
109-
if (annotation != null) {
110-
return new AnnotationDescriptor<T>(clazz, composedAnnotation, annotation);
125+
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
126+
if (visited.add(composedAnnotation)) {
127+
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(),
128+
visited, annotationType);
129+
if (descriptor != null) {
130+
return new AnnotationDescriptor<T>(clazz, descriptor.getDeclaringClass(), composedAnnotation,
131+
descriptor.getAnnotation());
111132
}
112133
}
113134
}
114135

115136
// Declared on a superclass?
116-
return findAnnotationDescriptor(clazz.getSuperclass(), annotationType);
137+
return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType);
117138
}
118139

119140
/**
120141
* Find the {@link UntypedAnnotationDescriptor} for the first {@link Class}
121142
* in the inheritance hierarchy of the specified {@code clazz} (including
122143
* the specified {@code clazz} itself) which declares at least one of the
123-
* specified {@code annotationTypes}, or {@code null} if none of the
124-
* specified annotation types could be found.
144+
* specified {@code annotationTypes}.
125145
*
126146
* <p>This method traverses the annotations and superclasses of the specified
127147
* {@code clazz} if no annotation can be found on the given class itself.
128-
*
148+
*
129149
* <p>This method explicitly handles class-level annotations which are not
130150
* declared as {@linkplain java.lang.annotation.Inherited inherited} <em>as
131151
* well as meta-annotations</em>.
@@ -135,21 +155,20 @@ public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescr
135155
* <li>Search for a local declaration of one of the annotation types on
136156
* the given class and return a corresponding {@code UntypedAnnotationDescriptor}
137157
* if found.
138-
* <li>Search through all annotations that the given class declares,
139-
* returning an {@code UntypedAnnotationDescriptor} for the first matching
140-
* candidate, if any.
141-
* <li>Proceed with introspection of the superclass hierarchy of the given
142-
* class by returning to step #1 with the superclass as the class to look
143-
* for annotations on.
158+
* <li>Recursively search through all annotations that the given class declares.
159+
* <li>Recursively search through the superclass hierarchy of the given class.
144160
* </ol>
145161
*
162+
* <p>In this context, the term <em>recursively</em> means that the search
163+
* process continues by returning to step #1 with the current annotation or
164+
* superclass as the class to look for annotations on.
165+
*
146166
* <p>If the supplied {@code clazz} is an interface, only the interface
147167
* itself will be checked; the inheritance hierarchy for interfaces will not
148168
* be traversed.
149169
*
150170
* @param clazz the class to look for annotations on
151-
* @param annotationTypes the types of annotations to look for, both locally
152-
* and as meta-annotations
171+
* @param annotationTypes the types of annotations to look for
153172
* @return the corresponding annotation descriptor if one of the annotations
154173
* was found; otherwise {@code null}
155174
* @see AnnotationUtils#findAnnotationDeclaringClassForTypes(java.util.List, Class)
@@ -158,6 +177,23 @@ public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescr
158177
@SuppressWarnings("unchecked")
159178
public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class<?> clazz,
160179
Class<? extends Annotation>... annotationTypes) {
180+
return findAnnotationDescriptorForTypes(clazz, new HashSet<Annotation>(), annotationTypes);
181+
}
182+
183+
/**
184+
* Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)},
185+
* avoiding endless recursion by tracking which annotations have already been
186+
* <em>visited</em>.
187+
*
188+
* @param clazz the class to look for annotations on
189+
* @param visited the set of annotations that have already been visited
190+
* @param annotationTypes the types of annotations to look for
191+
* @return the corresponding annotation descriptor if one of the annotations
192+
* was found; otherwise {@code null}
193+
*/
194+
@SuppressWarnings("unchecked")
195+
private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class<?> clazz,
196+
Set<Annotation> visited, Class<? extends Annotation>... annotationTypes) {
161197

162198
assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty");
163199

@@ -173,19 +209,19 @@ public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class
173209
}
174210

175211
// Declared on a composed annotation (i.e., as a meta-annotation)?
176-
if (!Annotation.class.isAssignableFrom(clazz)) {
177-
for (Annotation composedAnnotation : clazz.getAnnotations()) {
178-
for (Class<? extends Annotation> annotationType : annotationTypes) {
179-
Annotation annotation = composedAnnotation.annotationType().getAnnotation(annotationType);
180-
if (annotation != null) {
181-
return new UntypedAnnotationDescriptor(clazz, composedAnnotation, annotation);
182-
}
212+
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
213+
if (visited.add(composedAnnotation)) {
214+
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(
215+
composedAnnotation.annotationType(), visited, annotationTypes);
216+
if (descriptor != null) {
217+
return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), composedAnnotation,
218+
descriptor.getAnnotation());
183219
}
184220
}
185221
}
186222

187223
// Declared on a superclass?
188-
return findAnnotationDescriptorForTypes(clazz.getSuperclass(), annotationTypes);
224+
return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes);
189225
}
190226

191227

@@ -254,16 +290,16 @@ public static class AnnotationDescriptor<T extends Annotation> {
254290

255291

256292
public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
257-
this(rootDeclaringClass, null, annotation);
293+
this(rootDeclaringClass, rootDeclaringClass, null, annotation);
258294
}
259295

260-
public AnnotationDescriptor(Class<?> rootDeclaringClass, Annotation composedAnnotation, T annotation) {
296+
public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
297+
Annotation composedAnnotation, T annotation) {
261298
Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null");
262299
Assert.notNull(annotation, "annotation must not be null");
263300

264301
this.rootDeclaringClass = rootDeclaringClass;
265-
this.declaringClass = (composedAnnotation != null) ? composedAnnotation.annotationType()
266-
: rootDeclaringClass;
302+
this.declaringClass = declaringClass;
267303
this.composedAnnotation = composedAnnotation;
268304
this.annotation = annotation;
269305
this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass,
@@ -322,12 +358,13 @@ public String toString() {
322358
*/
323359
public static class UntypedAnnotationDescriptor extends AnnotationDescriptor<Annotation> {
324360

325-
public UntypedAnnotationDescriptor(Class<?> declaringClass, Annotation annotation) {
326-
super(declaringClass, annotation);
361+
public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Annotation annotation) {
362+
this(rootDeclaringClass, rootDeclaringClass, null, annotation);
327363
}
328364

329-
public UntypedAnnotationDescriptor(Class<?> declaringClass, Annotation composedAnnotation, Annotation annotation) {
330-
super(declaringClass, composedAnnotation, annotation);
365+
public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
366+
Annotation composedAnnotation, Annotation annotation) {
367+
super(rootDeclaringClass, declaringClass, composedAnnotation, annotation);
331368
}
332369
}
333370

0 commit comments

Comments
 (0)