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.*;
016
017import java.beans.*;
018import java.lang.annotation.*;
019import java.lang.reflect.*;
020import java.util.*;
021import java.util.function.*;
022
023import org.apache.juneau.*;
024import org.apache.juneau.annotation.*;
025import org.apache.juneau.internal.*;
026
027/**
028 * Lightweight utility class for introspecting information about a method.
029 */
030@BeanIgnore
031public final class MethodInfo extends ExecutableInfo implements Comparable<MethodInfo> {
032
033   private ClassInfo returnType;
034   private final Method m;
035   private List<Method> matching;
036
037   //-----------------------------------------------------------------------------------------------------------------
038   // Instantiation.
039   //-----------------------------------------------------------------------------------------------------------------
040
041   /**
042    * Constructor.
043    *
044    * @param declaringClass The class that declares this method.
045    * @param m The method being wrapped.
046    */
047   protected MethodInfo(ClassInfo declaringClass, Method m) {
048      super(declaringClass, m);
049      this.m = m;
050   }
051
052   /**
053    * Convenience method for instantiating a {@link MethodInfo};
054    *
055    * @param declaringClass The class that declares this method.
056    * @param m The method being wrapped.
057    * @return A new {@link MethodInfo} object, or <jk>null</jk> if the method was null;
058    */
059   public static MethodInfo of(ClassInfo declaringClass, Method m) {
060      if (m == null)
061         return null;
062      return new MethodInfo(declaringClass, m);
063   }
064
065   /**
066    * Convenience method for instantiating a {@link MethodInfo};
067    *
068    * @param m The method being wrapped.
069    * @return A new {@link MethodInfo} object, or <jk>null</jk> if the method was null;
070    */
071   public static MethodInfo of(Method m) {
072      if (m == null)
073         return null;
074      return new MethodInfo(ClassInfo.of(m.getDeclaringClass()), m);
075   }
076
077   /**
078    * Returns the wrapped method.
079    *
080    * @return The wrapped method.
081    */
082   public Method inner() {
083      return m;
084   }
085
086   //-----------------------------------------------------------------------------------------------------------------
087   // Matching methods.
088   //-----------------------------------------------------------------------------------------------------------------
089
090   /**
091    * Finds all declared methods with the same name and arguments on all superclasses and interfaces.
092    *
093    * @return
094    *    All matching methods including this method itself.
095    *    <br>Methods are ordered from child-to-parent order.
096    */
097   public List<Method> getMatching() {
098      if (matching == null)
099         matching = Collections.unmodifiableList(findMatching(new ArrayList<>(), m, m.getDeclaringClass()));
100      return matching;
101   }
102
103   /**
104    * Convenience method for retrieving values in {@link #getMatching()} in parent-to-child order.
105    *
106    * @return
107    *    All matching methods including this method itself.
108    *    <br>Methods are ordered from parent-to-child order.
109    */
110   public Iterable<Method> getMatchingParentFirst() {
111      return iterable(getMatching(), true);
112   }
113
114   private static List<Method> findMatching(List<Method> l, Method m, Class<?> c) {
115      for (Method m2 : c.getDeclaredMethods())
116         if (m.getName().equals(m2.getName()) && Arrays.equals(m.getParameterTypes(), m2.getParameterTypes()))
117            l.add(m2);
118      Class<?> pc = c.getSuperclass();
119      if (pc != null)
120         findMatching(l, m, pc);
121      for (Class<?> ic : c.getInterfaces())
122         findMatching(l, m, ic);
123      return l;
124   }
125
126   private Method findMatchingOnClass(ClassInfo c) {
127      for (Method m2 : c.inner().getDeclaredMethods())
128         if (m.getName().equals(m2.getName()) && Arrays.equals(m.getParameterTypes(), m2.getParameterTypes()))
129            return m2;
130      return null;
131   }
132
133   //-----------------------------------------------------------------------------------------------------------------
134   // Annotations
135   //-----------------------------------------------------------------------------------------------------------------
136
137   /**
138    * Returns all annotations of the specified type defined on the specified method.
139    *
140    * <p>
141    * Searches all methods with the same signature on the parent classes or interfaces
142    * and the return type on the method.
143    *
144    * @param a
145    *    The annotation to search for.
146    * @return
147    *    A list of all matching annotations found in child-to-parent order, or an empty list if none found.
148    */
149   public <T extends Annotation> List<T> getAnnotations(Class<T> a) {
150      return appendAnnotations(new ArrayList<>(), a);
151   }
152
153   /**
154    * Identical to {@link #getAnnotations(Class)} but returns the list in reverse (parent-to-child) order.
155    *
156    * @param a
157    *    The annotation to search for.
158    * @return
159    *    A list of all matching annotations found or an empty list if none found.
160    */
161   public <T extends Annotation> List<T> getAnnotationsParentFirst(Class<T> a) {
162      return appendAnnotationsParentFirst(new ArrayList<>(), a);
163   }
164
165   /**
166    * Finds and appends the specified annotation on the specified method and methods on superclasses/interfaces to the specified
167    * list.
168    *
169    * <p>
170    * Results are ordered in child-to-parent order.
171    *
172    * @param l The list of annotations.
173    * @param a The annotation.
174    * @return The same list.
175    */
176   @SuppressWarnings("unchecked")
177   public <T extends Annotation> List<T> appendAnnotations(List<T> l, Class<T> a) {
178      for (Method m2 : getMatching())
179         for (Annotation a2 :  m2.getDeclaredAnnotations())
180            if (a.isInstance(a2))
181               l.add((T)a2);
182      getReturnType().resolved().appendAnnotations(l, a);
183      declaringClass.appendAnnotations(l, a);
184      return l;
185   }
186
187   /**
188    * Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified
189    * list.
190    *
191    * @param l The list of annotations.
192    * @param a The annotation.
193    * @return The same list.
194    */
195   @SuppressWarnings("unchecked")
196   public <T extends Annotation> List<T> appendAnnotationsParentFirst(List<T> l, Class<T> a) {
197      declaringClass.appendAnnotationsParentFirst(l, a);
198      for (Method m2 : getMatchingParentFirst())
199         for (Annotation a2 :  m2.getDeclaredAnnotations())
200            if (a.isInstance(a2))
201               l.add((T)a2);
202      getReturnType().resolved().appendAnnotations(l, a);
203      return l;
204   }
205
206   /**
207    * Returns the first annotation in the specified list on this method.
208    *
209    * @param c The annotations that cannot be present on the method.
210    * @return <jk>true</jk> if this method does not have any of the specified annotations.
211    */
212   @SafeVarargs
213   public final Annotation getAnnotation(Class<? extends Annotation>...c) {
214      for (Class<? extends Annotation> cc : c) {
215         Annotation a = getAnnotation(cc);
216         if (a != null)
217            return a;
218      }
219      return null;
220   }
221
222   /**
223    * Constructs an {@link AnnotationList} of all annotations found on this method.
224    *
225    * <p>
226    * Annotations are appended in the following orders:
227    * <ol>
228    *    <li>On this method and matching methods ordered child-to-parent.
229    *    <li>On this class.
230    *    <li>On parent classes ordered child-to-parent.
231    *    <li>On interfaces ordered child-to-parent.
232    *    <li>On the package of this class.
233    * </ol>
234    *
235    * @param filter
236    *    Optional filter to apply to limit which annotations are added to the list.
237    *    <br>Can be <jk>null</jk> for no filtering.
238    * @return A new {@link AnnotationList} object on every call.
239    */
240   public AnnotationList getAnnotationList(Predicate<AnnotationInfo<?>> filter) {
241      return appendAnnotationList(new AnnotationList(filter));
242   }
243
244   /**
245    * Constructs an {@link AnnotationList} of all annotations found on this method.
246    *
247    * <p>
248    * Annotations are appended in the following orders:
249    * <ol>
250    *    <li>On the package of this class.
251    *    <li>On interfaces ordered parent-to-child.
252    *    <li>On parent classes ordered parent-to-child.
253    *    <li>On this class.
254    *    <li>On this method and matching methods ordered parent-to-child.
255    * </ol>
256    *
257    * @param filter
258    *    Optional filter to apply to limit which annotations are added to the list.
259    *    <br>Can be <jk>null</jk> for no filtering.
260    * @return A new {@link AnnotationList} object on every call.
261    */
262   public AnnotationList getAnnotationListParentFirst(Predicate<AnnotationInfo<?>> filter) {
263      return appendAnnotationListParentFirst(new AnnotationList(filter));
264   }
265
266   /**
267    * Same as {@link #getAnnotationListParentFirst(Predicate)} except only returns annotations defined on methods.
268    *
269    * @param filter
270    *    Optional filter to apply to limit which annotations are added to the list.
271    *    <br>Can be <jk>null</jk> for no filtering.
272    * @return A new {@link AnnotationList} object on every call.
273    */
274   public AnnotationList getAnnotationListMethodOnlyParentFirst(Predicate<AnnotationInfo<?>> filter) {
275      return appendAnnotationListMethodOnlyParentFirst(new AnnotationList(filter));
276   }
277
278   /**
279    * Returns <jk>true</jk> if this method or parent methods have any annotations annotated with {@link PropertyStoreApply}.
280    *
281    * @return <jk>true</jk> if this method or parent methods have any annotations annotated with {@link PropertyStoreApply}.
282    */
283   public boolean hasConfigAnnotations() {
284      for (Method m2 : getMatching())
285         for (Annotation a2 :  m2.getAnnotations())
286            if (a2.annotationType().getAnnotation(PropertyStoreApply.class) != null)
287               return true;
288      return false;
289   }
290
291   @Override
292   @SuppressWarnings("unchecked")
293   protected <T extends Annotation> T findAnnotation(Class<T> a) {
294      for (Method m2 : getMatching())
295         for (Annotation a2 :  m2.getAnnotations())
296            if (a.isInstance(a2))
297               return (T)a2;
298      return getReturnType().resolved().getAnnotation(a);
299   }
300
301   AnnotationList appendAnnotationList(AnnotationList al) {
302      ClassInfo c = this.declaringClass;
303      for (ClassInfo ci : c.getParents()) {
304         appendMethodAnnotations(al, ci);
305         appendAnnotations(al, ci);
306      }
307      for (ClassInfo ci : c.getInterfaces()) {
308         appendMethodAnnotations(al, ci);
309         appendAnnotations(al, ci);
310      }
311      appendAnnotations(al, c.getPackage());
312      return al;
313   }
314
315   AnnotationList appendAnnotationListParentFirst(AnnotationList al) {
316      ClassInfo c = this.declaringClass;
317      appendAnnotations(al, c.getPackage());
318      for (ClassInfo ci : c.getInterfacesParentFirst()) {
319         appendAnnotations(al, ci);
320         appendMethodAnnotations(al, ci);
321      }
322      for (ClassInfo ci : c.getParentsParentFirst()) {
323         appendAnnotations(al, ci);
324         appendMethodAnnotations(al, ci);
325      }
326      return al;
327   }
328
329   AnnotationList appendAnnotationListMethodOnlyParentFirst(AnnotationList al) {
330      ClassInfo c = this.declaringClass;
331      for (ClassInfo ci : c.getInterfacesParentFirst())
332         appendMethodAnnotations(al, ci);
333      for (ClassInfo ci : c.getParentsParentFirst())
334         appendMethodAnnotations(al, ci);
335      return al;
336   }
337
338   void appendAnnotations(AnnotationList al, Package p) {
339      if (p != null)
340         for (Annotation a : p.getDeclaredAnnotations())
341            al.add(AnnotationInfo.of(p, a));
342   }
343
344   void appendAnnotations(AnnotationList al, ClassInfo ci) {
345      if (ci != null)
346         for (Annotation a : ci.c.getDeclaredAnnotations())
347            al.add(AnnotationInfo.of(ci, a));
348   }
349
350   void appendMethodAnnotations(AnnotationList al, ClassInfo ci) {
351      Method m = findMatchingOnClass(ci);
352      if (m != null)
353         for (Annotation a : m.getDeclaredAnnotations())
354            al.add(AnnotationInfo.of(MethodInfo.of(m), a));
355   }
356
357   //-----------------------------------------------------------------------------------------------------------------
358   // Return type.
359   //-----------------------------------------------------------------------------------------------------------------
360
361   /**
362    * Returns the generic return type of this method as a {@link ClassInfo} object.
363    *
364    * @return The generic return type of this method.
365    */
366   public ClassInfo getReturnType() {
367      if (returnType == null)
368         returnType = ClassInfo.of(m.getReturnType(), m.getGenericReturnType());
369      return returnType;
370   }
371
372   /**
373    * Returns <jk>true</jk> if this method has this return type.
374    *
375    * @param c The return type to test for.
376    * @return <jk>true</jk> if this method has this return type.
377    */
378   public boolean hasReturnType(Class<?> c) {
379      return m.getReturnType() == c;
380   }
381
382   /**
383    * Returns <jk>true</jk> if this method has this return type.
384    *
385    * @param ci The return type to test for.
386    * @return <jk>true</jk> if this method has this return type.
387    */
388   public boolean hasReturnType(ClassInfo ci) {
389      return hasReturnType(ci.inner());
390   }
391
392   /**
393    * Returns <jk>true</jk> if this method has this parent return type.
394    *
395    * @param c The return type to test for.
396    * @return <jk>true</jk> if this method has this parent return type.
397    */
398   public boolean hasReturnTypeParent(Class<?> c) {
399      return ClassInfo.of(c).isParentOf(m.getReturnType());
400   }
401
402   /**
403    * Returns <jk>true</jk> if this method has this parent return type.
404    *
405    * @param ci The return type to test for.
406    * @return <jk>true</jk> if this method has this parent return type.
407    */
408   public boolean hasReturnTypeParent(ClassInfo ci) {
409      return hasReturnTypeParent(ci.inner());
410   }
411
412   //-----------------------------------------------------------------------------------------------------------------
413   // Other methods
414   //-----------------------------------------------------------------------------------------------------------------
415
416   /**
417    * Shortcut for calling the invoke method on the underlying method.
418    *
419    * @param obj the object the underlying method is invoked from.
420    * @param args the arguments used for the method call
421    * @return The object returned from the method.
422    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
423    */
424   @SuppressWarnings("unchecked")
425   public <T> T invoke(Object obj, Object...args) throws ExecutableException {
426      try {
427         return (T)m.invoke(obj, args);
428      } catch (IllegalAccessException | InvocationTargetException e) {
429         throw new ExecutableException(e);
430      }
431   }
432
433   /**
434    * Invokes the specified method using fuzzy-arg matching.
435    *
436    * <p>
437    * Arguments will be matched to the parameters based on the parameter types.
438    * <br>Arguments can be in any order.
439    * <br>Extra arguments will be ignored.
440    * <br>Missing arguments will be left <jk>null</jk>.
441    *
442    * <p>
443    * Note that this only works for methods that have distinguishable argument types.
444    * <br>It's not going to work on methods with generic argument types like <c>Object</c>
445    *
446    * @param pojo
447    *    The POJO the method is being called on.
448    *    <br>Can be <jk>null</jk> for static methods.
449    * @param args
450    *    The arguments to pass to the method.
451    * @return
452    *    The results of the method invocation.
453    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
454    */
455   public Object invokeFuzzy(Object pojo, Object...args) throws ExecutableException {
456      try {
457         return m.invoke(pojo, ClassUtils.getMatchingArgs(m.getParameterTypes(), args));
458      } catch (IllegalAccessException | InvocationTargetException e) {
459         throw new ExecutableException(e);
460      }
461   }
462
463   /**
464    * Returns the signature of this method.
465    *
466    * <p>
467    * For no-arg methods, the signature will be a simple string such as <js>"toString"</js>.
468    * For methods with one or more args, the arguments will be fully-qualified class names (e.g.
469    * <js>"append(java.util.StringBuilder,boolean)"</js>)
470    *
471    * @return The methods signature.
472    */
473   public String getSignature() {
474      StringBuilder sb = new StringBuilder(128);
475      sb.append(m.getName());
476      Class<?>[] pt = rawParamTypes();
477      if (pt.length > 0) {
478         sb.append('(');
479         List<ParamInfo> mpi = getParams();
480         for (int i = 0; i < pt.length; i++) {
481            if (i > 0)
482               sb.append(',');
483            mpi.get(i).getParameterType().appendFullName(sb);
484         }
485         sb.append(')');
486      }
487      return sb.toString();
488   }
489
490   /**
491    * Returns the bean property name if this is a getter or setter.
492    *
493    * @return The bean property name, or <jk>null</jk> if this isn't a getter or setter.
494    */
495   public String getPropertyName() {
496      String n = m.getName();
497      if ((n.startsWith("get") || n.startsWith("set")) && n.length() > 3)
498         return Introspector.decapitalize(n.substring(3));
499      if (n.startsWith("is") && n.length() > 2)
500         return Introspector.decapitalize(n.substring(2));
501      return n;
502   }
503
504   /**
505    * Returns <jk>true</jk> if the parameters on the method only consist of the types specified in the list.
506    *
507    * <h5 class='figure'>Example:</h5>
508    * <p class='bpcode w800'>
509    *
510    *  <jc>// Example method:</jc>
511    *    <jk>public void</jk> foo(String bar, Integer baz);
512    *
513    *    argsOnlyOfType(fooMethod, String.<jk>class</jk>, Integer.<jk>class</jk>);  <jc>// True.</jc>
514    *    argsOnlyOfType(fooMethod, String.<jk>class</jk>, Integer.<jk>class</jk>, Map.<jk>class</jk>);  <jc>// True.</jc>
515    *    argsOnlyOfType(fooMethod, String.<jk>class</jk>);  <jc>// False.</jc>
516    * </p>
517    *
518    * @param args The valid class types (exact) for the arguments.
519    * @return <jk>true</jk> if the method parameters only consist of the types specified in the list.
520    */
521   public boolean argsOnlyOfType(Class<?>...args) {
522      for (Class<?> c1 : rawParamTypes()) {
523         boolean foundMatch = false;
524         for (Class<?> c2 : args)
525            if (c1 == c2)
526               foundMatch = true;
527         if (! foundMatch)
528            return false;
529      }
530      return true;
531   }
532
533   /**
534    * Returns <jk>true</jk> if this method is a bridge method.
535    *
536    * @return <jk>true</jk> if this method is a bridge method.
537    */
538   public boolean isBridge() {
539      return m.isBridge();
540   }
541
542   @Override
543   public int compareTo(MethodInfo o) {
544      int i = getSimpleName().compareTo(o.getSimpleName());
545      if (i == 0) {
546         i = rawParamTypes().length - o.rawParamTypes().length;
547         if (i == 0) {
548            for (int j = 0; j < rawParamTypes().length && i == 0; j++) {
549               i = rawParamTypes()[j].getName().compareTo(o.rawParamTypes()[j].getName());
550            }
551         }
552      }
553      return i;
554   }
555}