From 90cf3fddc905c8bb9e24ecb2687120ab9c8419e6 Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Thu, 5 Oct 2017 00:00:44 +1300 Subject: [PATCH] #1151 - Refactor add util AnnotationUtil ... moving common annotation searching methods there --- .../config/AbstractNamingConvention.java | 8 +- .../java/io/ebean/util/AnnotationUtil.java | 270 ++++++++++++++++++ .../server/deploy/parse/AnnotationBase.java | 244 ++-------------- 3 files changed, 293 insertions(+), 229 deletions(-) create mode 100644 src/main/java/io/ebean/util/AnnotationUtil.java diff --git a/src/main/java/io/ebean/config/AbstractNamingConvention.java b/src/main/java/io/ebean/config/AbstractNamingConvention.java index e123bec55..870c2e2b2 100644 --- a/src/main/java/io/ebean/config/AbstractNamingConvention.java +++ b/src/main/java/io/ebean/config/AbstractNamingConvention.java @@ -1,7 +1,7 @@ package io.ebean.config; import io.ebean.config.dbplatform.DatabasePlatform; -import io.ebeaninternal.server.deploy.parse.AnnotationBase; +import io.ebean.util.AnnotationUtil; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -219,8 +219,8 @@ public abstract class AbstractNamingConvention implements NamingConvention { * Return true if this class is part of entity inheritance. */ protected boolean hasInheritance(Class supCls) { - return AnnotationBase.findAnnotationRecursive(supCls, Inheritance.class) != null - || AnnotationBase.findAnnotation(supCls, DiscriminatorValue.class) != null; + return AnnotationUtil.findAnnotationRecursive(supCls, Inheritance.class) != null + || AnnotationUtil.findAnnotation(supCls, DiscriminatorValue.class) != null; } @@ -253,7 +253,7 @@ public abstract class AbstractNamingConvention implements NamingConvention { */ protected TableName getTableNameFromAnnotation(Class beanClass) { - final Table t = AnnotationBase.findAnnotationRecursive(beanClass, Table.class); + final Table t = AnnotationUtil.findAnnotationRecursive(beanClass, Table.class); // Take the annotation if defined if (t != null && !isEmpty(t.name())) { diff --git a/src/main/java/io/ebean/util/AnnotationUtil.java b/src/main/java/io/ebean/util/AnnotationUtil.java new file mode 100644 index 000000000..bad0b6dfa --- /dev/null +++ b/src/main/java/io/ebean/util/AnnotationUtil.java @@ -0,0 +1,270 @@ +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! + */ + public static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType) { + if (annotationType == null) { + return null; + } + // check if directly present, if not, start search for meta-annotations. + A ann = annotatedElement.getAnnotation(annotationType); + if (ann != null) { + return ann; + } else { + return findAnnotation(annotatedElement, 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)! + */ + public static A findAnnotationRecursive(Class clazz, Class annotationType) { + if (annotationType == null) { + return null; + } + // check if directly present, if not, start search for meta-annotations. + A ann = clazz.getAnnotation(annotationType); + if (ann != null) { + return ann; + } else { + while (clazz != null && clazz != Object.class) { + ann = findAnnotation(clazz, 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. + */ + public static Set findAnnotationsRecursive(Class clazz, Class annotationType) { + if (annotationType == null) { + return null; + } + Set ret = new LinkedHashSet<>(); + Set visited = new HashSet<>(); + while (clazz != null && clazz != Object.class) { + findMetaAnnotations(clazz, annotationType, ret, visited); + clazz = clazz.getSuperclass(); + } + return ret; + } + + /** + * Perform the search algorithm avoiding endless recursion by tracking which + * annotations have already been visited. + */ + @SuppressWarnings("unchecked") + private static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType, Set visited) { + + Annotation[] anns = annotatedElement.getAnnotations(); // directly annotatated or inherited + for (Annotation ann : anns) { + if (ann.annotationType() == annotationType) { + return (A) ann; + } + } + for (Annotation ann : anns) { + if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) { + A annotation = findAnnotation(ann.annotationType(), 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[] platforms() default {}; + *

+ * The finding rules are: + *
    + *
  1. Check if T has method "platforms" if not, return ann[0] + *
  2. find the annotation that is defined for databasePlatform
  3. + *
  4. otherwise return the annotation for default platform (platforms = {})
  5. + *
  6. return null + *
+ * (This mechanism is currently used by {@link Where} and {@link Formula}) + */ + public static T getPlatformMatchingAnnotation(Set anns, Platform matchPlatform) { + if (anns.isEmpty()) { + return null; + } + Method getPlatformsMethod = null; + T fallback = null; + for (T ann : anns) { + try { + if (getPlatformsMethod == null) { + getPlatformsMethod = ann.getClass().getMethod("platforms"); + } + if (!Platform[].class.isAssignableFrom(getPlatformsMethod.getReturnType())) { + return ann; + } + Platform[] platforms = (Platform[]) getPlatformsMethod.invoke(ann); + if (platforms.length == 0) { + fallback = ann; + } else { + for (Platform platform : platforms) { + if (matchPlatform == platform) { + return ann; + } + } + } + } catch (NoSuchMethodException e) { + return ann; // not platform specific - return first one + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return fallback; + } + +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationBase.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationBase.java index 790dc7b96..eacae2cd3 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationBase.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationBase.java @@ -1,10 +1,9 @@ package io.ebeaninternal.server.deploy.parse; import io.ebean.annotation.Platform; -import io.ebean.annotation.Formula; -import io.ebean.annotation.Where; import io.ebean.config.NamingConvention; import io.ebean.config.dbplatform.DatabasePlatform; +import io.ebean.util.AnnotationUtil; import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; import javax.persistence.JoinColumn; @@ -13,34 +12,30 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; 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; /** * Provides some base methods for processing deployment annotations. All findAnnotation* methods * are capable to search for meta-annotations (annotation that has an other annotation) - * + *

*

search algorithm for ONE annotation:

*
    - *
  • Check if annotation is direct on the property
  • - *
  • if not found: Check all annotations at the annotateElement - * if they have the needed annotation as meta annotation
  • - *
  • if not found: go up to super class and try again - * (only findAnnotationRecursive)
  • + *
  • Check if annotation is direct on the property
  • + *
  • if not found: Check all annotations at the annotateElement + * if they have the needed annotation as meta annotation
  • + *
  • if not found: go up to super class and try again + * (only findAnnotationRecursive)
  • *
* DFS (Depth-First-Search) is used. The algorithm is the same as it is used in Spring-Framework, * as the code is taken from there. - * + *

*

search algoritm for a Set<Annotation> works a litte bit different, as it does not stop * on the first match, but continues searching down to the last corner to find all annotations.

- * + *

*

To prevent endless recursion, the search algoritm tracks all visited annotations

- * + *

*

Supports also "java 1.6 repeatable containers" like{@link JoinColumn} / {@link JoinColumns}.

- * + *

*

This means, searching for JoinColumn will find them also if they are inside a * JoinColumns annotation

*/ @@ -78,9 +73,9 @@ public abstract class AnnotationBase { *

*

* If a repeatable annotation class is specified and the annotation is platform - * specific(see {@link #getPlatformMatchingAnnotation(Set, Platform)}), then the platform specific - * annotation is returned. Otherwise the first annotation is returned. Note that you need no longer - * handle "java 1.6 repeatable containers" like {@link JoinColumn} / {@link JoinColumns} yourself. + * specific then the platform specific annotation is returned. Otherwise the first annotation + * is returned. Note that you need no longer handle "java 1.6 repeatable containers" + * like {@link JoinColumn} / {@link JoinColumns} yourself. *

*

*/ @@ -135,16 +130,6 @@ public abstract class AnnotationBase { return a; } - - // this code is taken from the spring framework to find annotations recursively - - /** - * 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}. *

@@ -155,16 +140,7 @@ public abstract class AnnotationBase { * It also does not filter out platform dependent annotations! */ public static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType) { - if (annotationType == null) { - return null; - } - // check if directly present, if not, start search for meta-annotations. - A ann = annotatedElement.getAnnotation(annotationType); - if (ann != null) { - return ann; - } else { - return findAnnotation(annotatedElement, annotationType, new HashSet<>()); - } + return AnnotationUtil.findAnnotation(annotatedElement, annotationType); } /** @@ -174,24 +150,7 @@ public abstract class AnnotationBase { *

Note: this method searches for annotations at class & superClass(es)! */ public static A findAnnotationRecursive(Class clazz, Class annotationType) { - if (annotationType == null) { - return null; - } - // check if directly present, if not, start search for meta-annotations. - A ann = clazz.getAnnotation(annotationType); - if (ann != null) { - return ann; - } else { - while (clazz != null && clazz != Object.class) { - ann = findAnnotation(clazz, annotationType, new HashSet<>()); - if (ann != null) { - return ann; - } - // no meta-annotation present at this class - traverse to superclass - clazz = clazz.getSuperclass(); - } - return null; - } + return AnnotationUtil.findAnnotationRecursive(clazz, annotationType); } /** @@ -199,51 +158,14 @@ public abstract class AnnotationBase { * 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); + return AnnotationUtil.findAnnotation(annotatedElement, annotationType, platform); } /** * Finds all annotations recusively for a class and its superclasses. */ public static Set findAnnotationsRecursive(Class clazz, Class annotationType) { - if (annotationType == null) { - return null; - } - Set ret = new LinkedHashSet<>(); - Set visited = new HashSet<>(); - while (clazz != null && clazz != Object.class) { - findMetaAnnotations(clazz, annotationType, ret, visited); - clazz = clazz.getSuperclass(); - } - return ret; - } - - /** - * Perform the search algorithm avoiding endless recursion by tracking which - * annotations have already been visited. - */ - @SuppressWarnings("unchecked") - private static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType, Set visited) { - - Annotation[] anns = annotatedElement.getAnnotations(); // directly annotatated or inherited - for (Annotation ann : anns) { - if (ann.annotationType() == annotationType) { - return (A) ann; - } - } - for (Annotation ann : anns) { - if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) { - A annotation = findAnnotation(ann.annotationType(), annotationType, visited); - if (annotation != null) { - return annotation; - } - } - } - return null; + return AnnotationUtil.findAnnotationsRecursive(clazz, annotationType); } /** @@ -255,135 +177,7 @@ public abstract class AnnotationBase { * 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 AnnotationBase.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[] platforms() default {}; - *

- * The finding rules are: - *
    - *
  1. Check if T has method "platforms" if not, return ann[0] - *
  2. find the annotation that is defined for databasePlatform
  3. - *
  4. otherwise return the annotation for default platform (platforms = {})
  5. - *
  6. return null - *
- * (This mechanism is currently used by {@link Where} and {@link Formula}) - */ - public static T getPlatformMatchingAnnotation(Set anns, Platform matchPlatform) { - if (anns.isEmpty()) { - return null; - } - Method getPlatformsMethod = null; - T fallback = null; - for (T ann : anns) { - try { - if (getPlatformsMethod == null) { - getPlatformsMethod = ann.getClass().getMethod("platforms"); - } - if (!Platform[].class.isAssignableFrom(getPlatformsMethod.getReturnType())) { - return ann; - } - Platform[] platforms = (Platform[]) getPlatformsMethod.invoke(ann); - if (platforms.length == 0) { - fallback = ann; - } else { - for (Platform platform : platforms) { - if (matchPlatform == platform) { - return ann; - } - } - } - } catch (NoSuchMethodException e) { - return ann; // not platform specific - return first one - } catch (Exception e) { - throw new RuntimeException(e); - } - } - return fallback; + return AnnotationUtil.findAnnotations(annotatedElement, annotationType); } }