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 static org.apache.juneau.internal.CollectionUtils.*;
016import static org.apache.juneau.internal.ConsumerUtils.*;
017
018import java.lang.annotation.*;
019import java.lang.reflect.*;
020import java.util.*;
021import java.util.concurrent.*;
022import java.util.function.*;
023
024import org.apache.juneau.*;
025import org.apache.juneau.annotation.*;
026
027/**
028 * Lightweight utility class for introspecting information about a method parameter.
029 *
030 * <h5 class='section'>See Also:</h5><ul>
031 * </ul>
032 */
033public final class ParamInfo {
034
035   private final ExecutableInfo eInfo;
036   private final Parameter p;
037   private final int index;
038   private volatile Map<Class<?>,Optional<Annotation>> annotationMap;
039
040   //-----------------------------------------------------------------------------------------------------------------
041   // Instantiation
042   //-----------------------------------------------------------------------------------------------------------------
043
044   /**
045    * Constructor.
046    *
047    * @param eInfo The constructor or method wrapper.
048    * @param p The parameter being wrapped.
049    * @param index The parameter index.
050    */
051   protected ParamInfo(ExecutableInfo eInfo, Parameter p, int index) {
052      this.eInfo = eInfo;
053      this.p = p;
054      this.index = index;
055   }
056
057   /**
058    * Returns the index position of this parameter.
059    *
060    * @return The index position of this parameter.
061    */
062   public int getIndex() {
063      return index;
064   }
065
066   /**
067    * Returns the method that this parameter belongs to.
068    *
069    * @return The method that this parameter belongs to, or <jk>null</jk> if it belongs to a constructor.
070    */
071   public MethodInfo getMethod() {
072      return eInfo.isConstructor() ? null : (MethodInfo)eInfo;
073   }
074
075   /**
076    * Returns the constructor that this parameter belongs to.
077    *
078    * @return The constructor that this parameter belongs to, or <jk>null</jk> if it belongs to a method.
079    */
080   public ConstructorInfo getConstructor() {
081      return eInfo.isConstructor() ? (ConstructorInfo)eInfo : null;
082   }
083
084   /**
085    * Returns the class type of this parameter.
086    *
087    * @return The class type of this parameter.
088    */
089   public ClassInfo getParameterType() {
090      return eInfo.getParamType(index);
091   }
092
093   //-----------------------------------------------------------------------------------------------------------------
094   // Annotations
095   //-----------------------------------------------------------------------------------------------------------------
096
097   /**
098    * Performs an action on all matching annotations declared on this parameter.
099    *
100    * @param <A> The annotation type to look for.
101    * @param type The annotation type to look for.
102    * @param filter A predicate to apply to the entries to determine if action should be performed.  Can be <jk>null</jk>.
103    * @param action An action to perform on the entry.
104    * @return This object.
105    */
106   public <A extends Annotation> ParamInfo forEachDeclaredAnnotation(Class<A> type, Predicate<A> filter, Consumer<A> action) {
107      for (Annotation a : eInfo._getParameterAnnotations(index))
108         consume(type, filter, action, a);
109      return this;
110   }
111
112   /**
113    * Returns the specified parameter annotation declared on this parameter.
114    *
115    * @param <A> The annotation type to look for.
116    * @param type The annotation type to look for.
117    * @return The specified parameter annotation declared on this parameter, or <jk>null</jk> if not found.
118    */
119   public <A extends Annotation> A getDeclaredAnnotation(Class<A> type) {
120      if (type != null)
121         for (Annotation a : eInfo._getParameterAnnotations(index))
122            if (type.isInstance(a))
123               return type.cast(a);
124      return null;
125   }
126
127   /**
128    * Finds the annotation of the specified type defined on this method parameter.
129    *
130    * <p>
131    * If the annotation cannot be found on the immediate method, searches methods with the same
132    * signature on the parent classes or interfaces.
133    * <br>The search is performed in child-to-parent order.
134    *
135    * <p>
136    * If still not found, searches for the annotation on the return type of the method.
137    *
138    * @param <A> The annotation type to look for.
139    * @param type The annotation type to look for.
140    * @return
141    *    The annotation if found, or <jk>null</jk> if not.
142    */
143   @SuppressWarnings("unchecked")
144   public <A extends Annotation> A getAnnotation(Class<A> type) {
145      Optional<Annotation> o = annotationMap().get(type);
146      if (o == null) {
147         o = optional(findAnnotation(type));
148         annotationMap().put(type, o);
149      }
150      return o.isPresent() ? (A)o.get() : null;
151   }
152
153   /**
154    * Returns <jk>true</jk> if this parameter has the specified annotation.
155    *
156    * @param <A> The annotation type to look for.
157    * @param type The annotation type to look for.
158    * @return
159    *    The <jk>true</jk> if annotation if found.
160    */
161   public <A extends Annotation> boolean hasAnnotation(Class<A> type) {
162      return getAnnotation(type) != null;
163   }
164
165   /**
166    * Returns <jk>true</jk> if this parameter doesn't have the specified annotation.
167    *
168    * @param <A> The annotation type to look for.
169    * @param type The annotation type to look for.
170    * @return
171    *    The <jk>true</jk> if annotation if not found.
172    */
173   public <A extends Annotation> boolean hasNoAnnotation(Class<A> type) {
174      return ! hasAnnotation(type);
175   }
176
177   private <A extends Annotation> A findAnnotation(Class<A> type) {
178      if (eInfo.isConstructor()) {
179         for (Annotation a2 : eInfo._getParameterAnnotations(index))
180            if (type.isInstance(a2))
181               return type.cast(a2);
182         return eInfo.getParamType(index).unwrap(Value.class,Optional.class).getAnnotation(type);
183      }
184      MethodInfo mi = (MethodInfo)eInfo;
185      Value<A> v = Value.empty();
186      mi.forEachMatchingParentFirst(x -> true, x -> x.forEachParameterAnnotation(index, type, y -> true, y -> v.set(y)));
187      return v.orElseGet(() -> eInfo.getParamType(index).unwrap(Value.class,Optional.class).getAnnotation(type));
188   }
189
190   /**
191    * Performs an action on all matching annotations on this parameter.
192    *
193    * <p>
194    * Searches all methods with the same signature on the parent classes or interfaces
195    * and the return type on the method.
196    * <p>
197    * Results are in parent-to-child order.
198    *
199    * @param <A> The annotation type to look for.
200    * @param type The annotation type to look for.
201    * @param filter A predicate to apply to the entries to determine if action should be performed.  Can be <jk>null</jk>.
202    * @param action An action to perform on the entry.
203    * @return This object.
204    */
205   public <A extends Annotation> ParamInfo forEachAnnotation(Class<A> type, Predicate<A> filter, Consumer<A> action) {
206      return forEachAnnotation(AnnotationProvider.DEFAULT, type, filter, action);
207   }
208
209   /**
210    * Returns the first matching annotation on this method parameter.
211    *
212    * <p>
213    * Searches all methods with the same signature on the parent classes or interfaces
214    * and the return type on the method.
215    * <p>
216    * Results are in parent-to-child order.
217    *
218    * @param <A> The annotation type to look for.
219    * @param type The annotation type to look for.
220    * @param filter A predicate to apply to the entries to determine if value should be used.  Can be <jk>null</jk>.
221    * @return A list of all matching annotations found or an empty list if none found.
222    */
223   @SuppressWarnings("unchecked")
224   public <A extends Annotation> A getAnnotation(Class<A> type, Predicate<A> filter) {
225      if (eInfo.isConstructor) {
226         ClassInfo ci = eInfo.getParamType(index).unwrap(Value.class,Optional.class);
227         A o = ci.getAnnotation(type, filter);
228         if (o != null)
229            return o;
230         for (Annotation a2 : eInfo._getParameterAnnotations(index))
231            if (test(type, filter, a2))
232               return (A)a2;
233      } else {
234         MethodInfo mi = (MethodInfo)eInfo;
235         ClassInfo ci = eInfo.getParamType(index).unwrap(Value.class,Optional.class);
236         A o = ci.getAnnotation(type, filter);
237         if (o != null)
238            return o;
239         Value<A> v = Value.empty();
240         mi.forEachMatchingParentFirst(x -> true, x -> x.forEachParameterAnnotation(index, type, filter, y -> v.set(y)));
241         return v.orElse(null);
242      }
243      return null;
244   }
245
246   private <A extends Annotation> ParamInfo forEachAnnotation(AnnotationProvider ap, Class<A> a, Predicate<A> filter, Consumer<A> action) {
247      if (eInfo.isConstructor) {
248         ClassInfo ci = eInfo.getParamType(index).unwrap(Value.class,Optional.class);
249         Annotation[] annotations = eInfo._getParameterAnnotations(index);
250         ci.forEachAnnotation(ap, a, filter, action);
251         for (Annotation a2 : annotations)
252            consume(a, filter, action, a2);
253      } else {
254         MethodInfo mi = (MethodInfo)eInfo;
255         ClassInfo ci = eInfo.getParamType(index).unwrap(Value.class,Optional.class);
256         ci.forEachAnnotation(ap, a, filter, action);
257         mi.forEachMatchingParentFirst(x -> true, x -> x.forEachParameterAnnotation(index, a, filter, action));
258      }
259      return this;
260   }
261
262   private Map<Class<?>,Optional<Annotation>> annotationMap() {
263      if (annotationMap == null) {
264         synchronized(this) {
265            annotationMap = new ConcurrentHashMap<>();
266         }
267      }
268      return annotationMap;
269   }
270
271   //-----------------------------------------------------------------------------------------------------------------
272   // Other methods
273   //-----------------------------------------------------------------------------------------------------------------
274
275   /**
276    * Returns <jk>true</jk> if this object passes the specified predicate test.
277    *
278    * @param test The test to perform.
279    * @return <jk>true</jk> if this object passes the specified predicate test.
280    */
281   public boolean matches(Predicate<ParamInfo> test) {
282      return test(test, this);
283   }
284
285   /**
286    * Performs an action on this object if the specified predicate test passes.
287    *
288    * @param test A test to apply to determine if action should be executed.  Can be <jk>null</jk>.
289    * @param action An action to perform on this object.
290    * @return This object.
291    */
292   public ParamInfo accept(Predicate<ParamInfo> test, Consumer<ParamInfo> action) {
293      if (matches(test))
294         action.accept(this);
295      return this;
296   }
297
298   /**
299    * Returns <jk>true</jk> if the parameter type is an exact match for the specified class.
300    *
301    * @param c The type to check.
302    * @return <jk>true</jk> if the parameter type is an exact match for the specified class.
303    */
304   public boolean isType(Class<?> c) {
305      return getParameterType().is(c);
306   }
307
308   /**
309    * Returns <jk>true</jk> if the parameter has a name provided by the class file.
310    *
311    * @return <jk>true</jk> if the parameter has a name provided by the class file.
312    */
313   public boolean hasName() {
314      return p.isNamePresent() || p.isAnnotationPresent(Name.class);
315   }
316
317   /**
318    * Returns the name of the parameter.
319    *
320    * <p>
321    * If the parameter's name is present, then this method returns the name provided by the class file.
322    * 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.
323    *
324    * @return The name of the parameter.
325    * @see Parameter#getName()
326    */
327   public String getName() {
328      Name n = p.getAnnotation(Name.class);
329      if (n != null)
330         return n.value();
331      if (p.isNamePresent())
332         return p.getName();
333      return null;
334   }
335
336   /**
337    * Returns <jk>true</jk> if this parameter can accept the specified value.
338    *
339    * @param value The value to check.
340    * @return <jk>true</jk> if this parameter can accept the specified value.
341    */
342   public boolean canAccept(Object value) {
343      return getParameterType().isInstance(value);
344   }
345
346   @Override
347   public String toString() {
348      return (eInfo.getSimpleName()) + "[" + index + "]";
349   }
350}