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 java.util.stream.Collectors.*;
020import static org.apache.juneau.commons.function.Suppliers.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022import static org.apache.juneau.commons.utils.StringUtils.*;
023import static org.apache.juneau.commons.utils.ThrowableUtils.*;
024
025import java.beans.*;
026import java.lang.annotation.*;
027import java.lang.reflect.*;
028import java.util.*;
029import java.util.function.*;
030import java.util.stream.*;
031
032import org.apache.juneau.commons.function.*;
033import org.apache.juneau.commons.inject.*;
034import org.apache.juneau.commons.utils.*;
035
036/**
037 * Lightweight utility class for introspecting information about a Java method.
038 *
039 * <p>
040 * This class provides a convenient wrapper around {@link Method} that extends the standard Java reflection
041 * API with additional functionality for method introspection, annotation handling, and hierarchy traversal.
042 * It's designed to be lightweight, thread-safe, and cached for efficient reuse.
043 *
044 * <h5 class='section'>Features:</h5>
045 * <ul class='spaced-list'>
046 *    <li>Method introspection - access method metadata, parameters, return type, exceptions
047 *    <li>Annotation support - get annotations from method and overridden methods in hierarchy
048 *    <li>Hierarchy traversal - find matching methods in parent classes and interfaces
049 *    <li>Type-safe access - wrapper around reflection with convenient methods
050 *    <li>Thread-safe - instances are immutable and safe for concurrent access
051 * </ul>
052 *
053 * <h5 class='section'>Use Cases:</h5>
054 * <ul class='spaced-list'>
055 *    <li>Introspecting method metadata for code generation or analysis
056 *    <li>Finding annotations on methods including those from parent classes
057 *    <li>Discovering method hierarchies and overridden methods
058 *    <li>Working with method parameters and return types
059 *    <li>Building frameworks that need to analyze method signatures
060 * </ul>
061 *
062 * <h5 class='section'>Usage:</h5>
063 * <p class='bjava'>
064 *    <jc>// Get MethodInfo from a class</jc>
065 *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
066 *    MethodInfo <jv>method</jv> = <jv>ci</jv>.getMethod(<js>"myMethod"</js>);
067 *
068 *    <jc>// Get return type</jc>
069 *    ClassInfo <jv>returnType</jv> = <jv>method</jv>.getReturnType();
070 *
071 *    <jc>// Get annotations including from parent methods</jc>
072 *    List&lt;AnnotationInfo&lt;MyAnnotation&gt;&gt; <jv>annotations</jv> =
073 *       <jv>method</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList();
074 *
075 *    <jc>// Find matching methods in hierarchy</jc>
076 *    List&lt;MethodInfo&gt; <jv>matching</jv> = <jv>method</jv>.getMatchingMethods();
077 * </p>
078 *
079 * <h5 class='section'>See Also:</h5><ul>
080 *    <li class='jc'>{@link ClassInfo} - Class introspection
081 *    <li class='jc'>{@link FieldInfo} - Field introspection
082 *    <li class='jc'>{@link ConstructorInfo} - Constructor introspection
083 *    <li class='jc'>{@link ParameterInfo} - Parameter introspection
084 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a>
085 * </ul>
086 */
087@SuppressWarnings({
088   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
089   "unchecked" // The (T) cast of Method.invoke() in invoke() and the (AnnotationInfo<A>) cast in getAnnotations(type) are safe because the caller chooses T and the stream is filtered by isType(type).
090})
091public final class MethodInfo extends ExecutableInfo implements Comparable<MethodInfo>, Annotatable {
092
093   /**
094    * Creates a MethodInfo wrapper for the specified method.
095    *
096    * <h5 class='section'>Example:</h5>
097    * <p class='bjava'>
098    *    Method <jv>m</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>);
099    *    MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <jv>m</jv>);
100    * </p>
101    *
102    * @param declaringClass The class that declares this method. Must not be <jk>null</jk>.
103    * @param inner The method being wrapped. Must not be <jk>null</jk>.
104    * @return A new MethodInfo object wrapping the method.
105    */
106   public static MethodInfo of(Class<?> declaringClass, Method inner) {
107      return ClassInfo.of(declaringClass).getMethod(inner);
108   }
109
110   /**
111    * Creates a MethodInfo wrapper for the specified method.
112    *
113    * <h5 class='section'>Example:</h5>
114    * <p class='bjava'>
115    *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
116    *    Method <jv>m</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>);
117    *    MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(<jv>ci</jv>, <jv>m</jv>);
118    * </p>
119    *
120    * @param declaringClass The ClassInfo for the class that declares this method. Must not be <jk>null</jk>.
121    * @param inner The method being wrapped. Must not be <jk>null</jk>.
122    * @return A new MethodInfo object wrapping the method.
123    */
124   public static MethodInfo of(ClassInfo declaringClass, Method inner) {
125      reqnn("declaringClass", declaringClass);
126      return declaringClass.getMethod(inner);
127   }
128
129   /**
130    * Creates a MethodInfo wrapper for the specified method.
131    *
132    * <p>
133    * This convenience method automatically determines the declaring class from the method.
134    *
135    * <h5 class='section'>Example:</h5>
136    * <p class='bjava'>
137    *    Method <jv>m</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>);
138    *    MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(<jv>m</jv>);
139    * </p>
140    *
141    * @param inner The method being wrapped. Must not be <jk>null</jk>.
142    * @return A new MethodInfo object wrapping the method.
143    */
144   public static MethodInfo of(Method inner) {
145      reqnn("inner", inner);
146      return ClassInfo.of(inner.getDeclaringClass()).getMethod(inner);
147   }
148
149   private final Method inner;
150   private final Supplier<ClassInfo> returnType;
151   private final Supplier<List<MethodInfo>> matchingMethods;
152   private final Supplier<List<AnnotationInfo<Annotation>>> annotations;
153
154   /**
155    * Constructor.
156    *
157    * <p>
158    * Creates a new MethodInfo wrapper for the specified method. This constructor is protected
159    * and should not be called directly. Use the static factory methods {@link #of(Method)} or
160    * obtain MethodInfo instances from {@link ClassInfo#getMethod(Method)}.
161    *
162    * @param declaringClass The ClassInfo for the class that declares this method. Must not be <jk>null</jk>.
163    * @param inner The method being wrapped. Must not be <jk>null</jk>.
164    */
165   protected MethodInfo(ClassInfo declaringClass, Method inner) {
166      super(declaringClass, inner);
167      this.inner = inner;
168      this.returnType = memoize(() -> ClassInfo.of(inner.getReturnType(), inner.getGenericReturnType()));
169      this.matchingMethods = memoize(this::findMatchingMethods);
170      this.annotations = memoize(() -> getMatchingMethods().stream().flatMap(m -> m.getDeclaredAnnotations().stream()).toList());
171   }
172
173   @Override /* Overridden from ExecutableInfo */
174   public MethodInfo accessible() {
175      super.accessible();
176      return this;
177   }
178
179   @Override
180   public int compareTo(MethodInfo o) {
181      int i = cmp(getNameSimple(), o.getNameSimple());
182      if (i == 0) {
183         var params = getParameters();
184         var oParams = o.getParameters();
185         i = params.size() - oParams.size();
186         if (i == 0) {
187            for (var j = 0; j < params.size() && i == 0; j++) {
188               i = cmp(params.get(j).getParameterType().getName(), oParams.get(j).getParameterType().getName());
189            }
190         }
191      }
192      // Tie-breaker: if methods have same name and parameters (e.g., overridden methods),
193      // prefer methods from child classes over parent classes
194      // This ensures consistent ordering and that child methods (with covariant return types) come first
195      if (i == 0) {
196         var declaringClass = getDeclaringClass();
197         var oDeclaringClass = o.getDeclaringClass();
198         if (declaringClass.isChildOf(oDeclaringClass)) {
199            i = -1; // This method is from a child class, so it comes first
200         } else if (oDeclaringClass.isChildOf(declaringClass)) {
201            i = 1; // Other method is from a child class, so it comes first
202         } else {
203            i = cmp(declaringClass.getName(), oDeclaringClass.getName()); // Neither is a child of the other, compare by class name for deterministic ordering
204         }
205      }
206      return i;
207   }
208
209   @Override /* Annotatable */
210   public AnnotatableType getAnnotatableType() { return AnnotatableType.METHOD_TYPE; }
211
212   /**
213    * Returns an {@link AnnotatedType} object that represents the use of a type to specify the return type of the method.
214    *
215    * <p>
216    * Same as calling {@link Method#getAnnotatedReturnType()}.
217    *
218    * <h5 class='section'>Example:</h5>
219    * <p class='bjava'>
220    *    <jc>// For method: public @NotNull String getName()</jc>
221    *    MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"getName"</js>);
222    *    AnnotatedType <jv>aType</jv> = <jv>mi</jv>.getAnnotatedReturnType();
223    *    <jc>// Check for @NotNull on the return type</jc>
224    * </p>
225    *
226    * @return An {@link AnnotatedType} object representing the return type.
227    * @see Method#getAnnotatedReturnType()
228    */
229   public AnnotatedType getAnnotatedReturnType() { return inner.getAnnotatedReturnType(); }
230
231   /**
232    * Returns all annotations on this method and parent overridden methods in child-to-parent order.
233    *
234    * <p>
235    * Results include annotations from:
236    * <ul>
237    *    <li>This method
238    *    <li>Matching methods in parent classes
239    *    <li>Matching methods in interfaces
240    * </ul>
241    *
242    * <p>
243    * <b>Note on Repeatable Annotations:</b>
244    * Repeatable annotations (those marked with {@link Repeatable @Repeatable}) are automatically
245    * expanded into their individual annotation instances. For example, if a method has multiple {@code @Marshalled} annotations,
246    * this method returns each {@code @Marshalled} annotation separately, rather than the container annotation.
247    *
248    * <p>
249    * List is unmodifiable.
250    *
251    * @return
252    *    A list of all annotations on this method and overridden methods.
253    *    <br>Repeatable annotations are expanded into individual instances.
254    */
255   public List<AnnotationInfo<Annotation>> getAnnotations() { return annotations.get(); }
256
257   /**
258    * Returns all annotations of the specified type on this method and parent overridden methods in child-to-parent order.
259    *
260    * <p>
261    * Results include annotations from:
262    * <ul>
263    *    <li>This method
264    *    <li>Matching methods in parent classes
265    *    <li>Matching methods in interfaces
266    * </ul>
267    *
268    * <p>
269    * <b>Note on Repeatable Annotations:</b>
270    * If the specified annotation type is repeatable (marked with {@link Repeatable @Repeatable}),
271    * this method automatically expands container annotations into individual instances. This allows you to filter for
272    * a repeatable annotation and get back all individual occurrences without manually handling the container.
273    *
274    * <h5 class='section'>Example:</h5>
275    * <p class='bjava'>
276    *    <jc>// Get all @Marshalled annotations on this method and overridden methods</jc>
277    *    Stream&lt;AnnotationInfo&lt;Bean&gt;&gt; <jv>beans</jv> = <jv>methodInfo</jv>.getAnnotations(Bean.<jk>class</jk>);
278    *    <jc>// If method has @Beans({@Marshalled(...), @Marshalled(...)}), both individual @Marshalled instances are returned</jc>
279    * </p>
280    *
281    * @param <A> The annotation type.
282    * @param type The annotation type to filter by. Must not be <jk>null</jk>.
283    * @return
284    *    A stream of matching annotation infos in child-to-parent order.
285    *    <br>Repeatable annotations are expanded into individual instances.
286    */
287   public <A extends Annotation> Stream<AnnotationInfo<A>> getAnnotations(Class<A> type) {
288      reqnn("type", type);
289      return getAnnotations().stream().filter(a -> a.isType(type)).map(a -> (AnnotationInfo<A>)a);
290   }
291
292   /**
293    * Returns the default value for the annotation member represented by this method.
294    *
295    * <p>
296    * Same as calling {@link Method#getDefaultValue()}.
297    *
298    * <p>
299    * Returns <jk>null</jk> if this method is not an annotation member, or if the annotation member has no default value.
300    *
301    * <h5 class='section'>Example:</h5>
302    * <p class='bjava'>
303    *    <jc>// For annotation: @interface MyAnnotation { String value() default "default"; }</jc>
304    *    MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyAnnotation.<jk>class</jk>).getMethod(<js>"value"</js>);
305    *    Object <jv>defaultValue</jv> = <jv>mi</jv>.getDefaultValue();
306    *    <jc>// defaultValue is "default"</jc>
307    * </p>
308    *
309    * @return The default value, or <jk>null</jk> if none.
310    * @see Method#getDefaultValue()
311    */
312   public Object getDefaultValue() { return inner.getDefaultValue(); }
313
314   /**
315    * Returns a {@link Type} object that represents the formal return type of the method.
316    *
317    * <p>
318    * Same as calling {@link Method#getGenericReturnType()}.
319    *
320    * <p>
321    * If the return type is a parameterized type, the {@link Type} object returned reflects the actual type parameters used in the source code.
322    *
323    * <h5 class='section'>Example:</h5>
324    * <p class='bjava'>
325    *    <jc>// For method: public List&lt;String&gt; getValues()</jc>
326    *    MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"getValues"</js>);
327    *    Type <jv>returnType</jv> = <jv>mi</jv>.getGenericReturnType();
328    *    <jk>if</jk> (<jv>returnType</jv> <jk>instanceof</jk> ParameterizedType) {
329    *       ParameterizedType <jv>pType</jv> = (ParameterizedType)<jv>returnType</jv>;
330    *       <jc>// pType.getActualTypeArguments()[0] is String.class</jc>
331    *    }
332    * </p>
333    *
334    * @return A {@link Type} object representing the formal return type.
335    * @see Method#getGenericReturnType()
336    */
337   public Type getGenericReturnType() { return inner.getGenericReturnType(); }
338
339   @Override /* Annotatable */
340   public String getLabel() { return getDeclaringClass().getNameSimple() + "." + getNameShort(); }
341
342   /**
343    * Returns this method and all matching methods up the hierarchy chain.
344    *
345    * <p>
346    * Searches parent classes and interfaces for methods with matching name and parameter types.
347    * Results are returned in the following order:
348    * <ol>
349    *    <li>This method
350    *    <li>Any matching methods on declared interfaces of this class
351    *    <li>Matching method on the parent class
352    *    <li>Any matching methods on the declared interfaces of the parent class
353    *    <li>Continue up the hierarchy
354    * </ol>
355    *
356    * <h5 class='section'>Examples:</h5>
357    * <p class='bjava'>
358    *    <jc>// Interface and class hierarchy:</jc>
359    *    <jk>interface</jk> I1 {
360    *       <jk>void</jk> foo(String <jv>s</jv>);
361    *    }
362    *    <jk>class</jk> A {
363    *       <jk>void</jk> foo(String <jv>s</jv>) {}
364    *    }
365    *    <jk>interface</jk> I2 {
366    *       <jk>void</jk> foo(String <jv>s</jv>);
367    *    }
368    *    <jk>class</jk> B <jk>extends</jk> A <jk>implements</jk> I2 {
369    *       &#64;Override
370    *       <jk>void</jk> foo(String <jv>s</jv>) {}
371    *    }
372    *    <jc>// For B.foo(), returns: [B.foo, I2.foo, A.foo, I1.foo]</jc>
373    *    MethodInfo <jv>mi</jv> = ...;
374    *    List&lt;MethodInfo&gt; <jv>matching</jv> = <jv>mi</jv>.getMatchingMethods();
375    * </p>
376    *
377    * @return A list of matching methods including this one, in child-to-parent order.
378    */
379   public List<MethodInfo> getMatchingMethods() { return matchingMethods.get(); }
380
381   /**
382    * Returns the name of this method.
383    *
384    * @return The name of this method
385    */
386   public String getName() { return inner.getName(); }
387
388   /**
389    * Returns the bean property name if this is a getter or setter.
390    *
391    * @return The bean property name, or <jk>null</jk> if this isn't a getter or setter.
392    */
393   public String getPropertyName() {
394      String n = inner.getName();
395      if ((n.startsWith("get") || n.startsWith("set")) && n.length() > 3)
396         return Introspector.decapitalize(n.substring(3));
397      if (n.startsWith("is") && n.length() > 2)
398         return Introspector.decapitalize(n.substring(2));
399      return n;
400   }
401
402   /**
403    * Returns <jk>true</jk> if this method follows the Java Bean getter convention.
404    *
405    * <p>
406    * A getter is a public no-argument method whose name starts with {@code get} (with a non-void return type)
407    * or {@code is} (with a {@code boolean}/{@link Boolean} return type).
408    *
409    * @return <jk>true</jk> if this method is a bean getter.
410    */
411   public boolean isGetter() {
412      var n = inner.getName();
413      if (getParameterCount() != 0)
414         return false;
415      if (n.startsWith("get") && n.length() > 3)
416         return !getReturnType().is(Void.TYPE);
417      if (n.startsWith("is") && n.length() > 2) {
418         var rt = getReturnType();
419         return rt.is(Boolean.TYPE) || rt.is(Boolean.class);
420      }
421      return false;
422   }
423
424   /**
425    * Returns <jk>true</jk> if this method follows the Java Bean setter convention.
426    *
427    * <p>
428    * A setter is a method whose name starts with {@code set} and accepts exactly one argument.
429    *
430    * @return <jk>true</jk> if this method is a bean setter.
431    */
432   public boolean isSetter() {
433      var n = inner.getName();
434      return n.startsWith("set") && n.length() > 3 && getParameterCount() == 1;
435   }
436
437   /**
438    * Returns the generic return type of this method as a {@link ClassInfo} object.
439    *
440    * @return The generic return type of this method.
441    */
442   public ClassInfo getReturnType() { return returnType.get(); }
443
444   /**
445    * Returns the signature of this method.
446    *
447    * <p>
448    * For no-arg methods, the signature will be a simple string such as <js>"toString"</js>.
449    * For methods with one or more args, the arguments will be fully-qualified class names (e.g.
450    * <js>"append(java.util.StringBuilder,boolean)"</js>)
451    *
452    * @return The methods signature.
453    */
454   public String getSignature() {
455      var sb = new StringBuilder(128);
456      sb.append(inner.getName());
457      var params = getParameters();
458      if (!params.isEmpty()) {
459         sb.append('(');
460         for (var i = 0; i < params.size(); i++) {
461            if (i > 0)
462               sb.append(',');
463            params.get(i).getParameterType().appendNameFormatted(sb, ClassNameFormat.FULL, true, '$', ClassArrayFormat.BRACKETS);
464         }
465         sb.append(')');
466      }
467      return sb.toString();
468   }
469
470   /**
471    * Returns <jk>true</jk> if this method has at least the specified parameters.
472    *
473    * <p>
474    * Method may or may not have additional parameters besides those specified.
475    *
476    * @param requiredParams The parameter types to check for.
477    * @return <jk>true</jk> if this method has at least the specified parameters.
478    */
479   public boolean hasAllParameters(Class<?>...requiredParams) {
480      var paramTypes = getParameters().stream().map(p -> p.getParameterType().inner()).toList();
481
482      for (var c : requiredParams)
483         if (! paramTypes.contains(c))
484            return false;
485
486      return true;
487   }
488
489   /**
490    * Returns <jk>true</jk> if the specified annotation is present on this method or any matching methods in parent classes/interfaces.
491    *
492    * <p>
493    * This method searches through all matching methods in the hierarchy.
494    *
495    * @param <A> The annotation type to look for.
496    * @param type The annotation to look for.
497    * @return <jk>true</jk> if the specified annotation is present on this method.
498    */
499   @Override
500   public <A extends Annotation> boolean hasAnnotation(Class<A> type) {
501      return getAnnotations(type).findAny().isPresent();
502   }
503
504   /**
505    * Returns <jk>true</jk> if the parameters on the method only consist of the types specified in the list.
506    *
507    * <p>
508    * <b>Note:</b> This method is not meant to be used on methods with duplicate parameter types.
509    * It checks if each parameter type is present in the specified list, but does not verify
510    * that the count of each type matches exactly.
511    *
512    * <h5 class='figure'>Example:</h5>
513    * <p class='bjava'>
514    *
515    *    <jc>// Example method:</jc>
516    *    <jk>public void</jk> foo(String <jv>bar</jv>, Integer <jv>baz</jv>);
517    *
518    *    <jv>fooMethod</jv>.hasOnlyParameterTypes(String.<jk>class</jk>, Integer.<jk>class</jk>);  <jc>// True.</jc>
519    *    <jv>fooMethod</jv>.hasOnlyParameterTypes(String.<jk>class</jk>, Integer.<jk>class</jk>, Map.<jk>class</jk>);  <jc>// True.</jc>
520    *    <jv>fooMethod</jv>.hasOnlyParameterTypes(String.<jk>class</jk>);  <jc>// False.</jc>
521    * </p>
522    *
523    * @param args The valid class types (exact) for the arguments.
524    * @return <jk>true</jk> if the method parameters only consist of the types specified in the list.
525    */
526   public boolean hasOnlyParameterTypes(Class<?>...args) {
527      for (var param : getParameters()) {
528         var c1 = param.getParameterType().inner();
529         var foundMatch = false;
530         for (var c2 : args)
531            if (c1 == c2)
532               foundMatch = true;
533         if (! foundMatch)
534            return false;
535      }
536      return true;
537   }
538
539   /**
540    * Returns <jk>true</jk> if this method has the specified parameter.
541    *
542    * <p>
543    * Method may or may not have additional parameters besides the one specified.
544    *
545    * @param requiredParam The parameter type to check for.
546    * @return <jk>true</jk> if this method has at least the specified parameter.
547    */
548   public boolean hasParameter(Class<?> requiredParam) {
549      return hasAllParameters(requiredParam);
550   }
551
552   /**
553    * Returns <jk>true</jk> if this method has a parameter that can accept the specified object.
554    *
555    * @param parameter The parameter object to check for.
556    * @return <jk>true</jk> if this method has at least one parameter that can accept the specified object.
557    */
558   public boolean hasParameter(Object parameter) {
559      return getParameters().stream().anyMatch(x -> x.canAccept(parameter));
560   }
561
562   /**
563    * Returns <jk>true</jk> if this method has this return type.
564    *
565    * @param c The return type to test for.
566    * @return <jk>true</jk> if this method has this return type.
567    */
568   public boolean hasReturnType(Class<?> c) {
569      return inner.getReturnType() == c;
570   }
571
572   /**
573    * Returns <jk>true</jk> if this method has this return type.
574    *
575    * @param ci The return type to test for.
576    * @return <jk>true</jk> if this method has this return type.
577    */
578   public boolean hasReturnType(ClassInfo ci) {
579      return hasReturnType(ci.inner());
580   }
581
582   /**
583    * Returns <jk>true</jk> if this method has this parent return type.
584    *
585    * @param c The return type to test for.
586    * @return <jk>true</jk> if this method has this parent return type.
587    */
588   public boolean hasReturnTypeParent(Class<?> c) {
589      return ClassInfo.of(c).isAssignableFrom(inner.getReturnType());
590   }
591
592   /**
593    * Returns <jk>true</jk> if this method has this parent return type.
594    *
595    * @param ci The return type to test for.
596    * @return <jk>true</jk> if this method has this parent return type.
597    */
598   public boolean hasReturnTypeParent(ClassInfo ci) {
599      return hasReturnTypeParent(ci.inner());
600   }
601
602   /**
603    * Returns the wrapped method.
604    *
605    * @return The wrapped method.
606    */
607   public Method inner() {
608      return inner;
609   }
610
611   /**
612    * Compares this MethodInfo with the specified object for equality.
613    *
614    * <p>
615    * Two MethodInfo objects are considered equal if they wrap the same underlying {@link Method} object.
616    * This delegates to the underlying {@link Method#equals(Object)} method.
617    *
618    * <p>
619    * This method makes MethodInfo suitable for use as keys in hash-based collections such as {@link HashMap}
620    * and {@link HashSet}.
621    *
622    * @param obj The object to compare with.
623    * @return <jk>true</jk> if the objects are equal, <jk>false</jk> otherwise.
624    */
625   @Override
626   public boolean equals(Object obj) {
627      return obj instanceof MethodInfo obj2 && eq(this, obj2, (x, y) -> eq(x.inner, y.inner) && eq(x.declaringClass, y.declaringClass));
628   }
629
630   /**
631    * Returns a hash code value for this MethodInfo.
632    *
633    * <p>
634    * This combines the hash code of the underlying {@link Method} with the hash code of the declaring class
635    * to ensure that methods from different subclasses that inherit the same method are not considered equal.
636    *
637    * <p>
638    * This method makes MethodInfo suitable for use as keys in hash-based collections such as {@link HashMap}
639    * and {@link HashSet}.
640    *
641    * @return A hash code value for this MethodInfo.
642    */
643   @Override
644   public int hashCode() {
645      return 31 * inner.hashCode() + declaringClass.hashCode();
646   }
647
648   /**
649    * Shortcut for calling the invoke method on the underlying method.
650    *
651    * <p>
652    * If the underlying method is static, then the specified <c>obj</c> argument is ignored. It may be <jk>null</jk>.
653    *
654    * <p>
655    * If the number of formal parameters required by the underlying method is 0, the supplied <c>args</c> array may be of length 0 or <jk>null</jk>.
656    *
657    * @param <T> The method return type.
658    * @param obj The object the underlying method is invoked from.  Can be <jk>null</jk> for static methods.
659    * @param args The arguments used for the method call.  Can be <jk>null</jk> or empty for methods with no parameters.
660    * @return The object returned from the method.
661    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
662    */
663   public <T> T invoke(Object obj, Object...args) throws ExecutableException {
664      try {
665         return (T)inner.invoke(obj, args);
666      } catch (InvocationTargetException e) {
667         throw exex(e.getTargetException());
668      } catch (RuntimeException e) {
669         throw e;
670      } catch (Exception | ExceptionInInitializerError e) {
671         // Method.invoke() throws ExceptionInInitializerError directly when it triggers a failing static initializer.
672         throw exex(e);
673      }
674   }
675
676   /**
677    * Invokes the specified method using lenient argument matching.
678    *
679    * <p>
680    * Lenient matching allows arguments to be matched to parameters based on parameter types.
681    * <br>Arguments can be in any order.
682    * <br>Extra arguments will be ignored.
683    * <br>Missing arguments will be left <jk>null</jk>.
684    *
685    * <p>
686    * Note that this only works for methods that have distinguishable argument types.
687    * <br>It's not going to work on methods with generic argument types like <c>Object</c>
688    *
689    * @param pojo
690    *    The POJO the method is being called on.
691    *    <br>Can be <jk>null</jk> for static methods.
692    * @param args
693    *    The arguments to pass to the method.
694    * @return
695    *    The results of the method invocation.
696    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
697    */
698   public Object invokeLenient(Object pojo, Object...args) throws ExecutableException {
699      return safe((ThrowingSupplier<Object>)() -> inner.invoke(pojo, ClassUtils.getMatchingArgs(inner.getParameterTypes(), args)), e -> exex(e instanceof InvocationTargetException e2 ? e2.getTargetException() : e));
700   }
701
702   @Override
703   public boolean is(ElementFlag flag) {
704      return switch (flag) {
705         case BRIDGE -> isBridge();
706         case NOT_BRIDGE -> ! isBridge();
707         case DEFAULT -> isDefault();
708         case NOT_DEFAULT -> ! isDefault();
709         default -> super.is(flag);
710      };
711   }
712
713   //-----------------------------------------------------------------------------------------------------------------
714   // High Priority Methods (direct Method API compatibility)
715   //-----------------------------------------------------------------------------------------------------------------
716
717   /**
718    * Returns <jk>true</jk> if this method is a bridge method.
719    *
720    * @return <jk>true</jk> if this method is a bridge method.
721    */
722   public boolean isBridge() { return inner.isBridge(); }
723
724   /**
725    * Returns <jk>true</jk> if this method is a default method (Java 8+ interface default method).
726    *
727    * <p>
728    * Same as calling {@link Method#isDefault()}.
729    *
730    * <p>
731    * A default method is a public non-abstract instance method (i.e., non-static method with a body) declared in an interface.
732    *
733    * <h5 class='section'>Example:</h5>
734    * <p class='bjava'>
735    *    <jc>// For interface: interface MyInterface { default String getName() { return "default"; } }</jc>
736    *    MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyInterface.<jk>class</jk>).getMethod(<js>"getName"</js>);
737    *    <jk>if</jk> (<jv>mi</jv>.isDefault()) {
738    *       <jc>// This is a default interface method</jc>
739    *    }
740    * </p>
741    *
742    * @return <jk>true</jk> if this method is a default method.
743    * @see Method#isDefault()
744    */
745   public boolean isDefault() { return inner.isDefault(); }
746
747   /**
748    * Returns <jk>true</jk> if this method matches the specified method by name and parameter types.
749    *
750    * @param m The method to compare against.
751    * @return <jk>true</jk> if this method has the same name and parameter types as the specified method.
752    */
753   public boolean matches(MethodInfo m) {
754      return hasName(m.getName()) && hasMatchingParameters(m.getParameters());
755   }
756
757   private void addMatchingMethodsFromInterface(List<MethodInfo> result, ClassInfo iface) {
758      // Add matching methods from this interface
759      iface.getDeclaredMethods().stream().filter(this::matches).forEach(result::add);
760
761      // Recursively search parent interfaces
762      iface.getDeclaredInterfaces().forEach(pi -> addMatchingMethodsFromInterface(result, pi));
763   }
764
765   //-----------------------------------------------------------------------------------------------------------------
766   // Annotatable interface methods
767   //-----------------------------------------------------------------------------------------------------------------
768
769   private List<MethodInfo> findMatchingMethods() {
770      var result = new ArrayList<MethodInfo>();
771      result.add(this); // 1. This method
772
773      var cc = getDeclaringClass();
774
775      while (nn(cc)) {
776         // 2. Add matching methods from declared interfaces of current class
777         cc.getDeclaredInterfaces().forEach(di -> addMatchingMethodsFromInterface(result, di));
778
779         // 3. Move to parent class
780         cc = cc.getSuperclass();
781         if (nn(cc)) {
782            // Add matching method from parent class
783            cc.getDeclaredMethods().stream().filter(this::matches).findFirst().ifPresent(result::add);
784         }
785      }
786
787      return result;
788   }
789
790   /**
791    * Returns comma-delimited list of missing parameter types.
792    *
793    * <p>
794    * Analyzes the parameters of this method and checks if all required beans are available
795    * in the bean store or in the <c>otherBeans</c> parameter.
796    *
797    * <p>
798    * The following parameter types are considered optional and are not checked:
799    * <ul>
800    *    <li>Parameters of type <c>Optional&lt;T&gt;</c>
801    *    <li>Parameters of type <c>T[]</c>, <c>List&lt;T&gt;</c>, <c>Set&lt;T&gt;</c>, or <c>Map&lt;String,T&gt;</c>
802    * </ul>
803    *
804    * <p>
805    * If a parameter has a <ja>@Named</ja> or <ja>@Qualifier</ja> annotation,
806    * the method checks for a bean with that specific name in the bean store.  Otherwise, it checks for an unnamed bean
807    * of the parameter type in the bean store, and if not found, checks the <c>otherBeans</c> parameter.
808    *
809    * @param beanStore The bean store to check for beans.
810    * @param otherBeans Optional additional bean instances to check if not found in the bean store.
811    *    These are checked after the bean store but before marking the parameter as missing.
812    * @return A comma-delimited, sorted list of missing parameter types (e.g., <js>"String,Integer"</js>),
813    *    or <jk>null</jk> if all required parameters are available.
814    */
815   public String getMissingParameterTypes(BeanStore beanStore, Object... otherBeans) {
816      // @formatter:off
817      return nullIfEmpty(
818         getParameters()
819            .stream()
820            .map(x -> x.getMissingType(beanStore, otherBeans))
821            .filter(Objects::nonNull)
822            .sorted()
823            .collect(joining(","))
824      );
825      // @formatter:on
826   }
827
828   /**
829    * Checks if all method parameters can be resolved.
830    *
831    * <p>
832    * This method performs the same checks as {@link #getMissingParameterTypes(BeanStore, Object...)} but
833    * returns a boolean instead of a list of missing types.
834    *
835    * <p>
836    * The following parameter types are considered optional and are not checked:
837    * <ul>
838    *    <li>Parameters of type <c>Optional&lt;T&gt;</c>
839    *    <li>Parameters of type <c>T[]</c>, <c>List&lt;T&gt;</c>, <c>Set&lt;T&gt;</c>, or <c>Map&lt;String,T&gt;</c>
840    * </ul>
841    *
842    * <p>
843    * If a parameter has a <ja>@Named</ja> or <ja>@Qualifier</ja> annotation,
844    * the method checks for a bean with that specific name in the bean store.  Otherwise, it checks for an unnamed bean
845    * of the parameter type in the bean store, and if not found, checks the <c>otherBeans</c> parameter.
846    *
847    * @param beanStore The bean store to check for beans.
848    * @param otherBeans Optional additional bean instances to check if not found in the bean store.
849    *    These are checked after the bean store but before marking the parameter as missing.
850    * @return <jk>true</jk> if all required parameters are available in the bean store or <c>otherBeans</c>, <jk>false</jk> otherwise.
851    */
852   public boolean canResolveAllParameters(BeanStore beanStore, Object... otherBeans) {
853      return getParameters().stream().map(x -> x.canResolve(beanStore, otherBeans)).allMatch(x -> x);
854   }
855
856   /**
857    * Resolves parameters from the bean store and invokes the method.
858    *
859    * <p>
860    * For each parameter in the method, this method:
861    * <ul>
862    *    <li>If the parameter is a collection/array/map type, collects all beans of the element type.
863    *    <li>Otherwise, looks up a single bean by type and optional qualifier name in the bean store.
864    *    <li>If not found in the bean store, checks the <c>otherBeans</c> parameter.
865    * </ul>
866    *
867    * <h5 class='section'>Parameter Resolution:</h5>
868    * <ul class='spaced-list'>
869    *    <li><b>Single beans</b> - Resolved using {@link BeanStore#getBean(Class)} or {@link BeanStore#getBean(Class, String)}.
870    *       If not found, checks <c>otherBeans</c> for a compatible instance.
871    *       Throws {@link ExecutableException} if not found in either location.
872    *    <li><b>Optional beans</b> - Wrapped in <c>Optional</c>, or <c>Optional.empty()</c> if not found.
873    *       Never throws an exception.
874    *    <li><b>Arrays</b> - All beans of the element type are collected into an array (may be empty).
875    *       Never throws an exception.
876    *    <li><b>Lists</b> - All beans of the element type are collected into a <c>List</c> (may be empty).
877    *       Never throws an exception.
878    *    <li><b>Sets</b> - All beans of the element type are collected into a <c>LinkedHashSet</c> (may be empty).
879    *       Never throws an exception.
880    *    <li><b>Maps</b> - All beans of the value type are collected into a <c>LinkedHashMap</c> keyed by bean name (may be empty).
881    *       Unnamed beans use an empty string as the key.  Never throws an exception.
882    * </ul>
883    *
884    * <p>
885    * The <c>bean</c> parameter is the object instance on which to invoke the method (or <jk>null</jk> for static methods).
886    *
887    * @param <T> The return type of the method.
888    * @param beanStore The bean store to resolve parameters from.
889    * @param bean The object instance on which to invoke the method (or <jk>null</jk> for static methods).
890    * @param otherBeans Optional additional bean instances to use if not found in the bean store.
891    * @return The result of invoking the method.
892    * @throws ExecutableException If the method cannot be invoked or parameter resolution fails.
893    */
894   public <T> T inject(BeanStore beanStore, Object bean, Object... otherBeans) {
895      var params = getParameters().stream().map(x -> x.resolveValue(beanStore, otherBeans)).toArray();
896      return accessible().invoke(bean, params);
897   }
898
899   /**
900    * Resolves all method parameters from the bean store.
901    *
902    * <p>
903    * This is a package-private helper method used by tests.  For normal usage, use {@link #inject(BeanStore, Object, Object...)}.
904    *
905    * @param beanStore The bean store to resolve beans from.
906    * @param otherBeans Optional additional bean instances to use if not found in the bean store.
907    * @return An array of parameter values in the same order as the method parameters.
908    * @throws ExecutableException If a required parameter cannot be resolved.
909    */
910   Object[] resolveParameters(BeanStore beanStore, Object... otherBeans) {
911      return getParameters().stream().map(x -> x.resolveValue(beanStore, otherBeans)).toArray();
912   }
913}