diff options
author | Emmanuel Bourg <ebourg@apache.org> | 2015-07-15 23:21:27 +0200 |
---|---|---|
committer | Emmanuel Bourg <ebourg@apache.org> | 2015-07-15 23:21:27 +0200 |
commit | da46d30e80e4c59a41cf52055d06faa1dbb7e383 (patch) | |
tree | 52b707fbbccd5b6100088913f32c1cbd00568790 /spring-core/src/main/java/org/springframework/core/annotation | |
parent | c03c348db4e91c613982cbe6c99d0cf04ea14fe3 (diff) |
Imported Upstream version 4.0.9
Diffstat (limited to 'spring-core/src/main/java/org/springframework/core/annotation')
4 files changed, 666 insertions, 139 deletions
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java new file mode 100644 index 00000000..af84f7b5 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -0,0 +1,253 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.core.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * Utility class used to collect all annotation values including those declared on + * meta-annotations. + * + * @author Phillip Webb + * @author Juergen Hoeller + * @author Sam Brannen + * @since 4.0 + */ +public class AnnotatedElementUtils { + + public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) { + final Set<String> types = new LinkedHashSet<String>(); + process(element, annotationType, false, new Processor<Object>() { + @Override + public Object process(Annotation annotation, int metaDepth) { + if (metaDepth > 0) { + types.add(annotation.annotationType().getName()); + } + return null; + } + @Override + public void postProcess(Annotation annotation, Object result) { + } + }); + return (types.isEmpty() ? null : types); + } + + public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) { + return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() { + @Override + public Boolean process(Annotation annotation, int metaDepth) { + if (metaDepth > 0) { + return Boolean.TRUE; + } + return null; + } + @Override + public void postProcess(Annotation annotation, Boolean result) { + } + })); + } + + public static boolean isAnnotated(AnnotatedElement element, String annotationType) { + return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() { + @Override + public Boolean process(Annotation annotation, int metaDepth) { + return Boolean.TRUE; + } + @Override + public void postProcess(Annotation annotation, Boolean result) { + } + })); + } + + public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) { + return getAnnotationAttributes(element, annotationType, false, false); + } + + public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType, + final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { + + return process(element, annotationType, false, new Processor<AnnotationAttributes>() { + @Override + public AnnotationAttributes process(Annotation annotation, int metaDepth) { + return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap); + } + @Override + public void postProcess(Annotation annotation, AnnotationAttributes result) { + for (String key : result.keySet()) { + if (!AnnotationUtils.VALUE.equals(key)) { + Object value = AnnotationUtils.getValue(annotation, key); + if (value != null) { + result.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap)); + } + } + } + } + }); + } + + public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, String annotationType) { + return getAllAnnotationAttributes(element, annotationType, false, false); + } + + public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, + final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { + + final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>(); + process(element, annotationType, false, new Processor<Void>() { + @Override + public Void process(Annotation annotation, int metaDepth) { + if (annotation.annotationType().getName().equals(annotationType)) { + for (Map.Entry<String, Object> entry : AnnotationUtils.getAnnotationAttributes( + annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) { + attributes.add(entry.getKey(), entry.getValue()); + } + } + return null; + } + @Override + public void postProcess(Annotation annotation, Void result) { + for (String key : attributes.keySet()) { + if (!AnnotationUtils.VALUE.equals(key)) { + Object value = AnnotationUtils.getValue(annotation, key); + if (value != null) { + attributes.add(key, value); + } + } + } + } + }); + return (attributes.isEmpty() ? null : attributes); + } + + /** + * Process all annotations of the specified {@code annotationType} and + * recursively all meta-annotations on the specified {@code element}. + * <p>If the {@code traverseClassHierarchy} flag is {@code true} and the sought + * annotation is neither <em>directly present</em> on the given element nor + * present on the given element as a meta-annotation, then the algorithm will + * recursively search through the class hierarchy of the given element. + * @param element the annotated element + * @param annotationType the annotation type to find + * @param traverseClassHierarchy whether or not to traverse up the class + * hierarchy recursively + * @param processor the processor to delegate to + * @return the result of the processor + */ + private static <T> T process(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy, + Processor<T> processor) { + + try { + return doProcess(element, annotationType, traverseClassHierarchy, processor, + new HashSet<AnnotatedElement>(), 0); + } + catch (Throwable ex) { + throw new IllegalStateException("Failed to introspect annotations: " + element, ex); + } + } + + /** + * Perform the search algorithm for the {@link #process} method, avoiding + * endless recursion by tracking which annotated elements have already been + * <em>visited</em>. + * <p>The {@code metaDepth} parameter represents the depth of the annotation + * relative to the initial element. For example, an annotation that is + * <em>present</em> on the element will have a depth of 0; a meta-annotation + * will have a depth of 1; and a meta-meta-annotation will have a depth of 2. + * @param element the annotated element + * @param annotationType the annotation type to find + * @param traverseClassHierarchy whether or not to traverse up the class + * hierarchy recursively + * @param processor the processor to delegate to + * @param visited the set of annotated elements that have already been visited + * @param metaDepth the depth of the annotation relative to the initial element + * @return the result of the processor + */ + private static <T> T doProcess(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy, + Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) { + + if (visited.add(element)) { + Annotation[] annotations = + (traverseClassHierarchy ? element.getDeclaredAnnotations() : element.getAnnotations()); + for (Annotation annotation : annotations) { + if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) { + T result = processor.process(annotation, metaDepth); + if (result != null) { + return result; + } + result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy, + processor, visited, metaDepth + 1); + if (result != null) { + processor.postProcess(annotation, result); + return result; + } + } + } + for (Annotation annotation : annotations) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { + T result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy, + processor, visited, metaDepth); + if (result != null) { + processor.postProcess(annotation, result); + return result; + } + } + } + if (traverseClassHierarchy && element instanceof Class) { + Class<?> superclass = ((Class<?>) element).getSuperclass(); + if (superclass != null && !superclass.equals(Object.class)) { + T result = doProcess(superclass, annotationType, true, processor, visited, metaDepth); + if (result != null) { + return result; + } + } + } + } + return null; + } + + + /** + * Callback interface used to process an annotation. + * @param <T> the result type + */ + private static interface Processor<T> { + + /** + * Called to process the annotation. + * <p>The {@code metaDepth} parameter represents the depth of the + * annotation relative to the initial element. For example, an annotation + * that is <em>present</em> on the element will have a depth of 0; a + * meta-annotation will have a depth of 1; and a meta-meta-annotation + * will have a depth of 2. + * @param annotation the annotation to process + * @param metaDepth the depth of the annotation relative to the initial element + * @return the result of the processing, or {@code null} to continue + */ + T process(Annotation annotation, int metaDepth); + + void postProcess(Annotation annotation, T result); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java index 40041e8d..090621d8 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java @@ -16,8 +16,7 @@ package org.springframework.core.annotation; -import static java.lang.String.format; - +import java.lang.reflect.Array; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; @@ -27,10 +26,9 @@ import org.springframework.util.StringUtils; /** * {@link LinkedHashMap} subclass representing annotation attribute key/value pairs - * as read by Spring's reflection- or ASM-based {@link - * org.springframework.core.type.AnnotationMetadata AnnotationMetadata} implementations. - * Provides 'pseudo-reification' to avoid noisy Map generics in the calling code as well - * as convenience methods for looking up annotation attributes in a type-safe fashion. + * as read by Spring's reflection- or ASM-based {@link org.springframework.core.type.AnnotationMetadata} + * implementations. Provides 'pseudo-reification' to avoid noisy Map generics in the calling code + * as well as convenience methods for looking up annotation attributes in a type-safe fashion. * * @author Chris Beams * @since 3.1.1 @@ -63,24 +61,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { super(map); } - /** - * Return an {@link AnnotationAttributes} instance based on the given map; if the map - * is already an {@code AnnotationAttributes} instance, it is casted and returned - * immediately without creating any new instance; otherwise create a new instance by - * wrapping the map with the {@link #AnnotationAttributes(Map)} constructor. - * @param map original source of annotation attribute key/value pairs - */ - public static AnnotationAttributes fromMap(Map<String, Object> map) { - if (map == null) { - return null; - } - - if (map instanceof AnnotationAttributes) { - return (AnnotationAttributes) map; - } - - return new AnnotationAttributes(map); - } public String getString(String attributeName) { return doGet(attributeName, String.class); @@ -96,7 +76,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { @SuppressWarnings("unchecked") public <N extends Number> N getNumber(String attributeName) { - return (N) doGet(attributeName, Integer.class); + return (N) doGet(attributeName, Number.class); } @SuppressWarnings("unchecked") @@ -124,14 +104,24 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { @SuppressWarnings("unchecked") private <T> T doGet(String attributeName, Class<T> expectedType) { Assert.hasText(attributeName, "attributeName must not be null or empty"); - Object value = this.get(attributeName); - Assert.notNull(value, format("Attribute '%s' not found", attributeName)); - Assert.isAssignable(expectedType, value.getClass(), - format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ", + Object value = get(attributeName); + Assert.notNull(value, String.format("Attribute '%s' not found", attributeName)); + if (!expectedType.isInstance(value)) { + if (expectedType.isArray() && expectedType.getComponentType().isInstance(value)) { + Object arrayValue = Array.newInstance(expectedType.getComponentType(), 1); + Array.set(arrayValue, 0, value); + value = arrayValue; + } + else { + throw new IllegalArgumentException( + String.format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ", attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName())); + } + } return (T) value; } + @Override public String toString() { Iterator<Map.Entry<String, Object>> entries = entrySet().iterator(); StringBuilder sb = new StringBuilder("{"); @@ -155,4 +145,23 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { } return String.valueOf(value); } + + + /** + * Return an {@link AnnotationAttributes} instance based on the given map; if the map + * is already an {@code AnnotationAttributes} instance, it is casted and returned + * immediately without creating any new instance; otherwise create a new instance by + * wrapping the map with the {@link #AnnotationAttributes(Map)} constructor. + * @param map original source of annotation attribute key/value pairs + */ + public static AnnotationAttributes fromMap(Map<String, Object> map) { + if (map == null) { + return null; + } + if (map instanceof AnnotationAttributes) { + return (AnnotationAttributes) map; + } + return new AnnotationAttributes(map); + } + } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java index 19ba5fec..f4a83b8f 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java @@ -50,7 +50,7 @@ public class AnnotationAwareOrderComparator extends OrderComparator { return ((Ordered) obj).getOrder(); } if (obj != null) { - Class<?> clazz = (obj instanceof Class ? (Class) obj : obj.getClass()); + Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass()); Order order = AnnotationUtils.findAnnotation(clazz, Order.class); if (order != null) { return order.value(); @@ -86,4 +86,21 @@ public class AnnotationAwareOrderComparator extends OrderComparator { } } + /** + * Sort the given array or List with a default AnnotationAwareOrderComparator, + * if necessary. Simply skips sorting when given any other value. + * <p>Optimized to skip sorting for lists with size 0 or 1, + * in order to avoid unnecessary array extraction. + * @param value the array or List to sort + * @see java.util.Arrays#sort(Object[], java.util.Comparator) + */ + public static void sortIfNecessary(Object value) { + if (value instanceof Object[]) { + sort((Object[]) value); + } + else if (value instanceof List) { + sort((List<?>) value); + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 3b4def63..45d7c48c 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -19,31 +19,44 @@ package org.springframework.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.core.BridgeMethodResolver; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; /** - * General utility methods for working with annotations, handling bridge methods (which the compiler - * generates for generic declarations) as well as super methods (for optional "annotation inheritance"). - * Note that none of this is provided by the JDK's introspection facilities themselves. + * General utility methods for working with annotations, handling bridge methods + * (which the compiler generates for generic declarations) as well as super methods + * (for optional "annotation inheritance"). Note that none of this is + * provided by the JDK's introspection facilities themselves. * - * <p>As a general rule for runtime-retained annotations (e.g. for transaction control, authorization or service - * exposure), always use the lookup methods on this class (e.g., {@link #findAnnotation(Method, Class)}, {@link - * #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)}) instead of the plain annotation lookup - * methods in the JDK. You can still explicitly choose between lookup on the given class level only ({@link - * #getAnnotation(Method, Class)}) and lookup in the entire inheritance hierarchy of the given method ({@link - * #findAnnotation(Method, Class)}). + * <p>As a general rule for runtime-retained annotations (e.g. for transaction + * control, authorization, or service exposure), always use the lookup methods + * on this class (e.g., {@link #findAnnotation(Method, Class)}, + * {@link #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)}) + * instead of the plain annotation lookup methods in the JDK. You can still + * explicitly choose between a <em>get</em> lookup on the given class level only + * ({@link #getAnnotation(Method, Class)}) and a <em>find</em> lookup in the entire + * inheritance hierarchy of the given method ({@link #findAnnotation(Method, Class)}). * * @author Rob Harrop * @author Juergen Hoeller * @author Sam Brannen * @author Mark Fisher * @author Chris Beams + * @author Phillip Webb * @since 2.0 * @see java.lang.reflect.Method#getAnnotations() * @see java.lang.reflect.Method#getAnnotation(Class) @@ -51,42 +64,100 @@ import org.springframework.util.ReflectionUtils; public abstract class AnnotationUtils { /** The attribute name for annotations with a single element */ - static final String VALUE = "value"; + public static final String VALUE = "value"; + private static final Map<Class<?>, Boolean> annotatedInterfaceCache = new WeakHashMap<Class<?>, Boolean>(); + private static transient Log logger; + + + /** + * Get a single {@link Annotation} of {@code annotationType} from the supplied + * annotation: either the given annotation itself or a meta-annotation thereof. + * @param ann the Annotation to check + * @param annotationType the annotation type to look for, both locally and as a meta-annotation + * @return the matching annotation, or {@code null} if none found + * @since 4.0 + */ + @SuppressWarnings("unchecked") + public static <T extends Annotation> T getAnnotation(Annotation ann, Class<T> annotationType) { + if (annotationType.isInstance(ann)) { + return (T) ann; + } + try { + return ann.annotationType().getAnnotation(annotationType); + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(ann.annotationType(), ex); + return null; + } + } /** * Get a single {@link Annotation} of {@code annotationType} from the supplied * Method, Constructor or Field. Meta-annotations will be searched if the annotation * is not declared locally on the supplied element. - * @param ae the Method, Constructor or Field from which to get the annotation + * @param annotatedElement the Method, Constructor or Field from which to get the annotation * @param annotationType the annotation type to look for, both locally and as a meta-annotation * @return the matching annotation, or {@code null} if none found * @since 3.1 */ - public static <T extends Annotation> T getAnnotation(AnnotatedElement ae, Class<T> annotationType) { - T ann = ae.getAnnotation(annotationType); - if (ann == null) { - for (Annotation metaAnn : ae.getAnnotations()) { - ann = metaAnn.annotationType().getAnnotation(annotationType); - if (ann != null) { - break; + public static <T extends Annotation> T getAnnotation(AnnotatedElement annotatedElement, Class<T> annotationType) { + try { + T ann = annotatedElement.getAnnotation(annotationType); + if (ann == null) { + for (Annotation metaAnn : annotatedElement.getAnnotations()) { + ann = metaAnn.annotationType().getAnnotation(annotationType); + if (ann != null) { + break; + } } } + return ann; + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(annotatedElement, ex); + return null; + } + } + + /** + * Get all {@link Annotation Annotations} from the supplied Method, Constructor or Field. + * @param annotatedElement the Method, Constructor or Field to retrieve annotations from + * @return the annotations found, or {@code null} if not resolvable (e.g. because nested + * Class values in annotation attributes failed to resolve at runtime) + * @since 4.0.8 + */ + public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) { + try { + return annotatedElement.getAnnotations(); + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(annotatedElement, ex); + return null; } - return ann; } /** * Get all {@link Annotation Annotations} from the supplied {@link Method}. * <p>Correctly handles bridge {@link Method Methods} generated by the compiler. - * @param method the method to look for annotations on + * @param method the Method to retrieve annotations from * @return the annotations found * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) */ public static Annotation[] getAnnotations(Method method) { - return BridgeMethodResolver.findBridgedMethod(method).getAnnotations(); + try { + return BridgeMethodResolver.findBridgedMethod(method).getAnnotations(); + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(method, ex); + return null; + } } /** @@ -99,22 +170,61 @@ public abstract class AnnotationUtils { */ public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) { Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); - A ann = resolvedMethod.getAnnotation(annotationType); - if (ann == null) { - for (Annotation metaAnn : resolvedMethod.getAnnotations()) { - ann = metaAnn.annotationType().getAnnotation(annotationType); - if (ann != null) { - break; - } + return getAnnotation((AnnotatedElement) resolvedMethod, annotationType); + } + + /** + * Get the possibly repeating {@link Annotation}s of {@code annotationType} from the + * supplied {@link Method}. Deals with both a single direct annotation and repeating + * annotations nested within a containing annotation. + * <p>Correctly handles bridge {@link Method Methods} generated by the compiler. + * @param method the method to look for annotations on + * @param containerAnnotationType the class of the container that holds the annotations + * @param annotationType the annotation type to look for + * @return the annotations found + * @since 4.0 + * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) + */ + public static <A extends Annotation> Set<A> getRepeatableAnnotation(Method method, + Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) { + + Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); + return getRepeatableAnnotation((AnnotatedElement) resolvedMethod, containerAnnotationType, annotationType); + } + + /** + * Get the possibly repeating {@link Annotation}s of {@code annotationType} from the + * supplied {@link AnnotatedElement}. Deals with both a single direct annotation and + * repeating annotations nested within a containing annotation. + * <p>Correctly handles bridge {@link Method Methods} generated by the compiler. + * @param annotatedElement the element to look for annotations on + * @param containerAnnotationType the class of the container that holds the annotations + * @param annotationType the annotation type to look for + * @return the annotations found + * @since 4.0 + * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) + */ + public static <A extends Annotation> Set<A> getRepeatableAnnotation(AnnotatedElement annotatedElement, + Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) { + + try { + if (annotatedElement.getAnnotations().length > 0) { + return new AnnotationCollector<A>(containerAnnotationType, annotationType).getResult(annotatedElement); } } - return ann; + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(annotatedElement, ex); + } + return Collections.emptySet(); } /** - * Get a single {@link Annotation} of {@code annotationType} from the supplied {@link Method}, - * traversing its super methods if no annotation can be found on the given method itself. - * <p>Annotations on methods are not inherited by default, so we need to handle this explicitly. + * Find a single {@link Annotation} of {@code annotationType} from the supplied + * {@link Method}, traversing its super methods (i.e., from superclasses and + * interfaces) if no annotation can be found on the given method itself. + * <p>Annotations on methods are not inherited by default, so we need to handle + * this explicitly. * @param method the method to look for annotations on * @param annotationType the annotation type to look for * @return the annotation found, or {@code null} if none @@ -171,9 +281,15 @@ public abstract class AnnotationUtils { } boolean found = false; for (Method ifcMethod : iface.getMethods()) { - if (ifcMethod.getAnnotations().length > 0) { - found = true; - break; + try { + if (ifcMethod.getAnnotations().length > 0) { + found = true; + break; + } + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(ifcMethod, ex); } } annotatedInterfaceCache.put(iface, found); @@ -182,45 +298,82 @@ public abstract class AnnotationUtils { } /** - * Find a single {@link Annotation} of {@code annotationType} from the supplied {@link Class}, - * traversing its interfaces and superclasses if no annotation can be found on the given class itself. - * <p>This method explicitly handles class-level annotations which are not declared as - * {@link java.lang.annotation.Inherited inherited} <i>as well as annotations on interfaces</i>. - * <p>The algorithm operates as follows: Searches for an annotation on the given class and returns - * it if found. Else searches all interfaces that the given class declares, returning the annotation - * from the first matching candidate, if any. Else proceeds with introspection of the superclass - * of the given class, checking the superclass itself; if no annotation found there, proceeds - * with the interfaces that the superclass declares. Recursing up through the entire superclass - * hierarchy if no match is found. + * Find a single {@link Annotation} of {@code annotationType} on the + * supplied {@link Class}, traversing its interfaces, annotations, and + * superclasses if the annotation is not <em>present</em> on the given class + * itself. + * <p>This method explicitly handles class-level annotations which are not + * declared as {@link java.lang.annotation.Inherited inherited} <em>as well + * as meta-annotations and annotations on interfaces</em>. + * <p>The algorithm operates as follows: + * <ol> + * <li>Search for the annotation on the given class and return it if found. + * <li>Recursively search through all interfaces that the given class declares. + * <li>Recursively search through all annotations that the given class declares. + * <li>Recursively search through the superclass hierarchy of the given class. + * </ol> + * <p>Note: in this context, the term <em>recursively</em> means that the search + * process continues by returning to step #1 with the current interface, + * annotation, or superclass as the class to look for annotations on. * @param clazz the class to look for annotations on - * @param annotationType the annotation type to look for - * @return the annotation found, or {@code null} if none found + * @param annotationType the type of annotation to look for + * @return the annotation if found, or {@code null} if not found */ public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) { + return findAnnotation(clazz, annotationType, new HashSet<Annotation>()); + } + + /** + * Perform the search algorithm for {@link #findAnnotation(Class, Class)}, + * avoiding endless recursion by tracking which annotations have already + * been <em>visited</em>. + * @param clazz the class to look for annotations on + * @param annotationType the type of annotation to look for + * @param visited the set of annotations that have already been visited + * @return the annotation if found, or {@code null} if not found + */ + private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) { Assert.notNull(clazz, "Class must not be null"); - A annotation = clazz.getAnnotation(annotationType); - if (annotation != null) { - return annotation; + + if (isAnnotationDeclaredLocally(annotationType, clazz)) { + try { + return clazz.getAnnotation(annotationType); + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(clazz, ex); + return null; + } } + for (Class<?> ifc : clazz.getInterfaces()) { - annotation = findAnnotation(ifc, annotationType); + A annotation = findAnnotation(ifc, annotationType, visited); if (annotation != null) { return annotation; } } - if (!Annotation.class.isAssignableFrom(clazz)) { - for (Annotation ann : clazz.getAnnotations()) { - annotation = findAnnotation(ann.annotationType(), annotationType); - if (annotation != null) { - return annotation; + + try { + for (Annotation ann : clazz.getDeclaredAnnotations()) { + if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) { + A annotation = findAnnotation(ann.annotationType(), annotationType, visited); + if (annotation != null) { + return annotation; + } } } } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(clazz, ex); + return null; + } + Class<?> superclass = clazz.getSuperclass(); if (superclass == null || superclass.equals(Object.class)) { return null; } - return findAnnotation(superclass, annotationType); + return findAnnotation(superclass, annotationType, visited); } /** @@ -312,12 +465,18 @@ public abstract class AnnotationUtils { Assert.notNull(annotationType, "Annotation type must not be null"); Assert.notNull(clazz, "Class must not be null"); boolean declaredLocally = false; - for (Annotation annotation : clazz.getDeclaredAnnotations()) { - if (annotation.annotationType().equals(annotationType)) { - declaredLocally = true; - break; + try { + for (Annotation ann : clazz.getDeclaredAnnotations()) { + if (ann.annotationType().equals(annotationType)) { + declaredLocally = true; + break; + } } } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(clazz, ex); + } return declaredLocally; } @@ -343,11 +502,21 @@ public abstract class AnnotationUtils { } /** - * Retrieve the given annotation's attributes as a Map, preserving all attribute types - * as-is. - * <p>Note: As of Spring 3.1.1, the returned map is actually an - * {@link AnnotationAttributes} instance, however the Map signature of this method has - * been preserved for binary compatibility. + * Determine if the supplied {@link Annotation} is defined in the core JDK + * {@code java.lang.annotation} package. + * @param annotation the annotation to check (never {@code null}) + * @return {@code true} if the annotation is in the {@code java.lang.annotation} package + */ + public static boolean isInJavaLangAnnotationPackage(Annotation annotation) { + Assert.notNull(annotation, "Annotation must not be null"); + return annotation.annotationType().getName().startsWith("java.lang.annotation"); + } + + /** + * Retrieve the given annotation's attributes as a {@link Map}, preserving all + * attribute types as-is. + * <p>Note: This method actually returns an {@link AnnotationAttributes} instance. + * However, the {@code Map} signature has been preserved for binary compatibility. * @param annotation the annotation to retrieve the attributes for * @return the Map of annotation attributes, with attribute names as keys and * corresponding attribute values as values @@ -357,16 +526,15 @@ public abstract class AnnotationUtils { } /** - * Retrieve the given annotation's attributes as a Map. Equivalent to calling - * {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with + * Retrieve the given annotation's attributes as a {@link Map}. Equivalent to + * calling {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with * the {@code nestedAnnotationsAsMap} parameter set to {@code false}. - * <p>Note: As of Spring 3.1.1, the returned map is actually an - * {@link AnnotationAttributes} instance, however the Map signature of this method has - * been preserved for binary compatibility. + * <p>Note: This method actually returns an {@link AnnotationAttributes} instance. + * However, the {@code Map} signature has been preserved for binary compatibility. * @param annotation the annotation to retrieve the attributes for * @param classValuesAsString whether to turn Class references into Strings (for - * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to - * preserve them as Class references + * compatibility with {@link org.springframework.core.type.AnnotationMetadata} + * or to preserve them as Class references * @return the Map of annotation attributes, with attribute names as keys and * corresponding attribute values as values */ @@ -376,13 +544,13 @@ public abstract class AnnotationUtils { /** * Retrieve the given annotation's attributes as an {@link AnnotationAttributes} - * map structure. Implemented in Spring 3.1.1 to provide fully recursive annotation - * reading capabilities on par with that of the reflection-based - * {@link org.springframework.core.type.StandardAnnotationMetadata}. + * map structure. + * <p>This method provides fully recursive annotation reading capabilities on par with + * the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}. * @param annotation the annotation to retrieve the attributes for * @param classValuesAsString whether to turn Class references into Strings (for - * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to - * preserve them as Class references + * compatibility with {@link org.springframework.core.type.AnnotationMetadata} + * or to preserve them as Class references * @param nestedAnnotationsAsMap whether to turn nested Annotation instances into * {@link AnnotationAttributes} maps (for compatibility with * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as @@ -400,34 +568,7 @@ public abstract class AnnotationUtils { if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) { try { Object value = method.invoke(annotation); - if (classValuesAsString) { - if (value instanceof Class) { - value = ((Class<?>) value).getName(); - } - else if (value instanceof Class[]) { - Class<?>[] clazzArray = (Class[]) value; - String[] newValue = new String[clazzArray.length]; - for (int i = 0; i < clazzArray.length; i++) { - newValue[i] = clazzArray[i].getName(); - } - value = newValue; - } - } - if (nestedAnnotationsAsMap && value instanceof Annotation) { - attrs.put(method.getName(), - getAnnotationAttributes((Annotation) value, classValuesAsString, true)); - } - else if (nestedAnnotationsAsMap && value instanceof Annotation[]) { - Annotation[] realAnnotations = (Annotation[]) value; - AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length]; - for (int i = 0; i < realAnnotations.length; i++) { - mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString, true); - } - attrs.put(method.getName(), mappedAnnotations); - } - else { - attrs.put(method.getName(), value); - } + attrs.put(method.getName(), adaptValue(value, classValuesAsString, nestedAnnotationsAsMap)); } catch (Exception ex) { throw new IllegalStateException("Could not obtain annotation attribute values", ex); @@ -438,6 +579,48 @@ public abstract class AnnotationUtils { } /** + * Adapt the given value according to the given class and nested annotation settings. + * @param value the annotation attribute value + * @param classValuesAsString whether to turn Class references into Strings (for + * compatibility with {@link org.springframework.core.type.AnnotationMetadata} + * or to preserve them as Class references + * @param nestedAnnotationsAsMap whether to turn nested Annotation instances into + * {@link AnnotationAttributes} maps (for compatibility with + * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as + * Annotation instances + * @return the adapted value, or the original value if no adaptation is needed + */ + static Object adaptValue(Object value, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + if (classValuesAsString) { + if (value instanceof Class) { + value = ((Class<?>) value).getName(); + } + else if (value instanceof Class[]) { + Class<?>[] clazzArray = (Class[]) value; + String[] newValue = new String[clazzArray.length]; + for (int i = 0; i < clazzArray.length; i++) { + newValue[i] = clazzArray[i].getName(); + } + value = newValue; + } + } + if (nestedAnnotationsAsMap && value instanceof Annotation) { + return getAnnotationAttributes((Annotation) value, classValuesAsString, true); + } + else if (nestedAnnotationsAsMap && value instanceof Annotation[]) { + Annotation[] realAnnotations = (Annotation[]) value; + AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length]; + for (int i = 0; i < realAnnotations.length; i++) { + mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString, true); + } + return mappedAnnotations; + } + else { + return value; + } + } + + /** * Retrieve the <em>value</em> of the {@code "value"} attribute of a * single-element Annotation, given an annotation instance. * @param annotation the annotation instance from which to retrieve the value @@ -449,7 +632,7 @@ public abstract class AnnotationUtils { } /** - * Retrieve the <em>value</em> of a named Annotation attribute, given an annotation instance. + * Retrieve the <em>value</em> of a named attribute, given an annotation instance. * @param annotation the annotation instance from which to retrieve the value * @param attributeName the name of the attribute value to retrieve * @return the attribute value, or {@code null} if not found @@ -478,7 +661,7 @@ public abstract class AnnotationUtils { } /** - * Retrieve the <em>default value</em> of a named Annotation attribute, given an annotation instance. + * Retrieve the <em>default value</em> of a named attribute, given an annotation instance. * @param annotation the annotation instance from which to retrieve the default value * @param attributeName the name of the attribute value to retrieve * @return the default value of the named attribute, or {@code null} if not found @@ -500,7 +683,8 @@ public abstract class AnnotationUtils { } /** - * Retrieve the <em>default value</em> of a named Annotation attribute, given the {@link Class annotation type}. + * Retrieve the <em>default value</em> of a named attribute, given the + * {@link Class annotation type}. * @param annotationType the <em>annotation type</em> for which the default value should be retrieved * @param attributeName the name of the attribute value to retrieve. * @return the default value of the named attribute, or {@code null} if not found @@ -515,4 +699,68 @@ public abstract class AnnotationUtils { } } + + private static void logIntrospectionFailure(AnnotatedElement annotatedElement, Exception ex) { + Log loggerToUse = logger; + if (loggerToUse == null) { + loggerToUse = LogFactory.getLog(AnnotationUtils.class); + logger = loggerToUse; + } + if (loggerToUse.isInfoEnabled()) { + loggerToUse.info("Failed to introspect annotations on [" + annotatedElement + "]: " + ex); + } + } + + + private static class AnnotationCollector<A extends Annotation> { + + private final Class<? extends Annotation> containerAnnotationType; + + private final Class<A> annotationType; + + private final Set<AnnotatedElement> visited = new HashSet<AnnotatedElement>(); + + private final Set<A> result = new LinkedHashSet<A>(); + + public AnnotationCollector(Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) { + this.containerAnnotationType = containerAnnotationType; + this.annotationType = annotationType; + } + + public Set<A> getResult(AnnotatedElement element) { + process(element); + return Collections.unmodifiableSet(this.result); + } + + @SuppressWarnings("unchecked") + private void process(AnnotatedElement annotatedElement) { + if (this.visited.add(annotatedElement)) { + for (Annotation ann : annotatedElement.getAnnotations()) { + if (ObjectUtils.nullSafeEquals(this.annotationType, ann.annotationType())) { + this.result.add((A) ann); + } + else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, ann.annotationType())) { + this.result.addAll(getValue(ann)); + } + else if (!isInJavaLangAnnotationPackage(ann)) { + process(ann.annotationType()); + } + } + } + } + + @SuppressWarnings("unchecked") + private List<A> getValue(Annotation annotation) { + try { + Method method = annotation.annotationType().getDeclaredMethod("value"); + ReflectionUtils.makeAccessible(method); + return Arrays.asList((A[]) method.invoke(annotation)); + } + catch (Exception ex) { + // Unable to read value from repeating annotation container -> ignore it. + return Collections.emptyList(); + } + } + } + } |