Overhaul AnnotatedElementUtils

- Methods which search for a specific annotation now properly ensure
   that the sought annotation was actually found.

 - Both the "get" and the "find" search algorithms no longer needlessly
   traverse meta-annotation hierarchies twice.

 - Both the "get" and the "find" search algorithms now properly
   increment the metaDepth when recursively searching within the
   meta-annotation hierarchy.

 - Redesigned getMetaAnnotationTypes() so that it doesn't needlessly
   search irrelevant annotations.

 - Documented and tested hasMetaAnnotationTypes().

 - Documented isAnnotated().

Issue: SPR-11514
This commit is contained in:
Sam Brannen
2015-05-05 23:35:00 +02:00
parent 8853107f76
commit ba84458c65
4 changed files with 135 additions and 59 deletions

View File

@@ -45,14 +45,16 @@ public class AnnotatedElementUtils {
* <em>present</em> on the annotation (of the specified
* {@code annotationType}) on the supplied {@link AnnotatedElement}.
*
* <p>This method also finds all meta-annotations in the annotation
* hierarchy above the specified annotation.
* <p>This method finds all meta-annotations in the annotation hierarchy
* above the specified annotation.
*
* @param element the annotated element; never {@code null}
* @param annotationType the annotation type on which to find
* meta-annotations; never {@code null}
* @return the names of all meta-annotations present on the annotation,
* or {@code null} if not found
* @see #getMetaAnnotationTypes(AnnotatedElement, String)
* @see #hasMetaAnnotationTypes
*/
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element,
Class<? extends Annotation> annotationType) {
@@ -65,14 +67,16 @@ public class AnnotatedElementUtils {
* <em>present</em> on the annotation (of the specified
* {@code annotationType}) on the supplied {@link AnnotatedElement}.
*
* <p>This method also finds all meta-annotations in the annotation
* hierarchy above the specified annotation.
* <p>This method finds all meta-annotations in the annotation hierarchy
* above the specified annotation.
*
* @param element the annotated element; never {@code null}
* @param annotationType the fully qualified class name of the annotation
* type on which to find meta-annotations; never {@code null} or empty
* @return the names of all meta-annotations present on the annotation,
* or {@code null} if not found
* @see #getMetaAnnotationTypes(AnnotatedElement, Class)
* @see #hasMetaAnnotationTypes
*/
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
Assert.notNull(element, "AnnotatedElement must not be null");
@@ -80,32 +84,49 @@ public class AnnotatedElementUtils {
final Set<String> types = new LinkedHashSet<String>();
processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Object>() {
@Override
public Object process(Annotation annotation, int metaDepth) {
if (metaDepth > 0) {
types.add(annotation.annotationType().getName());
}
return null;
try {
Annotation annotation = getAnnotation(element, annotationType);
if (annotation != null) {
processWithGetSemantics(annotation.annotationType(), annotationType, new SimpleAnnotationProcessor<Object>() {
@Override
public Object process(Annotation annotation, int metaDepth) {
types.add(annotation.annotationType().getName());
return null;
}
}, new HashSet<AnnotatedElement>(), 1);
}
});
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
}
return (types.isEmpty() ? null : types);
}
/**
* Determine if the supplied {@link AnnotatedElement} is annotated with
* a <em>composed annotation</em> that is meta-annotated with an
* annotation of the specified {@code annotationType}.
*
* <p>This method finds all meta-annotations in the annotation hierarchy
* above the specified element.
*
* @param element the annotated element; never {@code null}
* @param annotationType the fully qualified class name of the annotation
* @param annotationType the fully qualified class name of the meta-annotation
* type to find; never {@code null} or empty
* @return {@code true} if a matching meta-annotation is present
* @see #getMetaAnnotationTypes
*/
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, final String annotationType) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.hasText(annotationType, "annotationType must not be null or empty");
return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
@Override
public Boolean process(Annotation annotation, int metaDepth) {
if (metaDepth > 0) {
boolean found = annotation.annotationType().getName().equals(annotationType);
if (found && (metaDepth > 0)) {
return Boolean.TRUE;
}
return null;
@@ -114,18 +135,27 @@ public class AnnotatedElementUtils {
}
/**
* Determine if an annotation of the specified {@code annotationType}
* is <em>present</em> on the supplied {@link AnnotatedElement} or
* within the annotation hierarchy above the specified element.
*
* <p>If this method returns {@code true}, then {@link #getAnnotationAttributes}
* will return a non-null value.
*
* @param element the annotated element; never {@code null}
* @param annotationType the fully qualified class name of the annotation
* type to find; never {@code null} or empty
* @return {@code true} if a matching annotation is present
*/
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
public static boolean isAnnotated(AnnotatedElement element, final String annotationType) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.hasText(annotationType, "annotationType must not be null or empty");
return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
@Override
public Boolean process(Annotation annotation, int metaDepth) {
return Boolean.TRUE;
boolean found = annotation.annotationType().getName().equals(annotationType);
return (found ? Boolean.TRUE : null);
}
}));
}
@@ -167,7 +197,7 @@ public class AnnotatedElementUtils {
*/
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor(
return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor(annotationType,
classValuesAsString, nestedAnnotationsAsMap));
}
@@ -269,7 +299,7 @@ public class AnnotatedElementUtils {
return processWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses,
searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, new MergeAnnotationAttributesProcessor(
classValuesAsString, nestedAnnotationsAsMap));
annotationType, classValuesAsString, nestedAnnotationsAsMap));
}
/**
@@ -376,21 +406,12 @@ public class AnnotatedElementUtils {
// Search in local annotations
for (Annotation annotation : annotations) {
// TODO Add check for !isInJavaLangAnnotationPackage()
// if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
// && (annotation.annotationType().getName().equals(annotationType) ||
// metaDepth > 0)) {
// TODO Test for !AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) {
T result = processor.process(annotation, metaDepth);
if (result != null) {
return result;
}
result = processWithGetSemantics(annotation.annotationType(), annotationType, processor,
visited, metaDepth + 1);
if (result != null) {
processor.postProcess(annotation, result);
return result;
}
}
}
@@ -398,7 +419,7 @@ public class AnnotatedElementUtils {
for (Annotation annotation : annotations) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
T result = processWithGetSemantics(annotation.annotationType(), annotationType, processor,
visited, metaDepth);
visited, metaDepth + 1);
if (result != null) {
processor.postProcess(annotation, result);
return result;
@@ -492,13 +513,6 @@ public class AnnotatedElementUtils {
if (result != null) {
return result;
}
result = processWithFindSemantics(annotation.annotationType(), annotationType,
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1);
if (result != null) {
processor.postProcess(annotation, result);
return result;
}
}
}
@@ -507,7 +521,7 @@ public class AnnotatedElementUtils {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
T result = processWithFindSemantics(annotation.annotationType(), annotationType,
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
searchOnMethodsInSuperclasses, processor, visited, metaDepth);
searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1);
if (result != null) {
processor.postProcess(annotation, result);
return result;
@@ -636,6 +650,14 @@ public class AnnotatedElementUtils {
return null;
}
private static Annotation getAnnotation(AnnotatedElement element, String annotationType) {
for (Annotation annotation : element.getAnnotations()) {
if (annotation.annotationType().getName().equals(annotationType)) {
return annotation;
}
}
return null;
}
/**
* Callback interface that is used to process a target annotation (or
@@ -707,18 +729,21 @@ public class AnnotatedElementUtils {
*/
private static class MergeAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
private final String annotationType;
private final boolean classValuesAsString;
private final boolean nestedAnnotationsAsMap;
MergeAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
MergeAnnotationAttributesProcessor(String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
this.annotationType = annotationType;
this.classValuesAsString = classValuesAsString;
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
}
@Override
public AnnotationAttributes process(Annotation annotation, int metaDepth) {
return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);
boolean found = annotation.annotationType().getName().equals(annotationType);
return (!found ? null : AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap));
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -28,7 +28,7 @@ import org.springframework.util.ClassUtils;
* A simple filter which matches classes with a given annotation,
* checking inherited annotations as well.
*
* <p>The matching logic mirrors that of {@code Class.isAnnotationPresent()}.
* <p>The matching logic mirrors that of {@link java.lang.Class#isAnnotationPresent(Class)}.
*
* @author Mark Fisher
* @author Ramnivas Laddad