001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.reflect; 014 015import static org.apache.juneau.common.internal.ThrowableUtils.*; 016import static org.apache.juneau.internal.CollectionUtils.*; 017import static org.apache.juneau.internal.ConsumerUtils.*; 018import static org.apache.juneau.internal.ObjectUtils.*; 019 020import java.lang.annotation.*; 021import java.lang.reflect.*; 022import java.util.*; 023import java.util.function.*; 024 025import org.apache.juneau.*; 026import org.apache.juneau.annotation.*; 027import org.apache.juneau.collections.*; 028import org.apache.juneau.internal.*; 029import org.apache.juneau.marshaller.*; 030import org.apache.juneau.svl.*; 031 032/** 033 * Represents an annotation instance on a class and the class it was found on. 034 * 035 * <h5 class='section'>See Also:</h5><ul> 036 * </ul> 037 * 038 * @param <T> The annotation type. 039 */ 040public final class AnnotationInfo<T extends Annotation> { 041 042 private final ClassInfo c; 043 private final MethodInfo m; 044 private final Package p; 045 private final T a; 046 private volatile Method[] methods; 047 final int rank; 048 049 /** 050 * Constructor. 051 * 052 * @param c The class where the annotation was found. 053 * @param m The method where the annotation was found. 054 * @param p The package where the annotation was found. 055 * @param a The annotation found. 056 */ 057 AnnotationInfo(ClassInfo c, MethodInfo m, Package p, T a) { 058 this.c = c; 059 this.m = m; 060 this.p = p; 061 this.a = a; 062 this.rank = getRank(a); 063 } 064 065 private static int getRank(Object a) { 066 ClassInfo ci = ClassInfo.of(a); 067 MethodInfo mi = ci.getPublicMethod(x -> x.hasName("rank") && x.hasNoParams() && x.hasReturnType(int.class)); 068 if (mi != null) { 069 try { 070 return (int)mi.invoke(a); 071 } catch (ExecutableException e) { 072 e.printStackTrace(); 073 } 074 } 075 return 0; 076 } 077 078 /** 079 * Convenience constructor when annotation is found on a class. 080 * 081 * @param <A> The annotation class. 082 * @param onClass The class where the annotation was found. 083 * @param value The annotation found. 084 * @return A new {@link AnnotationInfo} object. 085 */ 086 public static <A extends Annotation> AnnotationInfo<A> of(ClassInfo onClass, A value) { 087 return new AnnotationInfo<>(onClass, null, null, value); 088 } 089 090 /** 091 * Convenience constructor when annotation is found on a method. 092 * 093 * @param <A> The annotation class. 094 * @param onMethod The method where the annotation was found. 095 * @param value The annotation found. 096 * @return A new {@link AnnotationInfo} object. 097 */ 098 public static <A extends Annotation> AnnotationInfo<A> of(MethodInfo onMethod, A value) { 099 return new AnnotationInfo<>(null, onMethod, null, value); 100 } 101 102 /** 103 * Convenience constructor when annotation is found on a package. 104 * 105 * @param <A> The annotation class. 106 * @param onPackage The package where the annotation was found. 107 * @param value The annotation found. 108 * @return A new {@link AnnotationInfo} object. 109 */ 110 public static <A extends Annotation> AnnotationInfo<A> of(Package onPackage, A value) { 111 return new AnnotationInfo<>(null, null, onPackage, value); 112 } 113 114 /** 115 * Returns the class where the annotation was found. 116 * 117 * @return the class where the annotation was found, or <jk>null</jk> if it wasn't found on a method. 118 */ 119 public ClassInfo getClassOn() { 120 return c; 121 } 122 123 /** 124 * Returns the method where the annotation was found. 125 * 126 * @return the method where the annotation was found, or <jk>null</jk> if it wasn't found on a method. 127 */ 128 public MethodInfo getMethodOn() { 129 return m; 130 } 131 132 /** 133 * Returns the package where the annotation was found. 134 * 135 * @return the package where the annotation was found, or <jk>null</jk> if it wasn't found on a package. 136 */ 137 public Package getPackageOn() { 138 return p; 139 } 140 141 /** 142 * Returns the annotation found. 143 * 144 * @return The annotation found. 145 */ 146 public T inner() { 147 return a; 148 } 149 150 /** 151 * Returns the class name of the annotation. 152 * 153 * @return The simple class name of the annotation. 154 */ 155 public String getName() { 156 return a.annotationType().getSimpleName(); 157 } 158 159 160 /** 161 * Converts this object to a readable JSON object for debugging purposes. 162 * 163 * @return A new map showing the attributes of this object as a JSON object. 164 */ 165 public JsonMap toJsonMap() { 166 JsonMap jm = new JsonMap(); 167 if (c != null) 168 jm.put("class", c.getSimpleName()); 169 if (m != null) 170 jm.put("method", m.getShortName()); 171 if (p != null) 172 jm.put("package", p.getName()); 173 JsonMap ja = new JsonMap(); 174 ClassInfo ca = ClassInfo.of(a.annotationType()); 175 ca.forEachDeclaredMethod(null, x -> { 176 try { 177 Object v = x.invoke(a); 178 Object d = x.inner().getDefaultValue(); 179 if (ne(v, d)) { 180 if (! (ArrayUtils.isArray(v) && Array.getLength(v) == 0 && Array.getLength(d) == 0)) 181 ja.put(m.getName(), v); 182 } 183 } catch (Exception e) { 184 ja.put(m.getName(), e.getLocalizedMessage()); 185 } 186 }); 187 jm.put("@" + ca.getSimpleName(), ja); 188 return jm; 189 } 190 191 private Constructor<? extends AnnotationApplier<?,?>>[] applyConstructors; 192 193 /** 194 * If this annotation has a {@link ContextApply} annotation, consumes an instance of the specified {@link AnnotationApplier} class. 195 * 196 * @param vrs Variable resolver passed to the {@link AnnotationApplier} object. 197 * @param consumer The consumer. 198 * @return This object. 199 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 200 */ 201 @SuppressWarnings("unchecked") 202 public AnnotationInfo<T> getApplies(VarResolverSession vrs, Consumer<AnnotationApplier<Annotation,Object>> consumer) throws ExecutableException { 203 try { 204 if (applyConstructors == null) { 205 ContextApply cpa = a.annotationType().getAnnotation(ContextApply.class); 206 if (cpa == null) 207 applyConstructors = new Constructor[]{ AnnotationApplier.NoOp.class.getConstructor(VarResolverSession.class) }; 208 else { 209 applyConstructors = new Constructor[cpa.value().length]; 210 for (int i = 0; i < cpa.value().length; i++) 211 applyConstructors[i] = (Constructor<? extends AnnotationApplier<?,?>>) cpa.value()[i].getConstructor(VarResolverSession.class); 212 } 213 } 214 for (Constructor<? extends AnnotationApplier<?, ?>> applyConstructor : applyConstructors) 215 consumer.accept((AnnotationApplier<Annotation,Object>) applyConstructor.newInstance(vrs)); 216 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { 217 throw new ExecutableException(e); 218 } 219 return this; 220 } 221 222 /** 223 * Returns the class that this annotation was found on. 224 * 225 * @return The class that this annotation was found on, or <jk>null</jk> if it was found on a package. 226 */ 227 public ClassInfo getClassInfo() { 228 if (this.c != null) 229 return this.c; 230 if (this.m != null) 231 return this.m.getDeclaringClass(); 232 return null; 233 } 234 235 /** 236 * Returns <jk>true</jk> if this annotation is the specified type. 237 * 238 * @param <A> The annotation class. 239 * @param type The type to test against. 240 * @return <jk>true</jk> if this annotation is the specified type. 241 */ 242 public <A extends Annotation> boolean isType(Class<A> type) { 243 Class<? extends Annotation> at = this.a.annotationType(); 244 return at == type; 245 } 246 247 /** 248 * Returns <jk>true</jk> if this annotation has the specified annotation defined on it. 249 * 250 * @param <A> The annotation class. 251 * @param type The annotation to test for. 252 * @return <jk>true</jk> if this annotation has the specified annotation defined on it. 253 */ 254 public <A extends Annotation> boolean hasAnnotation(Class<A> type) { 255 return this.a.annotationType().getAnnotation(type) != null; 256 } 257 258 /** 259 * Returns <jk>true</jk> if this annotation is in the specified {@link AnnotationGroup group}. 260 * 261 * @param <A> The annotation class. 262 * @param group The group annotation. 263 * @return <jk>true</jk> if this annotation is in the specified {@link AnnotationGroup group}. 264 */ 265 public <A extends Annotation> boolean isInGroup(Class<A> group) { 266 AnnotationGroup x = a.annotationType().getAnnotation(AnnotationGroup.class); 267 return (x != null && x.value().equals(group)); 268 } 269 270 /** 271 * Returns <jk>true</jk> if this object passes the specified predicate test. 272 * 273 * @param test The test to perform. 274 * @return <jk>true</jk> if this object passes the specified predicate test. 275 */ 276 public boolean matches(Predicate<AnnotationInfo<?>> test) { 277 return test(test, this); 278 } 279 280 /** 281 * Performs an action on this object if the specified predicate test passes. 282 * 283 * @param test A test to apply to determine if action should be executed. Can be <jk>null</jk>. 284 * @param action An action to perform on this object. 285 * @return This object. 286 */ 287 public AnnotationInfo<?> accept(Predicate<AnnotationInfo<?>> test, Consumer<AnnotationInfo<?>> action) { 288 if (matches(test)) 289 action.accept(this); 290 return this; 291 } 292 293 @Override /* Object */ 294 public String toString() { 295 return Json5.DEFAULT_READABLE.write(toJsonMap()); 296 } 297 298 /** 299 * Performs an action on all matching values on this annotation. 300 * 301 * @param <V> The annotation field type. 302 * @param type The annotation field type. 303 * @param name The annotation field name. 304 * @param test A predicate to apply to the value to determine if action should be performed. Can be <jk>null</jk>. 305 * @param action An action to perform on the value. 306 * @return This object. 307 */ 308 @SuppressWarnings("unchecked") 309 public <V> AnnotationInfo<?> forEachValue(Class<V> type, String name, Predicate<V> test, Consumer<V> action) { 310 for (Method m : _getMethods()) 311 if (m.getName().equals(name) && m.getReturnType().equals(type)) 312 safeRun(() -> consume(test, action, (V)m.invoke(a))); 313 return this; 314 } 315 316 /** 317 * Returns a matching value on this annotation. 318 * 319 * @param <V> The annotation field type. 320 * @param type The annotation field type. 321 * @param name The annotation field name. 322 * @param test A predicate to apply to the value to determine if value should be used. Can be <jk>null</jk>. 323 * @return This object. 324 */ 325 @SuppressWarnings("unchecked") 326 public <V> Optional<V> getValue(Class<V> type, String name, Predicate<V> test) { 327 for (Method m : _getMethods()) 328 if (m.getName().equals(name) && m.getReturnType().equals(type)) { 329 try { 330 V v = (V)m.invoke(a); 331 if (test(test, v)) 332 return optional(v); 333 } catch (Exception e) { 334 e.printStackTrace(); // Shouldn't happen. 335 } 336 } 337 return empty(); 338 } 339 340 Method[] _getMethods() { 341 if (methods == null) 342 synchronized(this) { 343 methods = a.annotationType().getMethods(); 344 } 345 return methods; 346 } 347}