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 getLastAnnotation(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 getLastAnnotation(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().getLastAnnotation(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().getLastAnnotation(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    * <p>
178    * Results are in parent-to-child order.
179    *
180    * @param a
181    *    The annotation to search for.
182    * @return
183    *    A list of all matching annotations found or an empty list if none found.
184    */
185   public <T extends Annotation> List<T> getAnnotations(Class<T> a) {
186      return appendAnnotations(new ArrayList<>(), a, true);
187   }
188
189   @SuppressWarnings("unchecked")
190   private <T extends Annotation> List<T> appendAnnotations(List<T> l, Class<T> a, boolean parentFirst) {
191      if (eInfo.isConstructor) {
192         ClassInfo ci = eInfo.getParamType(index).resolved();
193         Annotation[] annotations = eInfo.getParameterAnnotations(index);
194         if (parentFirst) {
195            ci.appendAnnotations(l, a);
196            for (Annotation a2 : annotations)
197               if (a.isInstance(a2))
198                  l.add((T)a2);
199         } else {
200            for (Annotation a2 : annotations)
201               if (a.isInstance(a2))
202                  l.add((T)a2);
203            ci.appendAnnotations(l, a);
204         }
205      } else {
206         MethodInfo mi = (MethodInfo)eInfo;
207         ClassInfo ci = eInfo.getParamType(index).resolved();
208         if (parentFirst) {
209            ci.appendAnnotations(l, a);
210            for (Method m2 : mi.getMatchingParentFirst())
211               for (Annotation a2 :  m2.getParameterAnnotations()[index])
212                  if (a.isInstance(a2))
213                     l.add((T)a2);
214         } else {
215            for (Method m2 : mi.getMatching())
216               for (Annotation a2 :  m2.getParameterAnnotations()[index])
217                  if (a.isInstance(a2))
218                     l.add((T)a2);
219            ci.appendAnnotations(l, a);
220         }
221      }
222      return l;
223   }
224
225   private synchronized Map<Class<?>,Optional<Annotation>> annotationMap() {
226      if (annotationMap == null)
227         annotationMap = new ConcurrentHashMap<>();
228      return annotationMap;
229   }
230
231   //-----------------------------------------------------------------------------------------------------------------
232   // Other methods.
233   //-----------------------------------------------------------------------------------------------------------------
234
235   /**
236    * Returns <jk>true</jk> if the parameter has a name provided by the class file.
237    *
238    * @return <jk>true</jk> if the parameter has a name provided by the class file.
239    */
240   public boolean hasName() {
241      return p.isNamePresent() || p.isAnnotationPresent(Name.class);
242   }
243
244   /**
245    * Returns the name of the parameter.
246    *
247    * <p>
248    * If the parameter's name is present, then this method returns the name provided by the class file.
249    * 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.
250    *
251    * @return The name of the parameter.
252    * @see Parameter#getName()
253    */
254   public String getName() {
255      Name n = p.getAnnotation(Name.class);
256      if (n != null)
257         return n.value();
258      if (p.isNamePresent())
259         return p.getName();
260      return null;
261   }
262
263   @Override
264   public String toString() {
265      return (eInfo.getSimpleName()) + "[" + index + "]";
266   }
267}