io.ebean.util.AnnotationUtil Maven / Gradle / Ivy
Show all versions of ebean Show documentation
package io.ebean.util;
import io.ebean.annotation.Formula;
import io.ebean.annotation.Platform;
import io.ebean.annotation.Where;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Annotation utility methods to find annotations recursively - taken from the spring framework.
*/
public class AnnotationUtil {
/**
* Determine if the supplied {@link Annotation} is defined in the core JDK {@code java.lang.annotation} package.
*/
public static boolean isInJavaLangAnnotationPackage(Annotation annotation) {
return annotation.annotationType().getName().startsWith("java.lang.annotation");
}
/**
* Find a single {@link Annotation} of {@code annotationType} on the supplied {@link AnnotatedElement}.
*
* Meta-annotations will be searched if the annotation is not directly present on the supplied element.
*
* Warning: this method operates generically on annotated elements. In other words, this method
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
* It also does not filter out platform dependent annotations!
*/
@SuppressWarnings("unchecked")
public static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType) {
if (annotationType == null) {
return null;
}
// check if directly present, if not, start search for meta-annotations.
Annotation[] anns = annotatedElement.getAnnotations();
if (anns.length == 0) {
return null; // no annotations present, so searching for meta annotations not required
}
// As we need the anns array anyway, we iterate over this instead
// of using annotatedElement.getAnnotation(...) which is synchronized internally
for (Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return (A) ann;
}
}
return findAnnotation(anns, annotationType, new HashSet<>());
}
/**
* Find a single {@link Annotation} of {@code annotationType} on the supplied class.
* Meta-annotations will be searched if the annotation is not directly present on
* the supplied element.
*
Note: this method searches for annotations at class & superClass(es)!
*/
@SuppressWarnings("unchecked")
public static A findAnnotationRecursive(Class> clazz, Class annotationType) {
if (annotationType == null) {
return null;
}
while (clazz != null && clazz != Object.class) {
// check if directly present, if not, start search for meta-annotations.
Annotation[] anns = clazz.getAnnotations();
if (anns.length != 0) {
for (Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return (A) ann;
}
}
A ann = findAnnotation(anns, annotationType, new HashSet<>());
if (ann != null) {
return ann;
}
}
// no meta-annotation present at this class - traverse to superclass
clazz = clazz.getSuperclass();
}
return null;
}
/**
* Finds the first annotation of a type for this platform. (if annotation is platform specific, otherwise first
* found annotation is returned)
*/
public static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType, Platform platform) {
if (annotationType == null) {
return null;
}
Set anns = findAnnotations(annotatedElement, annotationType);
return getPlatformMatchingAnnotation(anns, platform);
}
/**
* Finds all annotations recusively for a class and its superclasses or interfaces.
*/
public static Set findAnnotationsRecursive(Class> clazz, Class annotationType) {
if (annotationType == null) {
return null;
}
Set ret = new LinkedHashSet<>();
Set visited = new HashSet<>();
Set> visitedInterfaces = new HashSet<>();
while (clazz != null && !clazz.getName().startsWith("java.lang.")) {
findMetaAnnotationsRecursive(clazz, annotationType, ret, visited, visitedInterfaces);
clazz = clazz.getSuperclass();
}
return ret;
}
/**
* Searches the interfaces for annotations.
*/
private static void findMetaAnnotationsRecursive(Class> clazz,
Class annotationType, Set ret,
Set visited, Set> visitedInterfaces) {
findMetaAnnotations(clazz, annotationType, ret, visited);
for (Class> iface : clazz.getInterfaces()) {
if (!iface.getName().startsWith("java.lang.") && visitedInterfaces.add(iface)) {
findMetaAnnotationsRecursive(iface, annotationType, ret, visited, visitedInterfaces);
}
}
}
/**
* Perform the search algorithm avoiding endless recursion by tracking which
* annotations have already been visited.
*/
@SuppressWarnings("unchecked")
private static A findAnnotation(Annotation[] anns, Class annotationType, Set visited) {
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
Annotation[] metaAnns = ann.annotationType().getAnnotations();
for (Annotation metaAnn : metaAnns) {
if (metaAnn.annotationType() == annotationType) {
return (A) metaAnn;
}
}
if (metaAnns.length > 0) {
A annotation = findAnnotation(metaAnns, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
}
return null;
}
/**
* Find all {@link Annotation}s of {@code annotationType} on the supplied {@link AnnotatedElement}.
*
* Meta-annotations will be searched if the annotation is not directly present on the supplied element.
*
* Warning: this method operates generically on annotated elements. In other words, this method
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
*/
public static Set findAnnotations(AnnotatedElement annotatedElement, Class annotationType) {
if (annotationType == null) {
return null;
}
Set ret = new LinkedHashSet<>();
findMetaAnnotations(annotatedElement, annotationType, ret, new HashSet<>());
return ret;
}
/**
* Perform the search algorithm avoiding endless recursion by tracking which
* annotations have already been visited.
*/
@SuppressWarnings("unchecked")
private static void findMetaAnnotations(AnnotatedElement annotatedElement, Class annotationType, Set ret, Set visited) {
Annotation[] anns = annotatedElement.getAnnotations();
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
if (ann.annotationType() == annotationType) {
ret.add((A) ann);
} else {
Method repeatableValueMethod = getRepeatableValueMethod(ann, annotationType);
if (repeatableValueMethod != null) {
try {
A[] repeatedAnns = (A[]) repeatableValueMethod.invoke(ann);
for (Annotation repeatedAnn : repeatedAnns) {
ret.add((A) repeatedAnn);
findMetaAnnotations(repeatedAnn.annotationType(), annotationType, ret, visited);
}
} catch (Exception e) { // catch all exceptions (thrown by invoke)
throw new RuntimeException(e);
}
} else {
findMetaAnnotations(ann.annotationType(), annotationType, ret, visited);
}
}
}
}
}
// caches for getRepeatableValueMethod
private static Method getNullMethod() {
try {
return AnnotationUtil.class.getDeclaredMethod("getNullMethod");
} catch (NoSuchMethodException e) {
return null;
}
}
private static final ConcurrentMap valueMethods = new ConcurrentHashMap<>();
// only a non-null-marker the valueMethods - Cache
private static final Method nullMethod = getNullMethod();
/**
* Returns the value()
method for a possible containerAnnotation.
* Method is retuned only, if its signature is array of containingType
.
*/
private static Method getRepeatableValueMethod(
Annotation containerAnnotation, Class containingType) {
Method method = valueMethods.get(containerAnnotation);
if (method == null) {
try {
method = containerAnnotation.annotationType().getMethod("value");
} catch (NoSuchMethodException e) {
method = nullMethod;
} catch (Exception e) {
throw new RuntimeException(e);
}
Method prev = valueMethods.putIfAbsent(containerAnnotation, method);
method = prev == null ? method : prev;
}
if (method != nullMethod) {
Class> retType = method.getReturnType();
if (retType.isArray() && retType.getComponentType() == containingType) {
return method;
}
}
return null;
}
/**
* Finds a suitable annotation from Set anns
for this platform.
* To distinguish between platforms, annotation type T
must define
* a method withthis signature:
*
* Class extends DatabasePlatform>[] platforms() default {};
*
* The finding rules are:
*
* - Check if T has method "platforms" if not, return
ann[0]
*
databasePlatform