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    * Returns all annotations of the specified type defined on the specified method.
155    *
156    * <p>
157    * Searches all methods with the same signature on the parent classes or interfaces
158    * and the return type on the method.
159    *
160    * @param a
161    *    The annotation to search for.
162    * @return
163    *    A list of all matching annotations found in child-to-parent order, or an empty list if none found.
164    */
165   public <T extends Annotation> List<T> getAnnotations(Class<T> a) {
166      return appendAnnotations(new ArrayList<>(), a);
167   }
168
169   /**
170    * Identical to {@link #getAnnotations(Class)} but returns the list in reverse (parent-to-child) order.
171    *
172    * @param a
173    *    The annotation to search for.
174    * @return
175    *    A list of all matching annotations found or an empty list if none found.
176    */
177   public <T extends Annotation> List<T> getAnnotationsParentFirst(Class<T> a) {
178      return appendAnnotationsParentFirst(new ArrayList<>(), a);
179   }
180
181   /**
182    * Finds and appends the specified annotation on the specified method and methods on superclasses/interfaces to the specified
183    * list.
184    *
185    * <p>
186    * Results are ordered in child-to-parent order.
187    *
188    * @param l The list of annotations.
189    * @param a The annotation.
190    * @return The same list.
191    */
192   @SuppressWarnings("unchecked")
193   public <T extends Annotation> List<T> appendAnnotations(List<T> l, Class<T> a) {
194      for (Method m2 : getMatching())
195         for (Annotation a2 :  m2.getDeclaredAnnotations())
196            if (a.isInstance(a2))
197               l.add((T)a2);
198      getReturnType().resolved().appendAnnotations(l, a);
199      declaringClass.appendAnnotations(l, a);
200      return l;
201   }
202
203   /**
204    * Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified
205    * list.
206    *
207    * @param l The list of annotations.
208    * @param a The annotation.
209    * @return The same list.
210    */
211   @SuppressWarnings("unchecked")
212   public <T extends Annotation> List<T> appendAnnotationsParentFirst(List<T> l, Class<T> a) {
213      declaringClass.appendAnnotationsParentFirst(l, a);
214      for (Method m2 : getMatchingParentFirst())
215         for (Annotation a2 :  m2.getDeclaredAnnotations())
216            if (a.isInstance(a2))
217               l.add((T)a2);
218      getReturnType().resolved().appendAnnotations(l, a);
219      return l;
220   }
221
222   /**
223    * Returns the first annotation in the specified list on this method.
224    *
225    * @param c The annotations that cannot be present on the method.
226    * @return <jk>true</jk> if this method does not have any of the specified annotations.
227    */
228   @SafeVarargs
229   public final Annotation getAnnotation(Class<? extends Annotation>...c) {
230      for (Class<? extends Annotation> cc : c) {
231         Annotation a = getAnnotation(cc);
232         if (a != null)
233            return a;
234      }
235      return null;
236   }
237
238   /**
239    * Constructs an {@link AnnotationList} of all annotations found on this method.
240    *
241    * <p>
242    * Annotations are appended in the following orders:
243    * <ol>
244    *    <li>On this method and matching methods ordered child-to-parent.
245    *    <li>On this class.
246    *    <li>On parent classes ordered child-to-parent.
247    *    <li>On interfaces ordered child-to-parent.
248    *    <li>On the package of this class.
249    * </ol>
250    *
251    * @param filter
252    *    Optional filter to apply to limit which annotations are added to the list.
253    *    <br>Can be <jk>null</jk> for no filtering.
254    * @return A new {@link AnnotationList} object on every call.
255    */
256   public AnnotationList getAnnotationList(Predicate<AnnotationInfo<?>> filter) {
257      return appendAnnotationList(new AnnotationList(filter));
258   }
259
260   /**
261    * Constructs an {@link AnnotationList} of all annotations found on this method.
262    *
263    * <p>
264    * Annotations are appended in the following orders:
265    * <ol>
266    *    <li>On the package of this class.
267    *    <li>On interfaces ordered parent-to-child.
268    *    <li>On parent classes ordered parent-to-child.
269    *    <li>On this class.
270    *    <li>On this method and matching methods ordered parent-to-child.
271    * </ol>
272    *
273    * @param filter
274    *    Optional filter to apply to limit which annotations are added to the list.
275    *    <br>Can be <jk>null</jk> for no filtering.
276    * @return A new {@link AnnotationList} object on every call.
277    */
278   public AnnotationList getAnnotationListParentFirst(Predicate<AnnotationInfo<?>> filter) {
279      return appendAnnotationListParentFirst(new AnnotationList(filter));
280   }
281
282   /**
283    * Same as {@link #getAnnotationListParentFirst(Predicate)} except only returns annotations defined on methods.
284    *
285    * @param filter
286    *    Optional filter to apply to limit which annotations are added to the list.
287    *    <br>Can be <jk>null</jk> for no filtering.
288    * @return A new {@link AnnotationList} object on every call.
289    */
290   public AnnotationList getAnnotationListMethodOnlyParentFirst(Predicate<AnnotationInfo<?>> filter) {
291      return appendAnnotationListMethodOnlyParentFirst(new AnnotationList(filter));
292   }
293
294   /**
295    * Returns <jk>true</jk> if this method or parent methods have any annotations annotated with {@link PropertyStoreApply}.
296    *
297    * @return <jk>true</jk> if this method or parent methods have any annotations annotated with {@link PropertyStoreApply}.
298    */
299   public boolean hasConfigAnnotations() {
300      for (Method m2 : getMatching())
301         for (Annotation a2 :  m2.getAnnotations())
302            if (a2.annotationType().getAnnotation(PropertyStoreApply.class) != null)
303               return true;
304      return false;
305   }
306
307   @Override
308   @SuppressWarnings("unchecked")
309   protected <T extends Annotation> T findAnnotation(Class<T> a) {
310      for (Method m2 : getMatching())
311         for (Annotation a2 :  m2.getAnnotations())
312            if (a.isInstance(a2))
313               return (T)a2;
314      return null;
315   }
316
317   AnnotationList appendAnnotationList(AnnotationList al) {
318      ClassInfo c = this.declaringClass;
319      for (ClassInfo ci : c.getParents()) {
320         appendMethodAnnotations(al, ci);
321         appendAnnotations(al, ci);
322      }
323      for (ClassInfo ci : c.getInterfaces()) {
324         appendMethodAnnotations(al, ci);
325         appendAnnotations(al, ci);
326      }
327      appendAnnotations(al, c.getPackage());
328      return al;
329   }
330
331   AnnotationList appendAnnotationListParentFirst(AnnotationList al) {
332      ClassInfo c = this.declaringClass;
333      appendAnnotations(al, c.getPackage());
334      for (ClassInfo ci : c.getInterfacesParentFirst()) {
335         appendAnnotations(al, ci);
336         appendMethodAnnotations(al, ci);
337      }
338      for (ClassInfo ci : c.getParentsParentFirst()) {
339         appendAnnotations(al, ci);
340         appendMethodAnnotations(al, ci);
341      }
342      return al;
343   }
344
345   AnnotationList appendAnnotationListMethodOnlyParentFirst(AnnotationList al) {
346      ClassInfo c = this.declaringClass;
347      for (ClassInfo ci : c.getInterfacesParentFirst())
348         appendMethodAnnotations(al, ci);
349      for (ClassInfo ci : c.getParentsParentFirst())
350         appendMethodAnnotations(al, ci);
351      return al;
352   }
353
354   void appendAnnotations(AnnotationList al, Package p) {
355      if (p != null)
356         for (Annotation a : p.getDeclaredAnnotations())
357            al.add(AnnotationInfo.of(p, a));
358   }
359
360   void appendAnnotations(AnnotationList al, ClassInfo ci) {
361      if (ci != null)
362         for (Annotation a : ci.c.getDeclaredAnnotations())
363            al.add(AnnotationInfo.of(ci, a));
364   }
365
366   void appendMethodAnnotations(AnnotationList al, ClassInfo ci) {
367      Method m = findMatchingOnClass(ci);
368      if (m != null)
369         for (Annotation a : m.getDeclaredAnnotations())
370            al.add(AnnotationInfo.of(MethodInfo.of(m), a));
371   }
372
373   //-----------------------------------------------------------------------------------------------------------------
374   // Return type.
375   //-----------------------------------------------------------------------------------------------------------------
376
377   /**
378    * Returns the generic return type of this method as a {@link ClassInfo} object.
379    *
380    * @return The generic return type of this method.
381    */
382   public ClassInfo getReturnType() {
383      if (returnType == null)
384         returnType = ClassInfo.of(m.getReturnType(), m.getGenericReturnType());
385      return returnType;
386   }
387
388   /**
389    * Returns the generic return type of this method as a {@link ClassInfo} object.
390    * 
391    * <p>
392    * Unwraps the type if it's a {@link Value}.
393    *
394    * @return The generic return type of this method.
395    */
396   public ClassInfo getResolvedReturnType() {
397      if (returnType == null)
398         returnType = ClassInfo.of(m.getReturnType(), m.getGenericReturnType());
399      return returnType.resolved();
400   }
401
402   /**
403    * Returns <jk>true</jk> if this method has this return type.
404    *
405    * @param c The return type to test for.
406    * @return <jk>true</jk> if this method has this return type.
407    */
408   public boolean hasReturnType(Class<?> c) {
409      return m.getReturnType() == c;
410   }
411
412   /**
413    * Returns <jk>true</jk> if this method has this return type.
414    *
415    * @param ci The return type to test for.
416    * @return <jk>true</jk> if this method has this return type.
417    */
418   public boolean hasReturnType(ClassInfo ci) {
419      return hasReturnType(ci.inner());
420   }
421
422   /**
423    * Returns <jk>true</jk> if this method has this parent return type.
424    *
425    * @param c The return type to test for.
426    * @return <jk>true</jk> if this method has this parent return type.
427    */
428   public boolean hasReturnTypeParent(Class<?> c) {
429      return ClassInfo.of(c).isParentOf(m.getReturnType());
430   }
431
432   /**
433    * Returns <jk>true</jk> if this method has this parent return type.
434    *
435    * @param ci The return type to test for.
436    * @return <jk>true</jk> if this method has this parent return type.
437    */
438   public boolean hasReturnTypeParent(ClassInfo ci) {
439      return hasReturnTypeParent(ci.inner());
440   }
441
442   //-----------------------------------------------------------------------------------------------------------------
443   // Other methods
444   //-----------------------------------------------------------------------------------------------------------------
445
446   /**
447    * Shortcut for calling the invoke method on the underlying method.
448    *
449    * @param obj the object the underlying method is invoked from.
450    * @param args the arguments used for the method call
451    * @return The object returned from the method.
452    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
453    */
454   @SuppressWarnings("unchecked")
455   public <T> T invoke(Object obj, Object...args) throws ExecutableException {
456      try {
457         return (T)m.invoke(obj, args);
458      } catch (IllegalAccessException | InvocationTargetException e) {
459         throw new ExecutableException(e);
460      }
461   }
462
463   /**
464    * Invokes the specified method using fuzzy-arg matching.
465    *
466    * <p>
467    * Arguments will be matched to the parameters based on the parameter types.
468    * <br>Arguments can be in any order.
469    * <br>Extra arguments will be ignored.
470    * <br>Missing arguments will be left <jk>null</jk>.
471    *
472    * <p>
473    * Note that this only works for methods that have distinguishable argument types.
474    * <br>It's not going to work on methods with generic argument types like <c>Object</c>
475    *
476    * @param pojo
477    *    The POJO the method is being called on.
478    *    <br>Can be <jk>null</jk> for static methods.
479    * @param args
480    *    The arguments to pass to the method.
481    * @return
482    *    The results of the method invocation.
483    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
484    */
485   public Object invokeFuzzy(Object pojo, Object...args) throws ExecutableException {
486      try {
487         return m.invoke(pojo, ClassUtils.getMatchingArgs(m.getParameterTypes(), args));
488      } catch (IllegalAccessException | InvocationTargetException e) {
489         throw new ExecutableException(e);
490      }
491   }
492
493   /**
494    * Returns the signature of this method.
495    *
496    * <p>
497    * For no-arg methods, the signature will be a simple string such as <js>"toString"</js>.
498    * For methods with one or more args, the arguments will be fully-qualified class names (e.g.
499    * <js>"append(java.util.StringBuilder,boolean)"</js>)
500    *
501    * @return The methods signature.
502    */
503   public String getSignature() {
504      StringBuilder sb = new StringBuilder(128);
505      sb.append(m.getName());
506      Class<?>[] pt = rawParamTypes();
507      if (pt.length > 0) {
508         sb.append('(');
509         List<ParamInfo> mpi = getParams();
510         for (int i = 0; i < pt.length; i++) {
511            if (i > 0)
512               sb.append(',');
513            mpi.get(i).getParameterType().appendFullName(sb);
514         }
515         sb.append(')');
516      }
517      return sb.toString();
518   }
519
520   /**
521    * Returns the bean property name if this is a getter or setter.
522    *
523    * @return The bean property name, or <jk>null</jk> if this isn't a getter or setter.
524    */
525   public String getPropertyName() {
526      String n = m.getName();
527      if ((n.startsWith("get") || n.startsWith("set")) && n.length() > 3)
528         return Introspector.decapitalize(n.substring(3));
529      if (n.startsWith("is") && n.length() > 2)
530         return Introspector.decapitalize(n.substring(2));
531      return n;
532   }
533
534   /**
535    * Returns <jk>true</jk> if the parameters on the method only consist of the types specified in the list.
536    *
537    * <h5 class='figure'>Example:</h5>
538    * <p class='bpcode w800'>
539    *
540    *  <jc>// Example method:</jc>
541    *    <jk>public void</jk> foo(String bar, Integer baz);
542    *
543    *    argsOnlyOfType(fooMethod, String.<jk>class</jk>, Integer.<jk>class</jk>);  <jc>// True.</jc>
544    *    argsOnlyOfType(fooMethod, String.<jk>class</jk>, Integer.<jk>class</jk>, Map.<jk>class</jk>);  <jc>// True.</jc>
545    *    argsOnlyOfType(fooMethod, String.<jk>class</jk>);  <jc>// False.</jc>
546    * </p>
547    *
548    * @param args The valid class types (exact) for the arguments.
549    * @return <jk>true</jk> if the method parameters only consist of the types specified in the list.
550    */
551   public boolean argsOnlyOfType(Class<?>...args) {
552      for (Class<?> c1 : rawParamTypes()) {
553         boolean foundMatch = false;
554         for (Class<?> c2 : args)
555            if (c1 == c2)
556               foundMatch = true;
557         if (! foundMatch)
558            return false;
559      }
560      return true;
561   }
562
563   /**
564    * Returns <jk>true</jk> if this method is a bridge method.
565    *
566    * @return <jk>true</jk> if this method is a bridge method.
567    */
568   public boolean isBridge() {
569      return m.isBridge();
570   }
571
572   @Override
573   public int compareTo(MethodInfo o) {
574      int i = getSimpleName().compareTo(o.getSimpleName());
575      if (i == 0) {
576         i = rawParamTypes().length - o.rawParamTypes().length;
577         if (i == 0) {
578            for (int j = 0; j < rawParamTypes().length && i == 0; j++) {
579               i = rawParamTypes()[j].getName().compareTo(o.rawParamTypes()[j].getName());
580            }
581         }
582      }
583      return i;
584   }
585}