blob: 770694ec9b72dc65ba687e7403aeaced5851c684 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.dubbo.common.utils;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static org.apache.dubbo.common.function.Predicates.and;
import static org.apache.dubbo.common.function.Streams.filterAll;
import static org.apache.dubbo.common.function.Streams.filterFirst;
import static org.apache.dubbo.common.utils.ClassUtils.getAllInheritedTypes;
import static org.apache.dubbo.common.utils.ClassUtils.resolveClass;
import static org.apache.dubbo.common.utils.CollectionUtils.first;
import static org.apache.dubbo.common.utils.MethodUtils.findMethod;
import static org.apache.dubbo.common.utils.MethodUtils.invokeMethod;
/**
* Commons Annotation Utilities class
*
* @since 2.7.6
*/
public interface AnnotationUtils {
/**
* Resolve the annotation type by the annotated element and resolved class name
*
* @param annotatedElement the annotated element
* @param annotationClassName the class name of annotation
* @param <A> the type of annotation
* @return If resolved, return the type of annotation, or <code>null</code>
*/
static <A extends Annotation> Class<A> resolveAnnotationType(AnnotatedElement annotatedElement,
String annotationClassName) {
ClassLoader classLoader = annotatedElement.getClass().getClassLoader();
Class<?> annotationType = resolveClass(annotationClassName, classLoader);
if (annotationType == null || !Annotation.class.isAssignableFrom(annotationType)) {
return null;
}
return (Class<A>) annotationType;
}
/**
* Is the specified type a generic {@link Class type}
*
* @param annotatedElement the annotated element
* @return if <code>annotatedElement</code> is the {@link Class}, return <code>true</code>, or <code>false</code>
* @see ElementType#TYPE
*/
static boolean isType(AnnotatedElement annotatedElement) {
return annotatedElement instanceof Class;
}
/**
* Is the type of specified annotation same to the expected type?
*
* @param annotation the specified {@link Annotation}
* @param annotationType the expected annotation type
* @return if same, return <code>true</code>, or <code>false</code>
*/
static boolean isSameType(Annotation annotation, Class<? extends Annotation> annotationType) {
if (annotation == null || annotationType == null) {
return false;
}
return Objects.equals(annotation.annotationType(), annotationType);
}
/**
* Build an instance of {@link Predicate} to excluded annotation type
*
* @param excludedAnnotationType excluded annotation type
* @return non-null
*/
static Predicate<Annotation> excludedType(Class<? extends Annotation> excludedAnnotationType) {
return annotation -> !isSameType(annotation, excludedAnnotationType);
}
/**
* Get the attribute from the specified {@link Annotation annotation}
*
* @param annotation the specified {@link Annotation annotation}
* @param attributeName the attribute name
* @param <T> the type of attribute
* @return the attribute value
* @throws IllegalArgumentException If the attribute name can't be found
*/
static <T> T getAttribute(Annotation annotation, String attributeName) throws IllegalArgumentException {
return annotation == null ? null : invokeMethod(annotation, attributeName);
}
/**
* Get the "value" attribute from the specified {@link Annotation annotation}
*
* @param annotation the specified {@link Annotation annotation}
* @param <T> the type of attribute
* @return the value of "value" attribute
* @throws IllegalArgumentException If the attribute name can't be found
*/
static <T> T getValue(Annotation annotation) throws IllegalArgumentException {
return getAttribute(annotation, "value");
}
/**
* Get the {@link Annotation} from the specified {@link AnnotatedElement the annotated element} and
* {@link Annotation annotation} class name
*
* @param annotatedElement {@link AnnotatedElement}
* @param annotationClassName the class name of annotation
* @param <A> The type of {@link Annotation}
* @return the {@link Annotation} if found
* @throws ClassCastException If the {@link Annotation annotation} type that client requires can't match actual type
*/
static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, String annotationClassName)
throws ClassCastException {
Class<? extends Annotation> annotationType = resolveAnnotationType(annotatedElement, annotationClassName);
if (annotationType == null) {
return null;
}
return (A) annotatedElement.getAnnotation(annotationType);
}
/**
* Get annotations that are <em>directly present</em> on this element.
* This method ignores inherited annotations.
*
* @param annotatedElement the annotated element
* @param annotationsToFilter the annotations to filter
* @return non-null read-only {@link List}
*/
static List<Annotation> getDeclaredAnnotations(AnnotatedElement annotatedElement,
Predicate<Annotation>... annotationsToFilter) {
if (annotatedElement == null) {
return emptyList();
}
return unmodifiableList(filterAll(asList(annotatedElement.getDeclaredAnnotations()), annotationsToFilter));
}
/**
* Get all directly declared annotations of the the annotated element, not including
* meta annotations.
*
* @param annotatedElement the annotated element
* @param annotationsToFilter the annotations to filter
* @return non-null read-only {@link List}
*/
static List<Annotation> getAllDeclaredAnnotations(AnnotatedElement annotatedElement,
Predicate<Annotation>... annotationsToFilter) {
if (isType(annotatedElement)) {
return getAllDeclaredAnnotations((Class) annotatedElement, annotationsToFilter);
} else {
return getDeclaredAnnotations(annotatedElement, annotationsToFilter);
}
}
/**
* Get all directly declared annotations of the specified type and its' all hierarchical types, not including
* meta annotations.
*
* @param type the specified type
* @param annotationsToFilter the annotations to filter
* @return non-null read-only {@link List}
*/
static List<Annotation> getAllDeclaredAnnotations(Class<?> type, Predicate<Annotation>... annotationsToFilter) {
if (type == null) {
return emptyList();
}
List<Annotation> allAnnotations = new LinkedList<>();
// All types
Set<Class<?>> allTypes = new LinkedHashSet<>();
// Add current type
allTypes.add(type);
// Add all inherited types
allTypes.addAll(getAllInheritedTypes(type, t -> !Object.class.equals(t)));
for (Class<?> t : allTypes) {
allAnnotations.addAll(getDeclaredAnnotations(t, annotationsToFilter));
}
return unmodifiableList(allAnnotations);
}
/**
* Get the meta-annotated {@link Annotation annotations} directly, excluding {@link Target}, {@link Retention}
* and {@link Documented}
*
* @param annotationType the {@link Annotation annotation} type
* @param metaAnnotationsToFilter the meta annotations to filter
* @return non-null read-only {@link List}
*/
static List<Annotation> getMetaAnnotations(Class<? extends Annotation> annotationType,
Predicate<Annotation>... metaAnnotationsToFilter) {
return getDeclaredAnnotations(annotationType,
// Excludes the Java native annotation types or it causes the stack overflow, e.g,
// @Target annotates itself
excludedType(Target.class),
excludedType(Retention.class),
excludedType(Documented.class),
// Add other predicates
and(metaAnnotationsToFilter)
);
}
/**
* Get all meta annotations from the specified {@link Annotation annotation} type
*
* @param annotationType the {@link Annotation annotation} type
* @param annotationsToFilter the annotations to filter
* @return non-null read-only {@link List}
*/
static List<Annotation> getAllMetaAnnotations(Class<? extends Annotation> annotationType,
Predicate<Annotation>... annotationsToFilter) {
List<Annotation> allMetaAnnotations = new LinkedList<>();
List<Annotation> metaAnnotations = getMetaAnnotations(annotationType);
allMetaAnnotations.addAll(metaAnnotations);
for (Annotation metaAnnotation : metaAnnotations) {
// Get the nested meta annotations recursively
allMetaAnnotations.addAll(getAllMetaAnnotations(metaAnnotation.annotationType()));
}
return unmodifiableList(filterAll(allMetaAnnotations, annotationsToFilter));
}
/**
* Find the annotation that is annotated on the specified element may be a meta-annotation
*
* @param annotatedElement the annotated element
* @param annotationClassName the class name of annotation
* @param <A> the required type of annotation
* @return If found, return first matched-type {@link Annotation annotation}, or <code>null</code>
*/
static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, String annotationClassName) {
return findAnnotation(annotatedElement, resolveAnnotationType(annotatedElement, annotationClassName));
}
/**
* Find the annotation that is annotated on the specified element may be a meta-annotation
*
* @param annotatedElement the annotated element
* @param annotationType the type of annotation
* @param <A> the required type of annotation
* @return If found, return first matched-type {@link Annotation annotation}, or <code>null</code>
*/
static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
return (A) filterFirst(getAllDeclaredAnnotations(annotatedElement), a -> isSameType(a, annotationType));
}
/**
* Find the meta annotations from the the {@link Annotation annotation} type by meta annotation type
*
* @param annotationType the {@link Annotation annotation} type
* @param metaAnnotationType the meta annotation type
* @param <A> the type of required annotation
* @return if found, return all matched results, or get an {@link Collections#emptyList() empty list}
*/
static <A extends Annotation> List<A> findMetaAnnotations(Class<? extends Annotation> annotationType,
Class<A> metaAnnotationType) {
return (List<A>) getAllMetaAnnotations(annotationType, a -> isSameType(a, metaAnnotationType));
}
/**
* Find the meta annotations from the the the annotated element by meta annotation type
*
* @param annotatedElement the annotated element
* @param metaAnnotationType the meta annotation type
* @param <A> the type of required annotation
* @return if found, return all matched results, or get an {@link Collections#emptyList() empty list}
*/
static <A extends Annotation> List<A> findMetaAnnotations(AnnotatedElement annotatedElement,
Class<A> metaAnnotationType) {
List<A> metaAnnotations = new LinkedList<>();
for (Annotation annotation : getAllDeclaredAnnotations(annotatedElement)) {
metaAnnotations.addAll(findMetaAnnotations(annotation.annotationType(), metaAnnotationType));
}
return unmodifiableList(metaAnnotations);
}
/**
* Find the meta annotation from the annotated element by meta annotation type
*
* @param annotatedElement the annotated element
* @param metaAnnotationClassName the class name of meta annotation
* @param <A> the type of required annotation
* @return {@link #findMetaAnnotation(Class, Class)}
*/
static <A extends Annotation> A findMetaAnnotation(AnnotatedElement annotatedElement,
String metaAnnotationClassName) {
return findMetaAnnotation(annotatedElement, resolveAnnotationType(annotatedElement, metaAnnotationClassName));
}
/**
* Find the meta annotation from the annotation type by meta annotation type
*
* @param annotationType the {@link Annotation annotation} type
* @param metaAnnotationType the meta annotation type
* @param <A> the type of required annotation
* @return If found, return the {@link CollectionUtils#first(Collection)} matched result, return <code>null</code>.
* If it requires more result, please consider to use {@link #findMetaAnnotations(Class, Class)}
* @see #findMetaAnnotations(Class, Class)
*/
static <A extends Annotation> A findMetaAnnotation(Class<? extends Annotation> annotationType,
Class<A> metaAnnotationType) {
return first(findMetaAnnotations(annotationType, metaAnnotationType));
}
/**
* Find the meta annotation from the annotated element by meta annotation type
*
* @param annotatedElement the annotated element
* @param metaAnnotationType the meta annotation type
* @param <A> the type of required annotation
* @return If found, return the {@link CollectionUtils#first(Collection)} matched result, return <code>null</code>.
* If it requires more result, please consider to use {@link #findMetaAnnotations(AnnotatedElement, Class)}
* @see #findMetaAnnotations(AnnotatedElement, Class)
*/
static <A extends Annotation> A findMetaAnnotation(AnnotatedElement annotatedElement, Class<A> metaAnnotationType) {
return first(findMetaAnnotations(annotatedElement, metaAnnotationType));
}
/**
* Tests the annotated element is annotated the specified annotations or not
*
* @param type the annotated type
* @param matchAll If <code>true</code>, checking all annotation types are present or not, or match any
* @param annotationTypes the specified annotation types
* @return If the specified annotation types are present, return <code>true</code>, or <code>false</code>
*/
static boolean isAnnotationPresent(Class<?> type,
boolean matchAll,
Class<? extends Annotation>... annotationTypes) {
int size = annotationTypes == null ? 0 : annotationTypes.length;
if (size < 1) {
return false;
}
int presentCount = 0;
for (int i = 0; i < size; i++) {
Class<? extends Annotation> annotationType = annotationTypes[i];
if (findAnnotation(type, annotationType) != null || findMetaAnnotation(type, annotationType) != null) {
presentCount++;
}
}
return matchAll ? presentCount == size : presentCount > 0;
}
/**
* Tests the annotated element is annotated the specified annotation or not
*
* @param type the annotated type
* @param annotationType the class of annotation
* @return If the specified annotation type is present, return <code>true</code>, or <code>false</code>
*/
static boolean isAnnotationPresent(Class<?> type, Class<? extends Annotation> annotationType) {
return isAnnotationPresent(type, true, annotationType);
}
/**
* Tests the annotated element is present any specified annotation types
*
* @param annotatedElement the annotated element
* @param annotationClassName the class name of annotation
* @return If any specified annotation types are present, return <code>true</code>
*/
static boolean isAnnotationPresent(AnnotatedElement annotatedElement, String annotationClassName) {
ClassLoader classLoader = annotatedElement.getClass().getClassLoader();
Class<?> resolvedType = resolveClass(annotationClassName, classLoader);
if (!Annotation.class.isAssignableFrom(resolvedType)) {
return false;
}
return isAnnotationPresent(annotatedElement, (Class<? extends Annotation>) resolvedType);
}
/**
* Tests the annotated element is present any specified annotation types
*
* @param annotatedElement the annotated element
* @param annotationType the class of annotation
* @return If any specified annotation types are present, return <code>true</code>
*/
static boolean isAnnotationPresent(AnnotatedElement annotatedElement, Class<? extends Annotation> annotationType) {
if (isType(annotatedElement)) {
return isAnnotationPresent((Class) annotatedElement, annotationType);
} else {
return annotatedElement.isAnnotationPresent(annotationType) ||
findMetaAnnotation(annotatedElement, annotationType) != null; // to find meta-annotation
}
}
/**
* Tests the annotated element is annotated all specified annotations or not
*
* @param type the annotated type
* @param annotationTypes the specified annotation types
* @return If the specified annotation types are present, return <code>true</code>, or <code>false</code>
*/
static boolean isAllAnnotationPresent(Class<?> type, Class<? extends Annotation>... annotationTypes) {
return isAnnotationPresent(type, true, annotationTypes);
}
/**
* Tests the annotated element is present any specified annotation types
*
* @param type the annotated type
* @param annotationTypes the specified annotation types
* @return If any specified annotation types are present, return <code>true</code>
*/
static boolean isAnyAnnotationPresent(Class<?> type,
Class<? extends Annotation>... annotationTypes) {
return isAnnotationPresent(type, false, annotationTypes);
}
/**
* Get the default value of attribute on the specified annotation
*
* @param annotation {@link Annotation} object
* @param attributeName the name of attribute
* @param <T> the type of value
* @return <code>null</code> if not found
* @since 2.7.9
*/
static <T> T getDefaultValue(Annotation annotation, String attributeName) {
return getDefaultValue(annotation.annotationType(), attributeName);
}
/**
* Get the default value of attribute on the specified annotation
*
* @param annotationType the type of {@link Annotation}
* @param attributeName the name of attribute
* @param <T> the type of value
* @return <code>null</code> if not found
* @since 2.7.9
*/
static <T> T getDefaultValue(Class<? extends Annotation> annotationType, String attributeName) {
Method method = findMethod(annotationType, attributeName);
return (T) (method == null ? null : method.getDefaultValue());
}
/**
* Filter default value of Annotation type
* @param annotationType annotation type from {@link Annotation#annotationType()}
* @param attributes
* @return
*/
static Map<String, Object> filterDefaultValues(Class<? extends Annotation> annotationType, Map<String, Object> attributes) {
Map<String, Object> filteredAttributes = new LinkedHashMap<>(attributes.size());
attributes.forEach((key,val) -> {
if (!Objects.deepEquals(val, getDefaultValue(annotationType, key))) {
filteredAttributes.put(key, val);
}
});
// remove void class, compatible with spring 3.x
Object interfaceClassValue = filteredAttributes.get("interfaceClass");
if (interfaceClassValue instanceof String && StringUtils.isEquals((String) interfaceClassValue, "void")) {
filteredAttributes.remove("interfaceClass");
}
return filteredAttributes;
}
/**
* Filter default value of Annotation type
* @param annotation
* @param attributes
* @return
*/
static Map<String, Object> filterDefaultValues(Annotation annotation, Map<String, Object> attributes) {
return filterDefaultValues(annotation.annotationType(), attributes);
}
/**
* Get attributes of annotation
* @param annotation
* @return
*/
static Map<String, Object> getAttributes(Annotation annotation, boolean filterDefaultValue) {
Class<?> annotationType = annotation.annotationType();
Method[] methods = annotationType.getMethods();
Map<String, Object> attributes = new LinkedHashMap<>(methods.length);
for (Method method : methods) {
try {
if (method.getDeclaringClass() == Annotation.class) {
continue;
}
String name = method.getName();
Object value = method.invoke(annotation);
if (!filterDefaultValue || !Objects.deepEquals(value, method.getDefaultValue())) {
attributes.put(name, value);
}
} catch (Exception e) {
throw new IllegalStateException("get attribute value of annotation failed: " + method, e);
}
}
return attributes;
}
}