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