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 * @Transactional 260 * @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 * @Transactional 274 * @ContextConfiguration({"/test-datasource.xml", "/repository-config.xml"}) 275 * @Retention(RetentionPolicy.RUNTIME) 276 * public @interface RepositoryTests { } 277 * 278 * @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}