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