All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.test.util.MetaAnnotationUtils Maven / Gradle / Ivy

There is a newer version: 6.1.13
Show newest version
/*
 * Copyright 2002-2019 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.util;

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;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
 * {@code MetaAnnotationUtils} is a collection of utility methods that complements
 * the standard support already available in {@link AnnotationUtils}.
 *
 * 

Whereas {@code AnnotationUtils} provides utilities for getting or * finding an annotation, {@code MetaAnnotationUtils} goes a step further * by providing support for determining the root class on which an * annotation is declared, either directly or indirectly via a composed * annotation. This additional information is encapsulated in an * {@link AnnotationDescriptor}. * *

The additional information provided by an {@code AnnotationDescriptor} is * required by the Spring TestContext Framework in order to be able to * support class hierarchy traversals for annotations such as * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration}, * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}, * and {@link org.springframework.test.context.ActiveProfiles @ActiveProfiles} * which offer support for merging and overriding various inherited * annotation attributes (e.g. * {@link org.springframework.test.context.ContextConfiguration#inheritLocations}). * * @author Sam Brannen * @since 4.0 * @see AnnotationUtils * @see AnnotationDescriptor */ public abstract class MetaAnnotationUtils { /** * 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. *

This method explicitly handles class-level annotations which are not * declared as {@linkplain java.lang.annotation.Inherited inherited} as * well as meta-annotations. *

The algorithm operates as follows: *

    *
  1. Search for the annotation on the given class and return a corresponding * {@code AnnotationDescriptor} if found. *
  2. Recursively search through all annotations that the given class declares. *
  3. Recursively search through all interfaces implemented by the given class. *
  4. Recursively search through the superclass hierarchy of the given class. *
*

In this context, the term recursively 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 AnnotationDescriptor findAnnotationDescriptor( Class clazz, Class annotationType) { return findAnnotationDescriptor(clazz, new HashSet<>(), annotationType); } /** * Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)}, * avoiding endless recursion by tracking which annotations have already been * visited. * @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} */ @Nullable private static AnnotationDescriptor findAnnotationDescriptor( @Nullable Class clazz, Set visited, Class annotationType) { 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)); } // Declared on a composed annotation (i.e., as a meta-annotation)? for (Annotation composedAnn : clazz.getDeclaredAnnotations()) { Class composedType = composedAnn.annotationType(); if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) { AnnotationDescriptor descriptor = findAnnotationDescriptor(composedType, visited, annotationType); if (descriptor != null) { return new AnnotationDescriptor<>( clazz, descriptor.getDeclaringClass(), composedAnn, descriptor.getAnnotation()); } } } // Declared on interface? for (Class ifc : clazz.getInterfaces()) { AnnotationDescriptor descriptor = findAnnotationDescriptor(ifc, visited, annotationType); if (descriptor != null) { return new AnnotationDescriptor<>(clazz, descriptor.getDeclaringClass(), descriptor.getComposedAnnotation(), descriptor.getAnnotation()); } } // Declared on a superclass? 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}. *

This method traverses the annotations, interfaces, and superclasses * of the specified {@code clazz} if no annotation can be found on the given * class itself. *

This method explicitly handles class-level annotations which are not * declared as {@linkplain java.lang.annotation.Inherited inherited} as * well as meta-annotations. *

The algorithm operates as follows: *

    *
  1. Search for a local declaration of one of the annotation types on * the given class and return a corresponding {@code UntypedAnnotationDescriptor} * if found. *
  2. Recursively search through all annotations that the given class declares. *
  3. Recursively search through all interfaces implemented by the given class. *
  4. Recursively search through the superclass hierarchy of the given class. *
*

In this context, the term recursively 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... annotationTypes) { return findAnnotationDescriptorForTypes(clazz, new HashSet<>(), annotationTypes); } /** * Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)}, * avoiding endless recursion by tracking which annotations have already been * visited. * @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") @Nullable private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(@Nullable Class clazz, Set visited, Class... annotationTypes) { assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty"); if (clazz == null || Object.class == clazz) { return null; } // Declared locally? for (Class annotationType : annotationTypes) { if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType)); } } // 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(), visited, annotationTypes); if (descriptor != null) { return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), composedAnnotation, descriptor.getAnnotation()); } } } // Declared on interface? for (Class ifc : clazz.getInterfaces()) { UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(ifc, visited, annotationTypes); if (descriptor != null) { return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), descriptor.getComposedAnnotation(), descriptor.getAnnotation()); } } // Declared on a superclass? return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes); } 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 declared * as well as the actual {@linkplain #getAnnotation() annotation} instance. *

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 root declaring class is * not directly annotated with the annotation but rather indirectly via the * composed annotation. *

Given the following example, if we are searching for the {@code @Transactional} * annotation on the {@code TransactionalTests} class, then the * properties of the {@code AnnotationDescriptor} would be as follows. *

    *
  • rootDeclaringClass: {@code TransactionalTests} class object
  • *
  • declaringClass: {@code TransactionalTests} class object
  • *
  • composedAnnotation: {@code null}
  • *
  • annotation: instance of the {@code Transactional} annotation
  • *
*

	 * @Transactional
	 * @ContextConfiguration({"/test-datasource.xml", "/repository-config.xml"})
	 * public class TransactionalTests { }
	 * 
*

Given the following example, if we are searching for the {@code @Transactional} * annotation on the {@code UserRepositoryTests} class, then the * properties of the {@code AnnotationDescriptor} would be as follows. *

    *
  • rootDeclaringClass: {@code UserRepositoryTests} class object
  • *
  • declaringClass: {@code RepositoryTests} class object
  • *
  • composedAnnotation: instance of the {@code RepositoryTests} annotation
  • *
  • annotation: instance of the {@code Transactional} annotation
  • *
*

	 * @Transactional
	 * @ContextConfiguration({"/test-datasource.xml", "/repository-config.xml"})
	 * @Retention(RetentionPolicy.RUNTIME)
	 * public @interface RepositoryTests { }
	 *
	 * @RepositoryTests
	 * public class UserRepositoryTests { }
	 * 
* * @param the annotation type */ public static class AnnotationDescriptor { 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(annotation, "Annotation must not be null"); this.rootDeclaringClass = rootDeclaringClass; this.declaringClass = declaringClass; this.composedAnnotation = composedAnnotation; this.annotation = annotation; AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes( rootDeclaringClass, annotation.annotationType().getName(), false, false); Assert.state(attributes != null, "No annotation attributes"); this.annotationAttributes = 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}. * @since 4.2 * @see #getAnnotationAttributes() * @see #getAnnotationType() * @see AnnotationUtils#synthesizeAnnotation(java.util.Map, Class, java.lang.reflect.AnnotatedElement) */ @SuppressWarnings("unchecked") public T synthesizeAnnotation() { return AnnotationUtils.synthesizeAnnotation( getAnnotationAttributes(), (Class) getAnnotationType(), getRootDeclaringClass()); } public Class getAnnotationType() { return this.annotation.annotationType(); } public AnnotationAttributes getAnnotationAttributes() { return this.annotationAttributes; } @Nullable public Annotation getComposedAnnotation() { return this.composedAnnotation; } @Nullable public Class getComposedAnnotationType() { return (this.composedAnnotation != null ? this.composedAnnotation.annotationType() : null); } /** * Provide a textual representation of this {@code AnnotationDescriptor}. */ @Override public String toString() { return new ToStringCreator(this) .append("rootDeclaringClass", this.rootDeclaringClass) .append("declaringClass", this.declaringClass) .append("composedAnnotation", this.composedAnnotation) .append("annotation", this.annotation) .toString(); } } /** * Untyped extension of {@code 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 { public UntypedAnnotationDescriptor(Class rootDeclaringClass, Annotation annotation) { this(rootDeclaringClass, rootDeclaringClass, null, annotation); } public UntypedAnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, @Nullable Annotation composedAnnotation, Annotation annotation) { super(rootDeclaringClass, declaringClass, composedAnnotation, annotation); } /** * Throws an {@link UnsupportedOperationException} since the type of annotation * represented by the {@link #getAnnotationAttributes AnnotationAttributes} in * an {@code UntypedAnnotationDescriptor} is unknown. * @since 4.2 */ @Override public Annotation synthesizeAnnotation() { throw new UnsupportedOperationException( "getMergedAnnotation() is unsupported in UntypedAnnotationDescriptor"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy