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