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}