001package io.ebean.util; 002 003import io.ebean.annotation.Formula; 004import io.ebean.annotation.Platform; 005import io.ebean.annotation.Where; 006 007import java.lang.annotation.Annotation; 008import java.lang.reflect.AnnotatedElement; 009import java.lang.reflect.Method; 010import java.util.HashSet; 011import java.util.LinkedHashSet; 012import java.util.Set; 013import java.util.concurrent.ConcurrentHashMap; 014import java.util.concurrent.ConcurrentMap; 015 016/** 017 * Annotation utility methods to find annotations recursively - taken from the spring framework. 018 */ 019public class AnnotationUtil { 020 021 /** 022 * Determine if the supplied {@link Annotation} is defined in the core JDK {@code java.lang.annotation} package. 023 */ 024 public static boolean isInJavaLangAnnotationPackage(Annotation annotation) { 025 return annotation.annotationType().getName().startsWith("java.lang.annotation"); 026 } 027 028 /** 029 * Find a single {@link Annotation} of {@code annotationType} on the supplied {@link AnnotatedElement}. 030 * <p> 031 * Meta-annotations will be searched if the annotation is not <em>directly present</em> on the supplied element. 032 * <p> 033 * <strong>Warning</strong>: this method operates generically on annotated elements. In other words, this method 034 * does not execute specialized search algorithms for classes or methods. It only traverses through Annotations! 035 * It also does not filter out platform dependent annotations! 036 */ 037 @SuppressWarnings("unchecked") 038 public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) { 039 if (annotationType == null) { 040 return null; 041 } 042 // check if directly present, if not, start search for meta-annotations. 043 Annotation[] anns = annotatedElement.getAnnotations(); 044 if (anns.length == 0) { 045 return null; // no annotations present, so searching for meta annotations not required 046 } 047 048 // As we need the anns array anyway, we iterate over this instead 049 // of using annotatedElement.getAnnotation(...) which is synchronized internally 050 for (Annotation ann : anns) { 051 if (ann.annotationType() == annotationType) { 052 return (A) ann; 053 } 054 } 055 056 return findAnnotation(anns, annotationType, new HashSet<>()); 057 058 } 059 060 /** 061 * Find a single {@link Annotation} of {@code annotationType} on the supplied class. 062 * <p>Meta-annotations will be searched if the annotation is not directly present on 063 * the supplied element. 064 * <p><strong>Note</strong>: this method searches for annotations at class & superClass(es)! 065 */ 066 @SuppressWarnings("unchecked") 067 public static <A extends Annotation> A findAnnotationRecursive(Class<?> clazz, Class<A> annotationType) { 068 if (annotationType == null) { 069 return null; 070 } 071 072 while (clazz != null && clazz != Object.class) { 073 // check if directly present, if not, start search for meta-annotations. 074 Annotation[] anns = clazz.getAnnotations(); 075 if (anns.length != 0) { 076 for (Annotation ann : anns) { 077 if (ann.annotationType() == annotationType) { 078 return (A) ann; 079 } 080 } 081 082 A ann = findAnnotation(anns, annotationType, new HashSet<>()); 083 if (ann != null) { 084 return ann; 085 } 086 } 087 // no meta-annotation present at this class - traverse to superclass 088 clazz = clazz.getSuperclass(); 089 } 090 return null; 091 092 } 093 094 /** 095 * Finds the first annotation of a type for this platform. (if annotation is platform specific, otherwise first 096 * found annotation is returned) 097 */ 098 public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType, Platform platform) { 099 if (annotationType == null) { 100 return null; 101 } 102 Set<A> anns = findAnnotations(annotatedElement, annotationType); 103 return getPlatformMatchingAnnotation(anns, platform); 104 } 105 106 /** 107 * Finds all annotations recusively for a class and its superclasses or interfaces. 108 */ 109 public static <A extends Annotation> Set<A> findAnnotationsRecursive(Class<?> clazz, Class<A> annotationType) { 110 if (annotationType == null) { 111 return null; 112 } 113 Set<A> ret = new LinkedHashSet<>(); 114 Set<Annotation> visited = new HashSet<>(); 115 Set<Class<?>> visitedInterfaces = new HashSet<>(); 116 while (clazz != null && !clazz.getName().startsWith("java.lang.")) { 117 findMetaAnnotationsRecursive(clazz, annotationType, ret, visited, visitedInterfaces); 118 clazz = clazz.getSuperclass(); 119 } 120 return ret; 121 } 122 123 /** 124 * Searches the interfaces for annotations. 125 */ 126 private static <A extends Annotation> void findMetaAnnotationsRecursive(Class<?> clazz, 127 Class<A> annotationType, Set<A> ret, 128 Set<Annotation> visited, Set<Class<?>> visitedInterfaces) { 129 findMetaAnnotations(clazz, annotationType, ret, visited); 130 for (Class<?> iface : clazz.getInterfaces()) { 131 if (!iface.getName().startsWith("java.lang.") && visitedInterfaces.add(iface)) { 132 findMetaAnnotationsRecursive(iface, annotationType, ret, visited, visitedInterfaces); 133 } 134 } 135 } 136 137 /** 138 * Perform the search algorithm avoiding endless recursion by tracking which 139 * annotations have already been visited. 140 */ 141 @SuppressWarnings("unchecked") 142 private static <A extends Annotation> A findAnnotation(Annotation[] anns, Class<A> annotationType, Set<Annotation> visited) { 143 144 145 for (Annotation ann : anns) { 146 if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) { 147 Annotation[] metaAnns = ann.annotationType().getAnnotations(); 148 for (Annotation metaAnn : metaAnns) { 149 if (metaAnn.annotationType() == annotationType) { 150 return (A) metaAnn; 151 } 152 } 153 if (metaAnns.length > 0) { 154 A annotation = findAnnotation(metaAnns, annotationType, visited); 155 if (annotation != null) { 156 return annotation; 157 } 158 } 159 } 160 } 161 return null; 162 } 163 164 /** 165 * Find all {@link Annotation}s of {@code annotationType} on the supplied {@link AnnotatedElement}. 166 * <p> 167 * Meta-annotations will be searched if the annotation is not <em>directly present</em> on the supplied element. 168 * <p> 169 * <strong>Warning</strong>: this method operates generically on annotated elements. In other words, this method 170 * does not execute specialized search algorithms for classes or methods. It only traverses through Annotations! 171 */ 172 public static <A extends Annotation> Set<A> findAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType) { 173 if (annotationType == null) { 174 return null; 175 } 176 Set<A> ret = new LinkedHashSet<>(); 177 findMetaAnnotations(annotatedElement, annotationType, ret, new HashSet<>()); 178 return ret; 179 } 180 181 /** 182 * Perform the search algorithm avoiding endless recursion by tracking which 183 * annotations have already been visited. 184 */ 185 @SuppressWarnings("unchecked") 186 private static <A extends Annotation> void findMetaAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType, Set<A> ret, Set<Annotation> visited) { 187 188 Annotation[] anns = annotatedElement.getAnnotations(); 189 for (Annotation ann : anns) { 190 if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) { 191 if (ann.annotationType() == annotationType) { 192 ret.add((A) ann); 193 } else { 194 Method repeatableValueMethod = getRepeatableValueMethod(ann, annotationType); 195 if (repeatableValueMethod != null) { 196 try { 197 A[] repeatedAnns = (A[]) repeatableValueMethod.invoke(ann); 198 for (Annotation repeatedAnn : repeatedAnns) { 199 ret.add((A) repeatedAnn); 200 findMetaAnnotations(repeatedAnn.annotationType(), annotationType, ret, visited); 201 } 202 } catch (Exception e) { // catch all exceptions (thrown by invoke) 203 throw new RuntimeException(e); 204 } 205 } else { 206 findMetaAnnotations(ann.annotationType(), annotationType, ret, visited); 207 } 208 } 209 } 210 } 211 } 212 213 // caches for getRepeatableValueMethod 214 private static Method getNullMethod() { 215 try { 216 return AnnotationUtil.class.getDeclaredMethod("getNullMethod"); 217 } catch (NoSuchMethodException e) { 218 return null; 219 } 220 } 221 222 private static final ConcurrentMap<Annotation, Method> valueMethods = new ConcurrentHashMap<>(); 223 // only a non-null-marker the valueMethods - Cache 224 private static final Method nullMethod = getNullMethod(); 225 226 227 /** 228 * Returns the <code>value()</code> method for a possible containerAnnotation. 229 * Method is retuned only, if its signature is <code>array of containingType</code>. 230 */ 231 private static <A extends Annotation> Method getRepeatableValueMethod( 232 Annotation containerAnnotation, Class<A> containingType) { 233 234 Method method = valueMethods.get(containerAnnotation); 235 if (method == null) { 236 try { 237 method = containerAnnotation.annotationType().getMethod("value"); 238 } catch (NoSuchMethodException e) { 239 method = nullMethod; 240 } catch (Exception e) { 241 throw new RuntimeException(e); 242 } 243 Method prev = valueMethods.putIfAbsent(containerAnnotation, method); 244 method = prev == null ? method : prev; 245 } 246 if (method != nullMethod) { 247 Class<?> retType = method.getReturnType(); 248 if (retType.isArray() && retType.getComponentType() == containingType) { 249 return method; 250 } 251 } 252 return null; 253 } 254 255 256 /** 257 * Finds a suitable annotation from <code>Set<T> anns</code> for this platform. 258 * To distinguish between platforms, annotation type <code>T</code> must define 259 * a method withthis signature: 260 * <p> 261 * <code>Class<? extends DatabasePlatform>[] platforms() default {};</code> 262 * </p> 263 * The finding rules are: 264 * <ol> 265 * <li>Check if T has method "platforms" if not, return <code>ann[0]</code></code> 266 * <li>find the annotation that is defined for <code>databasePlatform</code></li> 267 * <li>otherwise return the annotation for default platform (platforms = {})</li> 268 * <li>return null 269 * </ol> 270 * (This mechanism is currently used by {@link Where} and {@link Formula}) 271 */ 272 public static <T extends Annotation> T getPlatformMatchingAnnotation(Set<T> anns, Platform matchPlatform) { 273 if (anns.isEmpty()) { 274 return null; 275 } 276 Method getPlatformsMethod = null; 277 T fallback = null; 278 for (T ann : anns) { 279 try { 280 if (getPlatformsMethod == null) { 281 getPlatformsMethod = ann.getClass().getMethod("platforms"); 282 } 283 if (!Platform[].class.isAssignableFrom(getPlatformsMethod.getReturnType())) { 284 return ann; 285 } 286 Platform[] platforms = (Platform[]) getPlatformsMethod.invoke(ann); 287 if (platforms.length == 0) { 288 fallback = ann; 289 } else { 290 for (Platform platform : platforms) { 291 if (matchPlatform == platform) { 292 return ann; 293 } 294 } 295 } 296 } catch (NoSuchMethodException e) { 297 return ann; // not platform specific - return first one 298 } catch (Exception e) { 299 throw new RuntimeException(e); 300 } 301 } 302 return fallback; 303 } 304 305}