/* * 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 com.freetmp.common.annotation; import com.freetmp.common.util.LinkedMultiValueMap; import com.freetmp.common.util.MultiValueMap; 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; /** * 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); } }