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 java.lang.annotation.*;
016import java.lang.reflect.*;
017import java.util.*;
018
019import org.apache.juneau.*;
020import org.apache.juneau.annotation.*;
021import org.apache.juneau.internal.*;
022import org.apache.juneau.marshall.*;
023import org.apache.juneau.svl.*;
024
025/**
026 * Represents an annotation instance on a class and the class it was found on.
027 *
028 * @param <T> The annotation type.
029 */
030public class AnnotationInfo<T extends Annotation> {
031
032   private final ClassInfo c;
033   private final MethodInfo m;
034   private final Package p;
035   private final T a;
036   final int rank;
037
038   /**
039    * Constructor.
040    *
041    * @param c The class where the annotation was found.
042    * @param m The method where the annotation was found.
043    * @param p The package where the annotation was found.
044    * @param a The annotation found.
045    */
046   protected AnnotationInfo(ClassInfo c, MethodInfo m, Package p, T a) {
047      this.c = c;
048      this.m = m;
049      this.p = p;
050      this.a = a;
051      this.rank = getRank(a);
052   }
053
054   private static int getRank(Object a) {
055      ClassInfo ci = ClassInfo.ofc(a);
056      MethodInfo mi = ci.getPublicMethod("rank");
057      if (mi != null && mi.hasReturnType(int.class)) {
058         try {
059            return (int)mi.invoke(a);
060         } catch (ExecutableException e) {
061            e.printStackTrace();
062         }
063      }
064      return 0;
065   }
066
067   /**
068    * Convenience constructor when annotation is found on a class.
069    *
070    * @param c The class where the annotation was found.
071    * @param a The annotation found.
072    * @return A new {@link AnnotationInfo} object.
073    */
074   public static <T extends Annotation> AnnotationInfo<T> of(ClassInfo c, T a) {
075      return new AnnotationInfo<>(c, null, null, a);
076   }
077
078   /**
079    * Convenience constructor when annotation is found on a method.
080    *
081    * @param m The method where the annotation was found.
082    * @param a The annotation found.
083    * @return A new {@link AnnotationInfo} object.
084    */
085   public static <T extends Annotation> AnnotationInfo<T> of(MethodInfo m, T a) {
086      return new AnnotationInfo<>(null, m, null, a);
087   }
088
089   /**
090    * Convenience constructor when annotation is found on a package.
091    *
092    * @param p The package where the annotation was found.
093    * @param a The annotation found.
094    * @return A new {@link AnnotationInfo} object.
095    */
096   public static <T extends Annotation> AnnotationInfo<T> of(Package p, T a) {
097      return new AnnotationInfo<>(null, null, p, a);
098   }
099
100   /**
101    * Returns the class where the annotation was found.
102    *
103    * @return the class where the annotation was found, or <jk>null</jk> if it wasn't found on a method.
104    */
105   public ClassInfo getClassOn() {
106      return c;
107   }
108
109   /**
110    * Returns the method where the annotation was found.
111    *
112    * @return the method where the annotation was found, or <jk>null</jk> if it wasn't found on a method.
113    */
114   public MethodInfo getMethodOn() {
115      return m;
116   }
117
118   /**
119    * Returns the package where the annotation was found.
120    *
121    * @return the package where the annotation was found, or <jk>null</jk> if it wasn't found on a package.
122    */
123   public Package getPackageOn() {
124      return p;
125   }
126
127   /**
128    * Returns the annotation found.
129    *
130    * @return The annotation found.
131    */
132   public T getAnnotation() {
133      return a;
134   }
135
136   /**
137    * Converts this object to a readable JSON object for debugging purposes.
138    *
139    * @return A new map showing the attributes of this object as a JSON object.
140    */
141   public ObjectMap toObjectMap() {
142      ObjectMap om = new ObjectMap();
143      if (c != null)
144         om.put("class", c.getSimpleName());
145      if (m != null)
146         om.put("method", m.getShortName());
147      if (p != null)
148         om.put("package", p.getName());
149      ObjectMap oa = new ObjectMap();
150      Class<?> ca = a.annotationType();
151      for (Method m : ca.getDeclaredMethods()) {
152         try {
153            Object v = m.invoke(a);
154            Object d = m.getDefaultValue();
155            if (! Objects.equals(v, d)) {
156               if (! (ArrayUtils.isArray(v) && Array.getLength(v) == 0 && Array.getLength(d) == 0))
157                  oa.put(m.getName(), v);
158            }
159         } catch (Exception e) {
160            oa.put(m.getName(), e.getLocalizedMessage());
161         }
162      }
163      om.put("@" + ca.getSimpleName(), oa);
164      return om;
165   }
166
167   private Constructor<? extends ConfigApply<?>> configApplyConstructor;
168
169   /**
170    * If this annotation has a {@link PropertyStoreApply} annotation, returns an instance of the specified {@link ConfigApply} class.
171    *
172    * @param vrs Variable resolver passed to the {@link ConfigApply} object.
173    * @return A new {@link ConfigApply} object.  Never <jk>null</jk>.
174    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
175    */
176   @SuppressWarnings("unchecked")
177   public ConfigApply<Annotation> getConfigApply(VarResolverSession vrs) throws ExecutableException {
178      try {
179         if (configApplyConstructor == null) {
180            PropertyStoreApply psa = a.annotationType().getAnnotation(PropertyStoreApply.class);
181            if (psa != null)
182               configApplyConstructor = (Constructor<? extends ConfigApply<?>>)psa.value().getConstructor(Class.class, VarResolverSession.class);
183            if (configApplyConstructor == null)
184               throw new NoSuchFieldError("Could not find ConfigApply constructor for annotation:\n" + toString());
185         }
186         ClassInfo ci = getClassInfo();
187         return (ConfigApply<Annotation>) configApplyConstructor.newInstance(ci == null ? null : ci.inner(), vrs);
188      } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
189         throw new ExecutableException(e);
190      }
191   }
192
193   /**
194    * Returns the class that this annotation was found on.
195    *
196    * @return The class that this annotation was found on, or <jk>null</jk> if it was found on a package.
197    */
198   public ClassInfo getClassInfo() {
199      if (this.c != null)
200         return this.c;
201      if (this.m != null)
202         return this.m.getDeclaringClass();
203      return null;
204   }
205
206   /**
207    * Returns <jk>true</jk> if this annotation is the specified type.
208    *
209    * @param a The type to test against.
210    * @return <jk>true</jk> if this annotation is the specified type.
211    */
212   public boolean isType(Class<? extends Annotation> a) {
213      Class<? extends Annotation> at = this.a.annotationType();
214      return at == a;
215   }
216
217   /**
218    * Returns <jk>true</jk> if this annotation has the specified annotation defined on it.
219    *
220    * @param a The annotation to test for.
221    * @return <jk>true</jk> if this annotation has the specified annotation defined on it.
222    */
223   public boolean hasAnnotation(Class<? extends Annotation> a) {
224      return this.a.annotationType().getAnnotation(a) != null;
225   }
226
227   @Override
228   public String toString() {
229      return SimpleJson.DEFAULT_READABLE.toString(toObjectMap());
230   }
231}