001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.reflect;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022
023import java.beans.*;
024import java.lang.annotation.*;
025import java.lang.reflect.*;
026import java.util.*;
027import java.util.function.*;
028import java.util.stream.*;
029
030import org.apache.juneau.commons.utils.*;
031
032/**
033 * Lightweight utility class for introspecting information about a Java method.
034 *
035 * <p>
036 * This class provides a convenient wrapper around {@link Method} that extends the standard Java reflection
037 * API with additional functionality for method introspection, annotation handling, and hierarchy traversal.
038 * It's designed to be lightweight, thread-safe, and cached for efficient reuse.
039 *
040 * <h5 class='section'>Features:</h5>
041 * <ul class='spaced-list'>
042 *    <li>Method introspection - access method metadata, parameters, return type, exceptions
043 *    <li>Annotation support - get annotations from method and overridden methods in hierarchy
044 *    <li>Hierarchy traversal - find matching methods in parent classes and interfaces
045 *    <li>Type-safe access - wrapper around reflection with convenient methods
046 *    <li>Thread-safe - instances are immutable and safe for concurrent access
047 * </ul>
048 *
049 * <h5 class='section'>Use Cases:</h5>
050 * <ul class='spaced-list'>
051 *    <li>Introspecting method metadata for code generation or analysis
052 *    <li>Finding annotations on methods including those from parent classes
053 *    <li>Discovering method hierarchies and overridden methods
054 *    <li>Working with method parameters and return types
055 *    <li>Building frameworks that need to analyze method signatures
056 * </ul>
057 *
058 * <h5 class='section'>Usage:</h5>
059 * <p class='bjava'>
060 *    <jc>// Get MethodInfo from a class</jc>
061 *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
062 *    MethodInfo <jv>method</jv> = <jv>ci</jv>.getMethod(<js>"myMethod"</js>);
063 *
064 *    <jc>// Get return type</jc>
065 *    ClassInfo <jv>returnType</jv> = <jv>method</jv>.getReturnType();
066 *
067 *    <jc>// Get annotations including from parent methods</jc>
068 *    List&lt;AnnotationInfo&lt;MyAnnotation&gt;&gt; <jv>annotations</jv> =
069 *       <jv>method</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList();
070 *
071 *    <jc>// Find matching methods in hierarchy</jc>
072 *    List&lt;MethodInfo&gt; <jv>matching</jv> = <jv>method</jv>.getMatchingMethods();
073 * </p>
074 *
075 * <h5 class='section'>See Also:</h5><ul>
076 *    <li class='jc'>{@link ClassInfo} - Class introspection
077 *    <li class='jc'>{@link FieldInfo} - Field introspection
078 *    <li class='jc'>{@link ConstructorInfo} - Constructor introspection
079 *    <li class='jc'>{@link ParameterInfo} - Parameter introspection
080 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a>
081 * </ul>
082 */
083public class MethodInfo extends ExecutableInfo implements Comparable<MethodInfo>, Annotatable {
084   /**
085    * Creates a MethodInfo wrapper for the specified method.
086    *
087    * <h5 class='section'>Example:</h5>
088    * <p class='bjava'>
089    *    Method <jv>m</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>);
090    *    MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <jv>m</jv>);
091    * </p>
092    *
093    * @param declaringClass The class that declares this method. Must not be <jk>null</jk>.
094    * @param inner The method being wrapped. Must not be <jk>null</jk>.
095    * @return A new MethodInfo object wrapping the method.
096    */
097   public static MethodInfo of(Class<?> declaringClass, Method inner) {
098      return ClassInfo.of(declaringClass).getMethod(inner);
099   }
100
101   /**
102    * Creates a MethodInfo wrapper for the specified method.
103    *
104    * <h5 class='section'>Example:</h5>
105    * <p class='bjava'>
106    *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
107    *    Method <jv>m</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>);
108    *    MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(<jv>ci</jv>, <jv>m</jv>);
109    * </p>
110    *
111    * @param declaringClass The ClassInfo for the class that declares this method. Must not be <jk>null</jk>.
112    * @param inner The method being wrapped. Must not be <jk>null</jk>.
113    * @return A new MethodInfo object wrapping the method.
114    */
115   public static MethodInfo of(ClassInfo declaringClass, Method inner) {
116      assertArgNotNull("declaringClass", declaringClass);
117      return declaringClass.getMethod(inner);
118   }
119
120   /**
121    * Creates a MethodInfo wrapper for the specified method.
122    *
123    * <p>
124    * This convenience method automatically determines the declaring class from the method.
125    *
126    * <h5 class='section'>Example:</h5>
127    * <p class='bjava'>
128    *    Method <jv>m</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>);
129    *    MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(<jv>m</jv>);
130    * </p>
131    *
132    * @param inner The method being wrapped. Must not be <jk>null</jk>.
133    * @return A new MethodInfo object wrapping the method.
134    */
135   public static MethodInfo of(Method inner) {
136      assertArgNotNull("inner", inner);
137      return ClassInfo.of(inner.getDeclaringClass()).getMethod(inner);
138   }
139
140   private final Method inner;
141   private final Supplier<ClassInfo> returnType;
142   private final Supplier<List<MethodInfo>> matchingMethods;
143   private final Supplier<List<AnnotationInfo<Annotation>>> annotations;
144
145   /**
146    * Constructor.
147    *
148    * <p>
149    * Creates a new MethodInfo wrapper for the specified method. This constructor is protected
150    * and should not be called directly. Use the static factory methods {@link #of(Method)} or
151    * obtain MethodInfo instances from {@link ClassInfo#getMethod(Method)}.
152    *
153    * @param declaringClass The ClassInfo for the class that declares this method.
154    * @param inner The method being wrapped.
155    */
156   protected MethodInfo(ClassInfo declaringClass, Method inner) {
157      super(declaringClass, inner);
158      this.inner = inner;
159      this.returnType = mem(() -> ClassInfo.of(inner.getReturnType(), inner.getGenericReturnType()));
160      this.matchingMethods = mem(this::findMatchingMethods);
161      this.annotations = mem(() -> getMatchingMethods().stream().flatMap(m -> m.getDeclaredAnnotations().stream()).toList());
162   }
163
164   @Override /* Overridden from ExecutableInfo */
165   public MethodInfo accessible() {
166      super.accessible();
167      return this;
168   }
169
170   @Override
171   public int compareTo(MethodInfo o) {
172      int i = cmp(getSimpleName(), o.getSimpleName());
173      if (i == 0) {
174         var params = getParameters();
175         var oParams = o.getParameters();
176         i = params.size() - oParams.size();
177         if (i == 0) {
178            for (var j = 0; j < params.size() && i == 0; j++) {
179               i = cmp(params.get(j).getParameterType().getName(), oParams.get(j).getParameterType().getName());
180            }
181         }
182      }
183      return i;
184   }
185
186   @Override /* Annotatable */
187   public AnnotatableType getAnnotatableType() { return AnnotatableType.METHOD_TYPE; }
188
189   /**
190    * Returns an {@link AnnotatedType} object that represents the use of a type to specify the return type of the method.
191    *
192    * <p>
193    * Same as calling {@link Method#getAnnotatedReturnType()}.
194    *
195    * <h5 class='section'>Example:</h5>
196    * <p class='bjava'>
197    *    <jc>// For method: public @NotNull String getName()</jc>
198    *    MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"getName"</js>);
199    *    AnnotatedType <jv>aType</jv> = <jv>mi</jv>.getAnnotatedReturnType();
200    *    <jc>// Check for @NotNull on the return type</jc>
201    * </p>
202    *
203    * @return An {@link AnnotatedType} object representing the return type.
204    * @see Method#getAnnotatedReturnType()
205    */
206   public AnnotatedType getAnnotatedReturnType() { return inner.getAnnotatedReturnType(); }
207
208   /**
209    * Returns all annotations on this method and parent overridden methods in child-to-parent order.
210    *
211    * <p>
212    * Results include annotations from:
213    * <ul>
214    *    <li>This method
215    *    <li>Matching methods in parent classes
216    *    <li>Matching methods in interfaces
217    * </ul>
218    *
219    * <p>
220    * <b>Note on Repeatable Annotations:</b>
221    * Repeatable annotations (those marked with {@link java.lang.annotation.Repeatable @Repeatable}) are automatically
222    * expanded into their individual annotation instances. For example, if a method has multiple {@code @Bean} annotations,
223    * this method returns each {@code @Bean} annotation separately, rather than the container annotation.
224    *
225    * <p>
226    * List is unmodifiable.
227    *
228    * @return
229    *    A list of all annotations on this method and overridden methods.
230    *    <br>Repeatable annotations are expanded into individual instances.
231    */
232   public List<AnnotationInfo<Annotation>> getAnnotations() { return annotations.get(); }
233
234   /**
235    * Returns all annotations of the specified type on this method and parent overridden methods in child-to-parent order.
236    *
237    * <p>
238    * Results include annotations from:
239    * <ul>
240    *    <li>This method
241    *    <li>Matching methods in parent classes
242    *    <li>Matching methods in interfaces
243    * </ul>
244    *
245    * <p>
246    * <b>Note on Repeatable Annotations:</b>
247    * If the specified annotation type is repeatable (marked with {@link java.lang.annotation.Repeatable @Repeatable}),
248    * this method automatically expands container annotations into individual instances. This allows you to filter for
249    * a repeatable annotation and get back all individual occurrences without manually handling the container.
250    *
251    * <h5 class='section'>Example:</h5>
252    * <p class='bjava'>
253    *    <jc>// Get all @Bean annotations on this method and overridden methods</jc>
254    *    Stream&lt;AnnotationInfo&lt;Bean&gt;&gt; <jv>beans</jv> = <jv>methodInfo</jv>.getAnnotations(Bean.<jk>class</jk>);
255    *    <jc>// If method has @Beans({@Bean(...), @Bean(...)}), both individual @Bean instances are returned</jc>
256    * </p>
257    *
258    * @param <A> The annotation type.
259    * @param type The annotation type to filter by.
260    * @return
261    *    A stream of matching annotation infos in child-to-parent order.
262    *    <br>Repeatable annotations are expanded into individual instances.
263    */
264   @SuppressWarnings("unchecked")
265   public <A extends Annotation> Stream<AnnotationInfo<A>> getAnnotations(Class<A> type) {
266      assertArgNotNull("type", type);
267      return getAnnotations().stream().filter(a -> a.isType(type)).map(a -> (AnnotationInfo<A>)a);
268   }
269
270   /**
271    * Returns the default value for the annotation member represented by this method.
272    *
273    * <p>
274    * Same as calling {@link Method#getDefaultValue()}.
275    *
276    * <p>
277    * Returns <jk>null</jk> if this method is not an annotation member, or if the annotation member has no default value.
278    *
279    * <h5 class='section'>Example:</h5>
280    * <p class='bjava'>
281    *    <jc>// For annotation: @interface MyAnnotation { String value() default "default"; }</jc>
282    *    MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyAnnotation.<jk>class</jk>).getMethod(<js>"value"</js>);
283    *    Object <jv>defaultValue</jv> = <jv>mi</jv>.getDefaultValue();
284    *    <jc>// defaultValue is "default"</jc>
285    * </p>
286    *
287    * @return The default value, or <jk>null</jk> if none.
288    * @see Method#getDefaultValue()
289    */
290   public Object getDefaultValue() { return inner.getDefaultValue(); }
291
292   /**
293    * Returns a {@link Type} object that represents the formal return type of the method.
294    *
295    * <p>
296    * Same as calling {@link Method#getGenericReturnType()}.
297    *
298    * <p>
299    * If the return type is a parameterized type, the {@link Type} object returned reflects the actual type parameters used in the source code.
300    *
301    * <h5 class='section'>Example:</h5>
302    * <p class='bjava'>
303    *    <jc>// For method: public List&lt;String&gt; getValues()</jc>
304    *    MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"getValues"</js>);
305    *    Type <jv>returnType</jv> = <jv>mi</jv>.getGenericReturnType();
306    *    <jk>if</jk> (<jv>returnType</jv> <jk>instanceof</jk> ParameterizedType) {
307    *       ParameterizedType <jv>pType</jv> = (ParameterizedType)<jv>returnType</jv>;
308    *       <jc>// pType.getActualTypeArguments()[0] is String.class</jc>
309    *    }
310    * </p>
311    *
312    * @return A {@link Type} object representing the formal return type.
313    * @see Method#getGenericReturnType()
314    */
315   public Type getGenericReturnType() { return inner.getGenericReturnType(); }
316
317   @Override /* Annotatable */
318   public String getLabel() { return getDeclaringClass().getNameSimple() + "." + getShortName(); }
319
320   /**
321    * Returns this method and all matching methods up the hierarchy chain.
322    *
323    * <p>
324    * Searches parent classes and interfaces for methods with matching name and parameter types.
325    * Results are returned in the following order:
326    * <ol>
327    *    <li>This method
328    *    <li>Any matching methods on declared interfaces of this class
329    *    <li>Matching method on the parent class
330    *    <li>Any matching methods on the declared interfaces of the parent class
331    *    <li>Continue up the hierarchy
332    * </ol>
333    *
334    * <h5 class='section'>Examples:</h5>
335    * <p class='bjava'>
336    *    <jc>// Interface and class hierarchy:</jc>
337    *    <jk>interface</jk> I1 {
338    *       <jk>void</jk> foo(String <jv>s</jv>);
339    *    }
340    *    <jk>class</jk> A {
341    *       <jk>void</jk> foo(String <jv>s</jv>) {}
342    *    }
343    *    <jk>interface</jk> I2 {
344    *       <jk>void</jk> foo(String <jv>s</jv>);
345    *    }
346    *    <jk>class</jk> B <jk>extends</jk> A <jk>implements</jk> I2 {
347    *       &#64;Override
348    *       <jk>void</jk> foo(String <jv>s</jv>) {}
349    *    }
350    *    <jc>// For B.foo(), returns: [B.foo, I2.foo, A.foo, I1.foo]</jc>
351    *    MethodInfo <jv>mi</jv> = ...;
352    *    List&lt;MethodInfo&gt; <jv>matching</jv> = <jv>mi</jv>.getMatchingMethods();
353    * </p>
354    *
355    * @return A list of matching methods including this one, in child-to-parent order.
356    */
357   public List<MethodInfo> getMatchingMethods() { return matchingMethods.get(); }
358
359   /**
360    * Returns the name of this method.
361    *
362    * @return The name of this method
363    */
364   public String getName() { return inner.getName(); }
365
366   /**
367    * Returns the bean property name if this is a getter or setter.
368    *
369    * @return The bean property name, or <jk>null</jk> if this isn't a getter or setter.
370    */
371   public String getPropertyName() {
372      String n = inner.getName();
373      if ((n.startsWith("get") || n.startsWith("set")) && n.length() > 3)
374         return Introspector.decapitalize(n.substring(3));
375      if (n.startsWith("is") && n.length() > 2)
376         return Introspector.decapitalize(n.substring(2));
377      return n;
378   }
379
380   /**
381    * Returns the generic return type of this method as a {@link ClassInfo} object.
382    *
383    * @return The generic return type of this method.
384    */
385   public ClassInfo getReturnType() { return returnType.get(); }
386
387   /**
388    * Returns the signature of this method.
389    *
390    * <p>
391    * For no-arg methods, the signature will be a simple string such as <js>"toString"</js>.
392    * For methods with one or more args, the arguments will be fully-qualified class names (e.g.
393    * <js>"append(java.util.StringBuilder,boolean)"</js>)
394    *
395    * @return The methods signature.
396    */
397   public String getSignature() {
398      var sb = new StringBuilder(128);
399      sb.append(inner.getName());
400      var params = getParameters();
401      if (params.size() > 0) {
402         sb.append('(');
403         for (var i = 0; i < params.size(); i++) {
404            if (i > 0)
405               sb.append(',');
406            params.get(i).getParameterType().appendNameFormatted(sb, ClassNameFormat.FULL, true, '$', ClassArrayFormat.BRACKETS);
407         }
408         sb.append(')');
409      }
410      return sb.toString();
411   }
412
413   /**
414    * Returns <jk>true</jk> if this method has at least the specified parameters.
415    *
416    * <p>
417    * Method may or may not have additional parameters besides those specified.
418    *
419    * @param requiredParams The parameter types to check for.
420    * @return <jk>true</jk> if this method has at least the specified parameters.
421    */
422   public boolean hasAllParameters(Class<?>...requiredParams) {
423      var paramTypes = getParameters().stream().map(p -> p.getParameterType().inner()).toList();
424
425      for (var c : requiredParams)
426         if (! paramTypes.contains(c))
427            return false;
428
429      return true;
430   }
431
432   /**
433    * Returns <jk>true</jk> if the specified annotation is present on this method or any matching methods in parent classes/interfaces.
434    *
435    * <p>
436    * This method searches through all matching methods in the hierarchy.
437    *
438    * @param <A> The annotation type to look for.
439    * @param type The annotation to look for.
440    * @return <jk>true</jk> if the specified annotation is present on this method.
441    */
442   @Override
443   public <A extends Annotation> boolean hasAnnotation(Class<A> type) {
444      return getAnnotations(type).findAny().isPresent();
445   }
446
447   /**
448    * Returns <jk>true</jk> if the parameters on the method only consist of the types specified in the list.
449    *
450    * <p>
451    * <b>Note:</b> This method is not meant to be used on methods with duplicate parameter types.
452    * It checks if each parameter type is present in the specified list, but does not verify
453    * that the count of each type matches exactly.
454    *
455    * <h5 class='figure'>Example:</h5>
456    * <p class='bjava'>
457    *
458    *    <jc>// Example method:</jc>
459    *    <jk>public void</jk> foo(String <jv>bar</jv>, Integer <jv>baz</jv>);
460    *
461    *    <jv>fooMethod</jv>.hasOnlyParameterTypes(String.<jk>class</jk>, Integer.<jk>class</jk>);  <jc>// True.</jc>
462    *    <jv>fooMethod</jv>.hasOnlyParameterTypes(String.<jk>class</jk>, Integer.<jk>class</jk>, Map.<jk>class</jk>);  <jc>// True.</jc>
463    *    <jv>fooMethod</jv>.hasOnlyParameterTypes(String.<jk>class</jk>);  <jc>// False.</jc>
464    * </p>
465    *
466    * @param args The valid class types (exact) for the arguments.
467    * @return <jk>true</jk> if the method parameters only consist of the types specified in the list.
468    */
469   public boolean hasOnlyParameterTypes(Class<?>...args) {
470      for (var param : getParameters()) {
471         var c1 = param.getParameterType().inner();
472         var foundMatch = false;
473         for (var c2 : args)
474            if (c1 == c2)
475               foundMatch = true;
476         if (! foundMatch)
477            return false;
478      }
479      return true;
480   }
481
482   /**
483    * Returns <jk>true</jk> if this method has the specified parameter.
484    *
485    * <p>
486    * Method may or may not have additional parameters besides the one specified.
487    *
488    * @param requiredParam The parameter type to check for.
489    * @return <jk>true</jk> if this method has at least the specified parameter.
490    */
491   public boolean hasParameter(Class<?> requiredParam) {
492      return hasAllParameters(requiredParam);
493   }
494
495   /**
496    * Returns <jk>true</jk> if this method has this return type.
497    *
498    * @param c The return type to test for.
499    * @return <jk>true</jk> if this method has this return type.
500    */
501   public boolean hasReturnType(Class<?> c) {
502      return inner.getReturnType() == c;
503   }
504
505   /**
506    * Returns <jk>true</jk> if this method has this return type.
507    *
508    * @param ci The return type to test for.
509    * @return <jk>true</jk> if this method has this return type.
510    */
511   public boolean hasReturnType(ClassInfo ci) {
512      return hasReturnType(ci.inner());
513   }
514
515   /**
516    * Returns <jk>true</jk> if this method has this parent return type.
517    *
518    * @param c The return type to test for.
519    * @return <jk>true</jk> if this method has this parent return type.
520    */
521   public boolean hasReturnTypeParent(Class<?> c) {
522      return ClassInfo.of(c).isParentOf(inner.getReturnType());
523   }
524
525   /**
526    * Returns <jk>true</jk> if this method has this parent return type.
527    *
528    * @param ci The return type to test for.
529    * @return <jk>true</jk> if this method has this parent return type.
530    */
531   public boolean hasReturnTypeParent(ClassInfo ci) {
532      return hasReturnTypeParent(ci.inner());
533   }
534
535   /**
536    * Returns the wrapped method.
537    *
538    * @return The wrapped method.
539    */
540   public Method inner() {
541      return inner;
542   }
543
544   /**
545    * Compares this MethodInfo with the specified object for equality.
546    *
547    * <p>
548    * Two MethodInfo objects are considered equal if they wrap the same underlying {@link Method} object.
549    * This delegates to the underlying {@link Method#equals(Object)} method.
550    *
551    * <p>
552    * This method makes MethodInfo suitable for use as keys in hash-based collections such as {@link HashMap}
553    * and {@link HashSet}.
554    *
555    * @param obj The object to compare with.
556    * @return <jk>true</jk> if the objects are equal, <jk>false</jk> otherwise.
557    */
558   @Override
559   public boolean equals(Object obj) {
560      return obj instanceof MethodInfo other && eq(this, other, (x, y) -> eq(x.inner, y.inner) && eq(x.declaringClass, y.declaringClass));
561   }
562
563   /**
564    * Returns a hash code value for this MethodInfo.
565    *
566    * <p>
567    * This combines the hash code of the underlying {@link Method} with the hash code of the declaring class
568    * to ensure that methods from different subclasses that inherit the same method are not considered equal.
569    *
570    * <p>
571    * This method makes MethodInfo suitable for use as keys in hash-based collections such as {@link HashMap}
572    * and {@link HashSet}.
573    *
574    * @return A hash code value for this MethodInfo.
575    */
576   @Override
577   public int hashCode() {
578      return 31 * inner.hashCode() + declaringClass.hashCode();
579   }
580
581   /**
582    * Shortcut for calling the invoke method on the underlying method.
583    *
584    * @param <T> The method return type.
585    * @param obj the object the underlying method is invoked from.
586    * @param args the arguments used for the method call
587    * @return The object returned from the method.
588    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
589    */
590   @SuppressWarnings("unchecked")
591   public <T> T invoke(Object obj, Object...args) throws ExecutableException {
592      return safe(() -> {
593         try {
594            return (T)inner.invoke(obj, args);
595         } catch (InvocationTargetException e) {
596            throw exex(e.getTargetException());
597         }
598      }, e -> exex(e));
599   }
600
601   /**
602    * Invokes the specified method using lenient argument matching.
603    *
604    * <p>
605    * Lenient matching allows arguments to be matched to parameters based on parameter types.
606    * <br>Arguments can be in any order.
607    * <br>Extra arguments will be ignored.
608    * <br>Missing arguments will be left <jk>null</jk>.
609    *
610    * <p>
611    * Note that this only works for methods that have distinguishable argument types.
612    * <br>It's not going to work on methods with generic argument types like <c>Object</c>
613    *
614    * @param pojo
615    *    The POJO the method is being called on.
616    *    <br>Can be <jk>null</jk> for static methods.
617    * @param args
618    *    The arguments to pass to the method.
619    * @return
620    *    The results of the method invocation.
621    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
622    */
623   public Object invokeLenient(Object pojo, Object...args) throws ExecutableException {
624      return safe(() -> {
625         return inner.invoke(pojo, ClassUtils.getMatchingArgs(inner.getParameterTypes(), args));
626      }, e -> exex(e instanceof InvocationTargetException ? ((InvocationTargetException)e).getTargetException() : e));
627   }
628
629   @Override
630   public boolean is(ElementFlag flag) {
631      return switch (flag) {
632         case BRIDGE -> isBridge();
633         case NOT_BRIDGE -> ! isBridge();
634         case DEFAULT -> isDefault();
635         case NOT_DEFAULT -> ! isDefault();
636         default -> super.is(flag);
637      };
638   }
639
640   //-----------------------------------------------------------------------------------------------------------------
641   // High Priority Methods (direct Method API compatibility)
642   //-----------------------------------------------------------------------------------------------------------------
643
644   /**
645    * Returns <jk>true</jk> if this method is a bridge method.
646    *
647    * @return <jk>true</jk> if this method is a bridge method.
648    */
649   public boolean isBridge() { return inner.isBridge(); }
650
651   /**
652    * Returns <jk>true</jk> if this method is a default method (Java 8+ interface default method).
653    *
654    * <p>
655    * Same as calling {@link Method#isDefault()}.
656    *
657    * <p>
658    * A default method is a public non-abstract instance method (i.e., non-static method with a body) declared in an interface.
659    *
660    * <h5 class='section'>Example:</h5>
661    * <p class='bjava'>
662    *    <jc>// For interface: interface MyInterface { default String getName() { return "default"; } }</jc>
663    *    MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyInterface.<jk>class</jk>).getMethod(<js>"getName"</js>);
664    *    <jk>if</jk> (<jv>mi</jv>.isDefault()) {
665    *       <jc>// This is a default interface method</jc>
666    *    }
667    * </p>
668    *
669    * @return <jk>true</jk> if this method is a default method.
670    * @see Method#isDefault()
671    */
672   public boolean isDefault() { return inner.isDefault(); }
673
674   /**
675    * Returns <jk>true</jk> if this method matches the specified method by name and parameter types.
676    *
677    * @param m The method to compare against.
678    * @return <jk>true</jk> if this method has the same name and parameter types as the specified method.
679    */
680   public boolean matches(MethodInfo m) {
681      return hasName(m.getName()) && hasMatchingParameters(m.getParameters());
682   }
683
684   private void addMatchingMethodsFromInterface(List<MethodInfo> result, ClassInfo iface) {
685      // Add matching methods from this interface
686      iface.getDeclaredMethods().stream().filter(this::matches).forEach(result::add);
687
688      // Recursively search parent interfaces
689      iface.getDeclaredInterfaces().stream().forEach(pi -> addMatchingMethodsFromInterface(result, pi));
690   }
691
692   //-----------------------------------------------------------------------------------------------------------------
693   // Annotatable interface methods
694   //-----------------------------------------------------------------------------------------------------------------
695
696   private List<MethodInfo> findMatchingMethods() {
697      var result = new ArrayList<MethodInfo>();
698      result.add(this); // 1. This method
699
700      var cc = getDeclaringClass();
701
702      while (nn(cc)) {
703         // 2. Add matching methods from declared interfaces of current class
704         cc.getDeclaredInterfaces().stream().forEach(di -> addMatchingMethodsFromInterface(result, di));
705
706         // 3. Move to parent class
707         cc = cc.getSuperclass();
708         if (nn(cc)) {
709            // Add matching method from parent class
710            cc.getDeclaredMethods().stream().filter(this::matches).findFirst().ifPresent(result::add);
711         }
712      }
713
714      return result;
715   }
716}