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