diff --git a/spring-core/src/main/java/org/springframework/core/type/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/type/AnnotatedElementUtils.java new file mode 100644 index 00000000000..ca817f48bd6 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/AnnotatedElementUtils.java @@ -0,0 +1,160 @@ +/* + * Copyright 2002-2013 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.type; + +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.core.annotation.AnnotationUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * Internal utility class used to collect all annotation values including those declared + * on meta-annotations. + * + * @author Phillip Webb + * @since 4.0 + */ +class AnnotatedElementUtils { + + public static Set getMetaAnnotationTypes(AnnotatedElement element, + String annotationType) { + final Set types = new LinkedHashSet(); + process(element, annotationType, new Processor() { + public Object process(Annotation annotation, int depth) { + if (depth > 0) { + types.add(annotation.annotationType().getName()); + } + return null; + } + }); + return (types.size() == 0 ? null : types); + } + + public static boolean hasMetaAnnotationTypes(AnnotatedElement element, + String annotationType) { + return Boolean.TRUE.equals( + process(element, annotationType, new Processor() { + public Boolean process(Annotation annotation, int depth) { + if (depth > 0) { + return true; + } + return null; + } + })); + } + + public static boolean isAnnotated(AnnotatedElement element, String annotationType) { + return Boolean.TRUE.equals( + process(element, annotationType, new Processor() { + public Boolean process(Annotation annotation, int depth) { + return true; + } + })); + } + + public static Map getAnnotationAttributes(AnnotatedElement element, + String annotationType, final boolean classValuesAsString, + final boolean nestedAnnotationsAsMap) { + return process(element, annotationType, new Processor>() { + public Map process(Annotation annotation, int depth) { + return AnnotationUtils.getAnnotationAttributes(annotation, + classValuesAsString, nestedAnnotationsAsMap); + } + }); + } + + public static MultiValueMap getAllAnnotationAttributes( + AnnotatedElement element, final String annotationType, + final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { + final MultiValueMap attributes = new LinkedMultiValueMap(); + process(element, annotationType, new Processor() { + public Object process(Annotation annotation, int depth) { + if (annotation.annotationType().getName().equals(annotationType)) { + for (Map.Entry entry : AnnotationUtils.getAnnotationAttributes( + annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) { + attributes.add(entry.getKey(), entry.getValue()); + } + } + return null; + } + }); + return (attributes.size() == 0 ? null : attributes); + } + + /** + * Process all annotations of the specified annotation type and recursively all + * meta-annotations on the specified type. + * @param element the annotated element + * @param annotationType the annotation type to find. Only items of the specified type + * or meta-annotations of the specified type will be processed + * @param processor the processor + * @return the result of the processor + */ + private static T process(AnnotatedElement element, String annotationType, + Processor processor) { + return recursivelyProcess(element, annotationType, processor, + new HashSet(), 0); + } + + private static T recursivelyProcess(AnnotatedElement element, + String annotationType, Processor processor, Set visited, + int depth) { + T result = null; + if (visited.add(element)) { + for (Annotation annotation : element.getAnnotations()) { + if (annotation.annotationType().getName().equals(annotationType) || depth > 0) { + result = result != null ? result : + processor.process(annotation, depth); + result = result != null ? result : + recursivelyProcess(annotation.annotationType(), annotationType, + processor, visited, depth + 1); + } + } + for (Annotation annotation : element.getAnnotations()) { + result = result != null ? result : + recursivelyProcess(annotation.annotationType(), annotationType, + processor, visited, depth); + } + + } + return result; + } + + /** + * Callback interface used to process an annotation. + * @param the result type + */ + private static interface Processor { + + /** + * Called to process the annotation. + * @param annotation the annotation to process + * @param depth the depth of the annotation relative to the initial match. For + * example a matched annotation will have a depth of 0, a meta-annotation 1 + * and a meta-meta-annotation 2 + * @return the result of the processing or {@code null} to continue + */ + T process(Annotation annotation, int depth); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java b/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java new file mode 100644 index 00000000000..4cef1d48b30 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2013 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.type; + +import java.util.Map; + +import org.springframework.util.MultiValueMap; + +/** + * Defines access to the annotations of a specific type ({@link AnnotationMetadata class} + * or {@link MethodMetadata method}), in a form that does not necessarily require the + * class-loading. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Mark Pollack + * @author Chris Beams + * @author Phillip Webb + * @since 4.0 + * @see AnnotationMetadata + * @see MethodMetadata + */ +public interface AnnotatedTypeMetadata { + + /** + * Determine whether the underlying type has an annotation or + * meta-annotation of the given type defined. + *

If this method returns {@code true}, then + * {@link #getAnnotationAttributes} will return a non-null Map. + * @param annotationType the annotation type to look for + * @return whether a matching annotation is defined + */ + boolean isAnnotated(String annotationType); + + /** + * Retrieve the attributes of the annotation of the given type, + * if any (i.e. if defined on the underlying class, as direct + * annotation or as meta-annotation). + * @param annotationType the annotation type to look for + * @return a Map of attributes, with the attribute name as key (e.g. "value") + * and the defined attribute value as Map value. This return value will be + * {@code null} if no matching annotation is defined. + */ + Map getAnnotationAttributes(String annotationType); + + /** + * Retrieve the attributes of the annotation of the given type, + * if any (i.e. if defined on the underlying class, as direct + * annotation or as meta-annotation). + * @param annotationType the annotation type to look for + * @param classValuesAsString whether to convert class references to String + * class names for exposure as values in the returned Map, instead of Class + * references which might potentially have to be loaded first + * @return a Map of attributes, with the attribute name as key (e.g. "value") + * and the defined attribute value as Map value. This return value will be + * {@code null} if no matching annotation is defined. + */ + Map getAnnotationAttributes(String annotationType, boolean classValuesAsString); + + /** + * Retrieve all attributes of all annotations of the given type, if any (i.e. if + * defined on the underlying method, as direct annotation or as meta-annotation). + * @param annotationType the annotation type to look for + * @return a MultiMap of attributes, with the attribute name as key (e.g. "value") and + * a list of the defined attribute values as Map value. This return value will + * be {@code null} if no matching annotation is defined. + */ + MultiValueMap getAllAnnotationAttributes(String annotationType); + + /** + * Retrieve all attributes of all annotations of the given type, if any (i.e. if + * defined on the underlying method, as direct annotation or as meta-annotation). + * @param annotationType the annotation type to look for + * @param classValuesAsString whether to convert class references to String + * @return a MultiMap of attributes, with the attribute name as key (e.g. "value") and + * a list of the defined attribute values as Map value. This return value will + * be {@code null} if no matching annotation is defined. + * @see #getAllAnnotationAttributes(String) + */ + MultiValueMap getAllAnnotationAttributes(String annotationType, + boolean classValuesAsString); + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java index beeb7d935a0..435e934415d 100644 --- a/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -16,7 +16,6 @@ package org.springframework.core.type; -import java.util.Map; import java.util.Set; /** @@ -25,11 +24,13 @@ import java.util.Set; * * @author Juergen Hoeller * @author Mark Fisher + * @author Phillip Webb * @since 2.5 * @see StandardAnnotationMetadata * @see org.springframework.core.type.classreading.MetadataReader#getAnnotationMetadata() + * @see AnnotatedTypeMetadata */ -public interface AnnotationMetadata extends ClassMetadata { +public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata { /** * Return the names of all annotation types defined on the underlying class. @@ -61,42 +62,6 @@ public interface AnnotationMetadata extends ClassMetadata { */ boolean hasMetaAnnotation(String metaAnnotationType); - /** - * Determine whether the underlying class has an annotation or - * meta-annotation of the given type defined. - *

This is equivalent to a "hasAnnotation || hasMetaAnnotation" - * check. If this method returns {@code true}, then - * {@link #getAnnotationAttributes} will return a non-null Map. - * @param annotationType the annotation type to look for - * @return whether a matching annotation is defined - */ - boolean isAnnotated(String annotationType); - - /** - * Retrieve the attributes of the annotation of the given type, - * if any (i.e. if defined on the underlying class, as direct - * annotation or as meta-annotation). - * @param annotationType the annotation type to look for - * @return a Map of attributes, with the attribute name as key (e.g. "value") - * and the defined attribute value as Map value. This return value will be - * {@code null} if no matching annotation is defined. - */ - Map getAnnotationAttributes(String annotationType); - - /** - * Retrieve the attributes of the annotation of the given type, - * if any (i.e. if defined on the underlying class, as direct - * annotation or as meta-annotation). - * @param annotationType the annotation type to look for - * @param classValuesAsString whether to convert class references to String - * class names for exposure as values in the returned Map, instead of Class - * references which might potentially have to be loaded first - * @return a Map of attributes, with the attribute name as key (e.g. "value") - * and the defined attribute value as Map value. This return value will be - * {@code null} if no matching annotation is defined. - */ - Map getAnnotationAttributes(String annotationType, boolean classValuesAsString); - /** * Determine whether the underlying class has any methods that are * annotated (or meta-annotated) with the given annotation type. diff --git a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java index 17d7bab24cb..585965ca32e 100644 --- a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -16,8 +16,6 @@ package org.springframework.core.type; -import java.util.Map; - /** * Interface that defines abstract access to the annotations of a specific * class, in a form that does not require that class to be loaded yet. @@ -25,11 +23,13 @@ import java.util.Map; * @author Juergen Hoeller * @author Mark Pollack * @author Chris Beams + * @author Phillip Webb * @since 3.0 * @see StandardMethodMetadata * @see AnnotationMetadata#getAnnotatedMethods + * @see AnnotatedTypeMetadata */ -public interface MethodMetadata { +public interface MethodMetadata extends AnnotatedTypeMetadata { /** * Return the name of the method. @@ -57,23 +57,4 @@ public interface MethodMetadata { */ boolean isOverridable(); - /** - * Determine whether the underlying method has an annotation or - * meta-annotation of the given type defined. - * @param annotationType the annotation type to look for - * @return whether a matching annotation is defined - */ - boolean isAnnotated(String annotationType); - - /** - * Retrieve the attributes of the annotation of the given type, - * if any (i.e. if defined on the underlying method, as direct - * annotation or as meta-annotation). - * @param annotationType the annotation type to look for - * @return a Map of attributes, with the attribute name as key (e.g. "value") - * and the defined attribute value as Map value. This return value will be - * {@code null} if no matching annotation is defined. - */ - Map getAnnotationAttributes(String annotationType); - } diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index 37473c5a77c..e0c609624cd 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.Set; import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.MultiValueMap; /** * {@link AnnotationMetadata} implementation that uses standard reflection @@ -32,6 +32,7 @@ import org.springframework.core.annotation.AnnotationUtils; * @author Juergen Hoeller * @author Mark Fisher * @author Chris Beams + * @author Phillip Webb * @since 2.5 */ public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata { @@ -74,21 +75,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements } public Set getMetaAnnotationTypes(String annotationType) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - Set types = new LinkedHashSet(); - Annotation[] metaAnns = ann.annotationType().getAnnotations(); - for (Annotation metaAnn : metaAnns) { - types.add(metaAnn.annotationType().getName()); - for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) { - types.add(metaMetaAnn.annotationType().getName()); - } - } - return types; - } - } - return null; + return AnnotatedElementUtils.getMetaAnnotationTypes(getIntrospectedClass(), annotationType); } public boolean hasAnnotation(String annotationType) { @@ -102,75 +89,38 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements } public boolean hasMetaAnnotation(String annotationType) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - Annotation[] metaAnns = ann.annotationType().getAnnotations(); - for (Annotation metaAnn : metaAnns) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) { - if (metaMetaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } - } - return false; + return AnnotatedElementUtils.hasMetaAnnotationTypes(getIntrospectedClass(), annotationType); } public boolean isAnnotated(String annotationType) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return true; - } - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } - return false; + return AnnotatedElementUtils.isAnnotated(getIntrospectedClass(), annotationType); } public Map getAnnotationAttributes(String annotationType) { return this.getAnnotationAttributes(annotationType, false); } - public Map getAnnotationAttributes(String annotationType, boolean classValuesAsString) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - ann, classValuesAsString, this.nestedAnnotationsAsMap); - } - } - for (Annotation ann : anns) { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - metaAnn, classValuesAsString, this.nestedAnnotationsAsMap); - } - } - } - return null; + public Map getAnnotationAttributes(String annotationType, + boolean classValuesAsString) { + return AnnotatedElementUtils.getAnnotationAttributes(getIntrospectedClass(), + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); + } + + public MultiValueMap getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + public MultiValueMap getAllAnnotationAttributes( + String annotationType, boolean classValuesAsString) { + return AnnotatedElementUtils.getAllAnnotationAttributes(getIntrospectedClass(), + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); } public boolean hasAnnotatedMethods(String annotationType) { Method[] methods = getIntrospectedClass().getDeclaredMethods(); for (Method method : methods) { - for (Annotation ann : method.getAnnotations()) { - if (ann.annotationType().getName().equals(annotationType)) { - return true; - } - else { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } + if (AnnotatedElementUtils.isAnnotated(method, annotationType)) { + return true; } } return false; @@ -180,19 +130,9 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements Method[] methods = getIntrospectedClass().getDeclaredMethods(); Set annotatedMethods = new LinkedHashSet(); for (Method method : methods) { - for (Annotation ann : method.getAnnotations()) { - if (ann.annotationType().getName().equals(annotationType)) { - annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); - break; - } - else { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); - break; - } - } - } + if (AnnotatedElementUtils.isAnnotated(method, annotationType)) { + annotatedMethods.add(new StandardMethodMetadata(method, + this.nestedAnnotationsAsMap)); } } return annotatedMethods; diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java index c6564c643f7..221cbdb8bec 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -16,13 +16,12 @@ package org.springframework.core.type; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Map; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; /** * {@link MethodMetadata} implementation that uses standard reflection @@ -31,6 +30,7 @@ import org.springframework.util.Assert; * @author Juergen Hoeller * @author Mark Pollack * @author Chris Beams + * @author Phillip Webb * @since 3.0 */ public class StandardMethodMetadata implements MethodMetadata { @@ -89,35 +89,26 @@ public class StandardMethodMetadata implements MethodMetadata { } public boolean isAnnotated(String annotationType) { - Annotation[] anns = this.introspectedMethod.getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return true; - } - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } - return false; + return AnnotatedElementUtils.isAnnotated(this.introspectedMethod, annotationType); } public Map getAnnotationAttributes(String annotationType) { - Annotation[] anns = this.introspectedMethod.getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - ann, true, nestedAnnotationsAsMap); - } - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - metaAnn, true, this.nestedAnnotationsAsMap); - } - } - } - return null; + return getAnnotationAttributes(annotationType, false); + } + + public Map getAnnotationAttributes(String annotationType, boolean classValuesAsString) { + return AnnotatedElementUtils.getAnnotationAttributes(this.introspectedMethod, + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); + } + + public MultiValueMap getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + public MultiValueMap getAllAnnotationAttributes( + String annotationType, boolean classValuesAsString) { + return AnnotatedElementUtils.getAllAnnotationAttributes(this.introspectedMethod, + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java index 0b89eeec783..b9a1586666c 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java @@ -34,13 +34,14 @@ import org.springframework.asm.SpringAsmInfo; import org.springframework.asm.Type; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; - /** * @author Chris Beams * @author Juergen Hoeller + * @author Phillip Webb * @since 3.1.1 */ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor { @@ -51,12 +52,14 @@ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor { protected final ClassLoader classLoader; + public AbstractRecursiveAnnotationVisitor(ClassLoader classLoader, AnnotationAttributes attributes) { super(SpringAsmInfo.ASM_VERSION); this.classLoader = classLoader; this.attributes = attributes; } + public void visit(String attributeName, Object attributeValue) { this.attributes.put(attributeName, attributeValue); } @@ -108,12 +111,14 @@ final class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationV private final List allNestedAttributes = new ArrayList(); + public RecursiveAnnotationArrayVisitor( String attributeName, AnnotationAttributes attributes, ClassLoader classLoader) { super(classLoader, attributes); this.attributeName = attributeName; } + @Override public void visit(String attributeName, Object attributeValue) { Object newValue = attributeValue; @@ -155,12 +160,14 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi private final String annotationType; + public RecursiveAnnotationAttributesVisitor( String annotationType, AnnotationAttributes attributes, ClassLoader classLoader) { super(classLoader, attributes); this.annotationType = annotationType; } + public final void visitEnd() { try { Class annotationClass = this.classLoader.loadClass(this.annotationType); @@ -214,18 +221,20 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi * * @author Juergen Hoeller * @author Chris Beams + * @author Phillip Webb * @since 3.0 */ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor { private final String annotationType; - private final Map attributesMap; + private final MultiValueMap attributesMap; private final Map> metaAnnotationMap; + public AnnotationAttributesReadingVisitor( - String annotationType, Map attributesMap, + String annotationType, MultiValueMap attributesMap, Map> metaAnnotationMap, ClassLoader classLoader) { super(annotationType, new AnnotationAttributes(), classLoader); @@ -234,29 +243,33 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib this.metaAnnotationMap = metaAnnotationMap; } + @Override public void doVisitEnd(Class annotationClass) { super.doVisitEnd(annotationClass); - this.attributesMap.put(this.annotationType, this.attributes); - registerMetaAnnotations(annotationClass); - } - - private void registerMetaAnnotations(Class annotationClass) { - // Register annotations that the annotation type is annotated with. + List attributes = this.attributesMap.get(this.annotationType); + if (attributes == null) { + this.attributesMap.add(this.annotationType, this.attributes); + } + else { + attributes.add(0, this.attributes); + } Set metaAnnotationTypeNames = new LinkedHashSet(); for (Annotation metaAnnotation : annotationClass.getAnnotations()) { - metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); - if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) { - this.attributesMap.put(metaAnnotation.annotationType().getName(), - AnnotationUtils.getAnnotationAttributes(metaAnnotation, true, true)); - } - for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) { - metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName()); - } + recusivelyCollectMetaAnnotations(metaAnnotationTypeNames, metaAnnotation); } if (this.metaAnnotationMap != null) { this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames); } } + private void recusivelyCollectMetaAnnotations(Set visited, Annotation annotation) { + if (visited.add(annotation.annotationType().getName())) { + this.attributesMap.add(annotation.annotationType().getName(), + AnnotationUtils.getAnnotationAttributes(annotation, true, true)); + for (Annotation metaMetaAnnotation : annotation.annotationType().getAnnotations()) { + recusivelyCollectMetaAnnotations(visited, metaMetaAnnotation); + } + } + } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index 72bf34bbbcb..5a2f959fb2d 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java @@ -41,6 +41,7 @@ import org.springframework.util.MultiValueMap; * @author Juergen Hoeller * @author Mark Fisher * @author Costin Leau + * @author Phillip Webb * @since 2.5 */ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { @@ -51,7 +52,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito protected final Map> metaAnnotationMap = new LinkedHashMap>(4); - protected final Map attributeMap = new LinkedHashMap(4); + protected final MultiValueMap attributeMap = new LinkedMultiValueMap(4); protected final MultiValueMap methodMetadataMap = new LinkedMultiValueMap(); @@ -105,60 +106,30 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito } public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) { - AnnotationAttributes raw = this.attributeMap.get(annotationType); - return convertClassValues(raw, classValuesAsString); + List attributes = this.attributeMap.get(annotationType); + AnnotationAttributes raw = (attributes == null ? null : attributes.get(0)); + return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, + classValuesAsString); } - private AnnotationAttributes convertClassValues(AnnotationAttributes original, boolean classValuesAsString) { - if (original == null) { + public MultiValueMap getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + public MultiValueMap getAllAnnotationAttributes( + String annotationType, boolean classValuesAsString) { + MultiValueMap allAttributes = new LinkedMultiValueMap(); + List attributes = this.attributeMap.get(annotationType); + if (attributes == null) { return null; } - AnnotationAttributes result = new AnnotationAttributes(original.size()); - for (Map.Entry entry : original.entrySet()) { - try { - Object value = entry.getValue(); - if (value instanceof AnnotationAttributes) { - value = convertClassValues((AnnotationAttributes) value, classValuesAsString); - } - else if (value instanceof AnnotationAttributes[]) { - AnnotationAttributes[] values = (AnnotationAttributes[])value; - for (int i = 0; i < values.length; i++) { - values[i] = convertClassValues(values[i], classValuesAsString); - } - } - else if (value instanceof Type) { - value = (classValuesAsString ? ((Type) value).getClassName() : - this.classLoader.loadClass(((Type) value).getClassName())); - } - else if (value instanceof Type[]) { - Type[] array = (Type[]) value; - Object[] convArray = (classValuesAsString ? new String[array.length] : new Class[array.length]); - for (int i = 0; i < array.length; i++) { - convArray[i] = (classValuesAsString ? array[i].getClassName() : - this.classLoader.loadClass(array[i].getClassName())); - } - value = convArray; - } - else 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; - } - } - result.put(entry.getKey(), value); - } - catch (Exception ex) { - // Class not found - can't resolve class reference in annotation attribute. + for (AnnotationAttributes raw : attributes) { + for (Map.Entry entry : AnnotationReadingVisitorUtils.convertClassValues( + this.classLoader, raw, classValuesAsString).entrySet()) { + allAttributes.add(entry.getKey(), entry.getValue()); } } - return result; + return allAttributes; } public boolean hasAnnotatedMethods(String annotationType) { diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java new file mode 100644 index 00000000000..82189cc29fc --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2013 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.type.classreading; + +import java.util.Map; + +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; + +/** + * Internal utility class used when reading annotations. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Costin Leau + * @author Phillip Webb + * @since 4.0 + */ +abstract class AnnotationReadingVisitorUtils { + + public static AnnotationAttributes convertClassValues(ClassLoader classLoader, + AnnotationAttributes original, boolean classValuesAsString) { + + if (original == null) { + return null; + } + + AnnotationAttributes result = new AnnotationAttributes(original.size()); + for (Map.Entry entry : original.entrySet()) { + try { + Object value = entry.getValue(); + if (value instanceof AnnotationAttributes) { + value = convertClassValues(classLoader, (AnnotationAttributes) value, + classValuesAsString); + } + else if (value instanceof AnnotationAttributes[]) { + AnnotationAttributes[] values = (AnnotationAttributes[])value; + for (int i = 0; i < values.length; i++) { + values[i] = convertClassValues(classLoader, values[i], + classValuesAsString); + } + } + else if (value instanceof Type) { + value = (classValuesAsString ? ((Type) value).getClassName() : + classLoader.loadClass(((Type) value).getClassName())); + } + else if (value instanceof Type[]) { + Type[] array = (Type[]) value; + Object[] convArray = (classValuesAsString ? new String[array.length] : new Class[array.length]); + for (int i = 0; i < array.length; i++) { + convArray[i] = (classValuesAsString ? array[i].getClassName() : + classLoader.loadClass(array[i].getClassName())); + } + value = convArray; + } + else 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; + } + } + result.put(entry.getKey(), value); + } + catch (Exception ex) { + // Class not found - can't resolve class reference in annotation attribute. + } + } + return result; + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java index 4b39bbaea7b..b7e9a1e1209 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java @@ -16,7 +16,7 @@ package org.springframework.core.type.classreading; -import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.springframework.asm.AnnotationVisitor; @@ -26,6 +26,7 @@ import org.springframework.asm.SpringAsmInfo; import org.springframework.asm.Type; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.MethodMetadata; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** @@ -37,6 +38,7 @@ import org.springframework.util.MultiValueMap; * @author Mark Pollack * @author Costin Leau * @author Chris Beams + * @author Phillip Webb * @since 3.0 */ public class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata { @@ -51,7 +53,7 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho protected final MultiValueMap methodMetadataMap; - protected final Map attributeMap = new LinkedHashMap(2); + protected final MultiValueMap attributeMap = new LinkedMultiValueMap(2); public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, ClassLoader classLoader, @@ -93,8 +95,34 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho return this.attributeMap.containsKey(annotationType); } - public AnnotationAttributes getAnnotationAttributes(String annotationType) { - return this.attributeMap.get(annotationType); + public Map getAnnotationAttributes(String annotationType) { + return getAnnotationAttributes(annotationType, false); + } + + public Map getAnnotationAttributes(String annotationType, + boolean classValuesAsString) { + List attributes = this.attributeMap.get(annotationType); + return (attributes == null ? null : AnnotationReadingVisitorUtils.convertClassValues( + this.classLoader, attributes.get(0), classValuesAsString)); + } + + public MultiValueMap getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + public MultiValueMap getAllAnnotationAttributes( + String annotationType, boolean classValuesAsString) { + if(!this.attributeMap.containsKey(annotationType)) { + return null; + } + MultiValueMap allAttributes = new LinkedMultiValueMap(); + for (AnnotationAttributes annotationAttributes : this.attributeMap.get(annotationType)) { + for (Map.Entry entry : AnnotationReadingVisitorUtils.convertClassValues( + this.classLoader, annotationAttributes, classValuesAsString).entrySet()) { + allAttributes.add(entry.getKey(), entry.getValue()); + } + } + return allAttributes; } public String getDeclaringClassName() { diff --git a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java index cb6808114c0..870ccd3beea 100644 --- a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java @@ -18,7 +18,9 @@ package org.springframework.core.type; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -29,10 +31,12 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; import java.util.Set; import org.junit.Test; - import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -45,6 +49,7 @@ import org.springframework.stereotype.Component; * * @author Juergen Hoeller * @author Chris Beams + * @author Phillip Webb */ public class AnnotationMetadataTests { @@ -93,7 +98,7 @@ public class AnnotationMetadataTests { assertThat(metadata.hasAnnotation(Component.class.getName()), is(true)); assertThat(metadata.hasAnnotation(Scope.class.getName()), is(true)); assertThat(metadata.hasAnnotation(SpecialAttr.class.getName()), is(true)); - assertThat(metadata.getAnnotationTypes().size(), is(3)); + assertThat(metadata.getAnnotationTypes().size(), is(5)); assertThat(metadata.getAnnotationTypes().contains(Component.class.getName()), is(true)); assertThat(metadata.getAnnotationTypes().contains(Scope.class.getName()), is(true)); assertThat(metadata.getAnnotationTypes().contains(SpecialAttr.class.getName()), is(true)); @@ -104,6 +109,15 @@ public class AnnotationMetadataTests { AnnotationAttributes scopeAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(Scope.class.getName()); assertThat(scopeAttrs.size(), is(1)); assertThat(scopeAttrs.getString("value"), is("myScope")); + + Set methods = metadata.getAnnotatedMethods(DirectAnnotation.class.getName()); + MethodMetadata method = methods.iterator().next(); + assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value")); + List allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value"); + assertThat(new HashSet(allMeta), is(equalTo(new HashSet(Arrays.asList("direct", "meta"))))); + + assertTrue(metadata.isAnnotated(IsAnnotatedAnnotation.class.getName())); + { // perform tests with classValuesAsString = false (the default) AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName()); assertThat(specialAttrs.size(), is(6)); @@ -137,6 +151,10 @@ public class AnnotationMetadataTests { assertTrue(optionalArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT)); assertArrayEquals(new Class[]{Void.class}, (Class[])optionalArray[0].get("classArray")); assertArrayEquals(new Class[]{Void.class}, optionalArray[0].getClassArray("classArray")); + + assertEquals("direct", metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value")); + allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value"); + assertThat(new HashSet(allMeta), is(equalTo(new HashSet(Arrays.asList("direct", "meta"))))); } { // perform tests with classValuesAsString = true AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true); @@ -161,6 +179,10 @@ public class AnnotationMetadataTests { AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray"); assertArrayEquals(new String[]{Void.class.getName()}, (String[])optionalArray[0].get("classArray")); assertArrayEquals(new String[]{Void.class.getName()}, optionalArray[0].getStringArray("classArray")); + + assertEquals(metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"), "direct"); + allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value"); + assertThat(new HashSet(allMeta), is(equalTo(new HashSet(Arrays.asList("direct", "meta"))))); } } @@ -201,6 +223,29 @@ public class AnnotationMetadataTests { NestedAnno[] optionalArray() default {@NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class)}; } + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface DirectAnnotation { + String value(); + } + + @Target({ ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + public @interface IsAnnotatedAnnotation { + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @DirectAnnotation("meta") + @IsAnnotatedAnnotation + public @interface MetaAnnotation { + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + public @interface MetaMetaAnnotation { + } @Component("myName") @Scope("myScope") @@ -211,6 +256,8 @@ public class AnnotationMetadataTests { @NestedAnno(value = "na1", anEnum = SomeEnum.LABEL2, classArray = {Number.class}) }) @SuppressWarnings({"serial", "unused"}) + @DirectAnnotation("direct") + @MetaMetaAnnotation private static class AnnotatedComponent implements Serializable { @TestAutowired @@ -219,6 +266,11 @@ public class AnnotationMetadataTests { public void doSleep() { } + + @DirectAnnotation("direct") + @MetaMetaAnnotation + public void meta() { + } } }