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