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