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