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}