001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.util;
018
019import java.lang.annotation.Annotation;
020import java.util.HashSet;
021import java.util.Set;
022
023import org.springframework.core.annotation.AnnotatedElementUtils;
024import org.springframework.core.annotation.AnnotationAttributes;
025import org.springframework.core.annotation.AnnotationUtils;
026import org.springframework.core.style.ToStringCreator;
027import org.springframework.util.Assert;
028import org.springframework.util.ObjectUtils;
029
030/**
031 * {@code MetaAnnotationUtils} is a collection of utility methods that complements
032 * the standard support already available in {@link AnnotationUtils}.
033 *
034 * <p>Whereas {@code AnnotationUtils} provides utilities for <em>getting</em> or
035 * <em>finding</em> an annotation, {@code MetaAnnotationUtils} goes a step further
036 * by providing support for determining the <em>root class</em> on which an
037 * annotation is declared, either directly or indirectly via a <em>composed
038 * annotation</em>. This additional information is encapsulated in an
039 * {@link AnnotationDescriptor}.
040 *
041 * <p>The additional information provided by an {@code AnnotationDescriptor} is
042 * required by the <em>Spring TestContext Framework</em> in order to be able to
043 * support class hierarchy traversals for annotations such as
044 * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration},
045 * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners},
046 * and {@link org.springframework.test.context.ActiveProfiles @ActiveProfiles}
047 * which offer support for merging and overriding various <em>inherited</em>
048 * annotation attributes (e.g.
049 * {@link org.springframework.test.context.ContextConfiguration#inheritLocations}).
050 *
051 * @author Sam Brannen
052 * @since 4.0
053 * @see AnnotationUtils
054 * @see AnnotationDescriptor
055 */
056public abstract class MetaAnnotationUtils {
057
058        /**
059         * Find the {@link AnnotationDescriptor} for the supplied {@code annotationType}
060         * on the supplied {@link Class}, traversing its annotations, interfaces, and
061         * superclasses if no annotation can be found on the given class itself.
062         * <p>This method explicitly handles class-level annotations which are not
063         * declared as {@linkplain java.lang.annotation.Inherited inherited} <em>as
064         * well as meta-annotations</em>.
065         * <p>The algorithm operates as follows:
066         * <ol>
067         * <li>Search for the annotation on the given class and return a corresponding
068         * {@code AnnotationDescriptor} if found.
069         * <li>Recursively search through all annotations that the given class declares.
070         * <li>Recursively search through all interfaces implemented by the given class.
071         * <li>Recursively search through the superclass hierarchy of the given class.
072         * </ol>
073         * <p>In this context, the term <em>recursively</em> means that the search
074         * process continues by returning to step #1 with the current annotation,
075         * interface, or superclass as the class to look for annotations on.
076         * @param clazz the class to look for annotations on
077         * @param annotationType the type of annotation to look for
078         * @return the corresponding annotation descriptor if the annotation was found;
079         * otherwise {@code null}
080         * @see AnnotationUtils#findAnnotationDeclaringClass(Class, Class)
081         * @see #findAnnotationDescriptorForTypes(Class, Class...)
082         */
083        public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
084                        Class<?> clazz, Class<T> annotationType) {
085
086                return findAnnotationDescriptor(clazz, new HashSet<Annotation>(), annotationType);
087        }
088
089        /**
090         * Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)},
091         * avoiding endless recursion by tracking which annotations have already been
092         * <em>visited</em>.
093         * @param clazz the class to look for annotations on
094         * @param visited the set of annotations that have already been visited
095         * @param annotationType the type of annotation to look for
096         * @return the corresponding annotation descriptor if the annotation was found;
097         * otherwise {@code null}
098         */
099        private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
100                        Class<?> clazz, Set<Annotation> visited, Class<T> annotationType) {
101
102                Assert.notNull(annotationType, "Annotation type must not be null");
103                if (clazz == null || Object.class == clazz) {
104                        return null;
105                }
106
107                // Declared locally?
108                if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) {
109                        return new AnnotationDescriptor<T>(clazz, clazz.getAnnotation(annotationType));
110                }
111
112                // Declared on a composed annotation (i.e., as a meta-annotation)?
113                for (Annotation composedAnn : clazz.getDeclaredAnnotations()) {
114                        Class<? extends Annotation> composedType = composedAnn.annotationType();
115                        if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) {
116                                AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(composedType, visited, annotationType);
117                                if (descriptor != null) {
118                                        return new AnnotationDescriptor<T>(
119                                                        clazz, descriptor.getDeclaringClass(), composedAnn, descriptor.getAnnotation());
120                                }
121                        }
122                }
123
124                // Declared on interface?
125                for (Class<?> ifc : clazz.getInterfaces()) {
126                        AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(ifc, visited, annotationType);
127                        if (descriptor != null) {
128                                return new AnnotationDescriptor<T>(clazz, descriptor.getDeclaringClass(),
129                                                descriptor.getComposedAnnotation(), descriptor.getAnnotation());
130                        }
131                }
132
133                // Declared on a superclass?
134                return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType);
135        }
136
137        /**
138         * Find the {@link UntypedAnnotationDescriptor} for the first {@link Class}
139         * in the inheritance hierarchy of the specified {@code clazz} (including
140         * the specified {@code clazz} itself) which declares at least one of the
141         * specified {@code annotationTypes}.
142         * <p>This method traverses the annotations, interfaces, and superclasses
143         * of the specified {@code clazz} if no annotation can be found on the given
144         * class itself.
145         * <p>This method explicitly handles class-level annotations which are not
146         * declared as {@linkplain java.lang.annotation.Inherited inherited} <em>as
147         * well as meta-annotations</em>.
148         * <p>The algorithm operates as follows:
149         * <ol>
150         * <li>Search for a local declaration of one of the annotation types on
151         * the given class and return a corresponding {@code UntypedAnnotationDescriptor}
152         * if found.
153         * <li>Recursively search through all annotations that the given class declares.
154         * <li>Recursively search through all interfaces implemented by the given class.
155         * <li>Recursively search through the superclass hierarchy of the given class.
156         * </ol>
157         * <p>In this context, the term <em>recursively</em> means that the search
158         * process continues by returning to step #1 with the current annotation,
159         * interface, or superclass as the class to look for annotations on.
160         * @param clazz the class to look for annotations on
161         * @param annotationTypes the types of annotations to look for
162         * @return the corresponding annotation descriptor if one of the annotations
163         * was found; otherwise {@code null}
164         * @see AnnotationUtils#findAnnotationDeclaringClassForTypes(java.util.List, Class)
165         * @see #findAnnotationDescriptor(Class, Class)
166         */
167        @SuppressWarnings("unchecked")
168        public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(
169                        Class<?> clazz, Class<? extends Annotation>... annotationTypes) {
170
171                return findAnnotationDescriptorForTypes(clazz, new HashSet<Annotation>(), annotationTypes);
172        }
173
174        /**
175         * Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)},
176         * avoiding endless recursion by tracking which annotations have already been
177         * <em>visited</em>.
178         * @param clazz the class to look for annotations on
179         * @param visited the set of annotations that have already been visited
180         * @param annotationTypes the types of annotations to look for
181         * @return the corresponding annotation descriptor if one of the annotations
182         * was found; otherwise {@code null}
183         */
184        @SuppressWarnings("unchecked")
185        private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class<?> clazz,
186                        Set<Annotation> visited, Class<? extends Annotation>... annotationTypes) {
187
188                assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty");
189                if (clazz == null || Object.class == clazz) {
190                        return null;
191                }
192
193                // Declared locally?
194                for (Class<? extends Annotation> annotationType : annotationTypes) {
195                        if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) {
196                                return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType));
197                        }
198                }
199
200                // Declared on a composed annotation (i.e., as a meta-annotation)?
201                for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
202                        if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) {
203                                UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(
204                                                composedAnnotation.annotationType(), visited, annotationTypes);
205                                if (descriptor != null) {
206                                        return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(),
207                                                        composedAnnotation, descriptor.getAnnotation());
208                                }
209                        }
210                }
211
212                // Declared on interface?
213                for (Class<?> ifc : clazz.getInterfaces()) {
214                        UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(ifc, visited, annotationTypes);
215                        if (descriptor != null) {
216                                return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(),
217                                                descriptor.getComposedAnnotation(), descriptor.getAnnotation());
218                        }
219                }
220
221                // Declared on a superclass?
222                return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes);
223        }
224
225        private static void assertNonEmptyAnnotationTypeArray(Class<?>[] annotationTypes, String message) {
226                if (ObjectUtils.isEmpty(annotationTypes)) {
227                        throw new IllegalArgumentException(message);
228                }
229                for (Class<?> clazz : annotationTypes) {
230                        if (!Annotation.class.isAssignableFrom(clazz)) {
231                                throw new IllegalArgumentException("Array elements must be of type Annotation");
232                        }
233                }
234        }
235
236
237        /**
238         * Descriptor for an {@link Annotation}, including the {@linkplain
239         * #getDeclaringClass() class} on which the annotation is <em>declared</em>
240         * as well as the actual {@linkplain #getAnnotation() annotation} instance.
241         * <p>If the annotation is used as a meta-annotation, the descriptor also includes
242         * the {@linkplain #getComposedAnnotation() composed annotation} on which the
243         * annotation is present. In such cases, the <em>root declaring class</em> is
244         * not directly annotated with the annotation but rather indirectly via the
245         * composed annotation.
246         * <p>Given the following example, if we are searching for the {@code @Transactional}
247         * annotation <em>on</em> the {@code TransactionalTests} class, then the
248         * properties of the {@code AnnotationDescriptor} would be as follows.
249         * <ul>
250         * <li>rootDeclaringClass: {@code TransactionalTests} class object</li>
251         * <li>declaringClass: {@code TransactionalTests} class object</li>
252         * <li>composedAnnotation: {@code null}</li>
253         * <li>annotation: instance of the {@code Transactional} annotation</li>
254         * </ul>
255         * <pre style="code">
256         * &#064;Transactional
257         * &#064;ContextConfiguration({"/test-datasource.xml", "/repository-config.xml"})
258         * public class TransactionalTests { }
259         * </pre>
260         * <p>Given the following example, if we are searching for the {@code @Transactional}
261         * annotation <em>on</em> the {@code UserRepositoryTests} class, then the
262         * properties of the {@code AnnotationDescriptor} would be as follows.
263         * <ul>
264         * <li>rootDeclaringClass: {@code UserRepositoryTests} class object</li>
265         * <li>declaringClass: {@code RepositoryTests} class object</li>
266         * <li>composedAnnotation: instance of the {@code RepositoryTests} annotation</li>
267         * <li>annotation: instance of the {@code Transactional} annotation</li>
268         * </ul>
269         * <pre style="code">
270         * &#064;Transactional
271         * &#064;ContextConfiguration({"/test-datasource.xml", "/repository-config.xml"})
272         * &#064;Retention(RetentionPolicy.RUNTIME)
273         * public &#064;interface RepositoryTests { }
274         *
275         * &#064;RepositoryTests
276         * public class UserRepositoryTests { }
277         * </pre>
278         */
279        public static class AnnotationDescriptor<T extends Annotation> {
280
281                private final Class<?> rootDeclaringClass;
282
283                private final Class<?> declaringClass;
284
285                private final Annotation composedAnnotation;
286
287                private final T annotation;
288
289                private final AnnotationAttributes annotationAttributes;
290
291                public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
292                        this(rootDeclaringClass, rootDeclaringClass, null, annotation);
293                }
294
295                public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
296                                Annotation composedAnnotation, T annotation) {
297
298                        Assert.notNull(rootDeclaringClass, "'rootDeclaringClass' must not be null");
299                        Assert.notNull(annotation, "Annotation must not be null");
300                        this.rootDeclaringClass = rootDeclaringClass;
301                        this.declaringClass = declaringClass;
302                        this.composedAnnotation = composedAnnotation;
303                        this.annotation = annotation;
304                        this.annotationAttributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
305                                        rootDeclaringClass, annotation.annotationType().getName(), false, false);
306                }
307
308                public Class<?> getRootDeclaringClass() {
309                        return this.rootDeclaringClass;
310                }
311
312                public Class<?> getDeclaringClass() {
313                        return this.declaringClass;
314                }
315
316                public T getAnnotation() {
317                        return this.annotation;
318                }
319
320                /**
321                 * Synthesize the merged {@link #getAnnotationAttributes AnnotationAttributes}
322                 * in this descriptor back into an annotation of the target
323                 * {@linkplain #getAnnotationType annotation type}.
324                 * @since 4.2
325                 * @see #getAnnotationAttributes()
326                 * @see #getAnnotationType()
327                 * @see AnnotationUtils#synthesizeAnnotation(java.util.Map, Class, java.lang.reflect.AnnotatedElement)
328                 */
329                @SuppressWarnings("unchecked")
330                public T synthesizeAnnotation() {
331                        return AnnotationUtils.synthesizeAnnotation(
332                                        getAnnotationAttributes(), (Class<T>) getAnnotationType(), getRootDeclaringClass());
333                }
334
335                public Class<? extends Annotation> getAnnotationType() {
336                        return this.annotation.annotationType();
337                }
338
339                public AnnotationAttributes getAnnotationAttributes() {
340                        return this.annotationAttributes;
341                }
342
343                public Annotation getComposedAnnotation() {
344                        return this.composedAnnotation;
345                }
346
347                public Class<? extends Annotation> getComposedAnnotationType() {
348                        return (this.composedAnnotation != null ? this.composedAnnotation.annotationType() : null);
349                }
350
351                /**
352                 * Provide a textual representation of this {@code AnnotationDescriptor}.
353                 */
354                @Override
355                public String toString() {
356                        return new ToStringCreator(this)
357                                        .append("rootDeclaringClass", this.rootDeclaringClass)
358                                        .append("declaringClass", this.declaringClass)
359                                        .append("composedAnnotation", this.composedAnnotation)
360                                        .append("annotation", this.annotation)
361                                        .toString();
362                }
363        }
364
365
366        /**
367         * <em>Untyped</em> extension of {@code AnnotationDescriptor} that is used
368         * to describe the declaration of one of several candidate annotation types
369         * where the actual annotation type cannot be predetermined.
370         */
371        public static class UntypedAnnotationDescriptor extends AnnotationDescriptor<Annotation> {
372
373                public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Annotation annotation) {
374                        this(rootDeclaringClass, rootDeclaringClass, null, annotation);
375                }
376
377                public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
378                                Annotation composedAnnotation, Annotation annotation) {
379
380                        super(rootDeclaringClass, declaringClass, composedAnnotation, annotation);
381                }
382
383                /**
384                 * Throws an {@link UnsupportedOperationException} since the type of annotation
385                 * represented by the {@link #getAnnotationAttributes AnnotationAttributes} in
386                 * an {@code UntypedAnnotationDescriptor} is unknown.
387                 * @since 4.2
388                 */
389                @Override
390                public Annotation synthesizeAnnotation() {
391                        throw new UnsupportedOperationException(
392                                        "getMergedAnnotation() is unsupported in UntypedAnnotationDescriptor");
393                }
394        }
395
396}