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.*;
018import java.util.concurrent.*;
019
020import org.apache.juneau.annotation.*;
021
022/**
023 * Lightweight utility class for introspecting information about a method parameter.
024 */
025@BeanIgnore
026public final class ParamInfo {
027
028   private final ExecutableInfo eInfo;
029   private final Parameter p;
030   private final int index;
031   private volatile Map<Class<?>,Optional<Annotation>> annotationMap;
032
033   //-----------------------------------------------------------------------------------------------------------------
034   // Instantiation.
035   //-----------------------------------------------------------------------------------------------------------------
036
037   /**
038    * Constructor.
039    *
040    * @param eInfo The constructor or method wrapper.
041    * @param p The parameter being wrapped.
042    * @param index The parameter index.
043    */
044   protected ParamInfo(ExecutableInfo eInfo, Parameter p, int index) {
045      this.eInfo = eInfo;
046      this.p = p;
047      this.index = index;
048   }
049
050   /**
051    * Returns the index position of this parameter.
052    *
053    * @return The index position of this parameter.
054    */
055   public int getIndex() {
056      return index;
057   }
058
059   /**
060    * Returns the method that this parameter belongs to.
061    *
062    * @return The method that this parameter belongs to, or <jk>null</jk> if it belongs to a constructor.
063    */
064   public MethodInfo getMethod() {
065      return eInfo.isConstructor() ? null : (MethodInfo)eInfo;
066   }
067
068   /**
069    * Returns the constructor that this parameter belongs to.
070    *
071    * @return The constructor that this parameter belongs to, or <jk>null</jk> if it belongs to a method.
072    */
073   public ConstructorInfo getConstructor() {
074      return eInfo.isConstructor() ? (ConstructorInfo)eInfo : null;
075   }
076
077   /**
078    * Returns the class type of this parameter.
079    *
080    * @return The class type of this parameter.
081    */
082   public ClassInfo getParameterType() {
083      return eInfo.getParamType(index);
084   }
085
086   //-----------------------------------------------------------------------------------------------------------------
087   // Annotations.
088   //-----------------------------------------------------------------------------------------------------------------
089
090   /**
091    * Returns the parameter annotations declared on this parameter.
092    *
093    * @return The parameter annotations declared on this parameter, or an empty array if none found.
094    */
095   public Annotation[] getDeclaredAnnotations() {
096      return eInfo.getParameterAnnotations(index);
097   }
098
099   /**
100    * Returns the specified parameter annotation declared on this parameter.
101    *
102    * @param a
103    *    The annotation to search for.
104    * @param <T>
105    *    The annotation type.
106    * @return The specified parameter annotation declared on this parameter, or <jk>null</jk> if not found.
107    */
108   @SuppressWarnings("unchecked")
109   public <T extends Annotation> T getDeclaredAnnotation(Class<T> a) {
110      if (a != null)
111         for (Annotation aa : eInfo.getParameterAnnotations(index))
112            if (a.isInstance(aa))
113               return (T)aa;
114      return null;
115   }
116
117   /**
118    * Finds the annotation of the specified type defined on this method parameter.
119    *
120    * <p>
121    * If the annotation cannot be found on the immediate method, searches methods with the same
122    * signature on the parent classes or interfaces.
123    * <br>The search is performed in child-to-parent order.
124    *
125    * <p>
126    * If still not found, searches for the annotation on the return type of the method.
127    *
128    * @param a
129    *    The annotation to search for.
130    * @return
131    *    The annotation if found, or <jk>null</jk> if not.
132    */
133   @SuppressWarnings("unchecked")
134   public <T extends Annotation> T getAnnotation(Class<T> a) {
135      Optional<Annotation> o = annotationMap().get(a);
136      if (o == null) {
137         o = Optional.ofNullable(findAnnotation(a));
138         annotationMap().put(a, o);
139      }
140      return o.isPresent() ? (T)o.get() : null;
141   }
142
143   /**
144    * Returns <jk>true</jk> if this parameter has the specified annotation.
145    *
146    * @param a
147    *    The annotation to search for.
148    * @return
149    *    The <jk>true</jk> if annotation if found.
150    */
151   public boolean hasAnnotation(Class<? extends Annotation> a) {
152      return getAnnotation(a) != null;
153   }
154
155   @SuppressWarnings("unchecked")
156   private <T extends Annotation> T findAnnotation(Class<T> a) {
157      if (eInfo.isConstructor()) {
158         for (Annotation a2 : eInfo.getParameterAnnotations(index))
159            if (a.isInstance(a2))
160               return (T)a2;
161         return eInfo.getParamType(index).resolved().getAnnotation(a);
162      }
163      MethodInfo mi = (MethodInfo)eInfo;
164      for (Method m2 : mi.getMatching())
165         for (Annotation a2 :  m2.getParameterAnnotations()[index])
166            if (a.isInstance(a2))
167               return (T)a2;
168      return eInfo.getParamType(index).resolved().getAnnotation(a);
169   }
170
171   /**
172    * Returns all annotations of the specified type defined on this method parameter.
173    *
174    * <p>
175    * Searches all methods with the same signature on the parent classes or interfaces
176    * and the return type on the method.
177    *
178    * @param a
179    *    The annotation to search for.
180    * @return
181    *    A list of all matching annotations found in child-to-parent order, or an empty list if none found.
182    */
183   public <T extends Annotation> List<T> getAnnotations(Class<T> a) {
184      return appendAnnotations(new ArrayList<>(), a, false);
185   }
186
187   /**
188    * Identical to {@link #getAnnotations(Class)} but returns the list in reverse (parent-to-child) order.
189    *
190    * @param a
191    *    The annotation to search for.
192    * @return
193    *    A list of all matching annotations found or an empty list if none found.
194    */
195   public <T extends Annotation> List<T> getAnnotationsParentFirst(Class<T> a) {
196      return appendAnnotations(new ArrayList<>(), a, true);
197   }
198
199   @SuppressWarnings("unchecked")
200   private <T extends Annotation> List<T> appendAnnotations(List<T> l, Class<T> a, boolean parentFirst) {
201      if (eInfo.isConstructor) {
202         ClassInfo ci = eInfo.getParamType(index).resolved();
203         Annotation[] annotations = eInfo.getParameterAnnotations(index);
204         if (parentFirst) {
205            ci.appendAnnotationsParentFirst(l, a);
206            for (Annotation a2 : annotations)
207               if (a.isInstance(a2))
208                  l.add((T)a2);
209         } else {
210            for (Annotation a2 : annotations)
211               if (a.isInstance(a2))
212                  l.add((T)a2);
213            ci.appendAnnotations(l, a);
214         }
215      } else {
216         MethodInfo mi = (MethodInfo)eInfo;
217         ClassInfo ci = eInfo.getParamType(index).resolved();
218         if (parentFirst) {
219            ci.appendAnnotationsParentFirst(l, a);
220            for (Method m2 : mi.getMatchingParentFirst())
221               for (Annotation a2 :  m2.getParameterAnnotations()[index])
222                  if (a.isInstance(a2))
223                     l.add((T)a2);
224         } else {
225            for (Method m2 : mi.getMatching())
226               for (Annotation a2 :  m2.getParameterAnnotations()[index])
227                  if (a.isInstance(a2))
228                     l.add((T)a2);
229            ci.appendAnnotations(l, a);
230         }
231      }
232      return l;
233   }
234
235   private synchronized Map<Class<?>,Optional<Annotation>> annotationMap() {
236      if (annotationMap == null)
237         annotationMap = new ConcurrentHashMap<>();
238      return annotationMap;
239   }
240
241   //-----------------------------------------------------------------------------------------------------------------
242   // Other methods.
243   //-----------------------------------------------------------------------------------------------------------------
244
245   /**
246    * Returns <jk>true</jk> if the parameter has a name provided by the class file.
247    *
248    * @return <jk>true</jk> if the parameter has a name provided by the class file.
249    */
250   public boolean hasName() {
251      return p.isNamePresent() || p.isAnnotationPresent(Name.class);
252   }
253
254   /**
255    * Returns the name of the parameter.
256    *
257    * <p>
258    * If the parameter's name is present, then this method returns the name provided by the class file.
259    * Otherwise, this method synthesizes a name of the form argN, where N is the index of the parameter in the descriptor of the method which declares the parameter.
260    *
261    * @return The name of the parameter.
262    * @see Parameter#getName()
263    */
264   public String getName() {
265      Name n = p.getAnnotation(Name.class);
266      if (n != null)
267         return n.value();
268      if (p.isNamePresent())
269         return p.getName();
270      return null;
271   }
272
273   @Override
274   public String toString() {
275      return (eInfo.getSimpleName()) + "[" + index + "]";
276   }
277}