View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.commons.reflect;
18  
19  import static org.apache.juneau.commons.utils.AssertionUtils.*;
20  import static org.apache.juneau.commons.utils.ThrowableUtils.*;
21  import static org.apache.juneau.commons.utils.Utils.*;
22  
23  import java.beans.*;
24  import java.lang.annotation.*;
25  import java.lang.reflect.*;
26  import java.util.*;
27  import java.util.function.*;
28  import java.util.stream.*;
29  
30  import org.apache.juneau.commons.utils.*;
31  
32  /**
33   * Lightweight utility class for introspecting information about a Java method.
34   *
35   * <p>
36   * This class provides a convenient wrapper around {@link Method} that extends the standard Java reflection
37   * API with additional functionality for method introspection, annotation handling, and hierarchy traversal.
38   * It's designed to be lightweight, thread-safe, and cached for efficient reuse.
39   *
40   * <h5 class='section'>Features:</h5>
41   * <ul class='spaced-list'>
42   * 	<li>Method introspection - access method metadata, parameters, return type, exceptions
43   * 	<li>Annotation support - get annotations from method and overridden methods in hierarchy
44   * 	<li>Hierarchy traversal - find matching methods in parent classes and interfaces
45   * 	<li>Type-safe access - wrapper around reflection with convenient methods
46   * 	<li>Thread-safe - instances are immutable and safe for concurrent access
47   * </ul>
48   *
49   * <h5 class='section'>Use Cases:</h5>
50   * <ul class='spaced-list'>
51   * 	<li>Introspecting method metadata for code generation or analysis
52   * 	<li>Finding annotations on methods including those from parent classes
53   * 	<li>Discovering method hierarchies and overridden methods
54   * 	<li>Working with method parameters and return types
55   * 	<li>Building frameworks that need to analyze method signatures
56   * </ul>
57   *
58   * <h5 class='section'>Usage:</h5>
59   * <p class='bjava'>
60   * 	<jc>// Get MethodInfo from a class</jc>
61   * 	ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
62   * 	MethodInfo <jv>method</jv> = <jv>ci</jv>.getMethod(<js>"myMethod"</js>);
63   *
64   * 	<jc>// Get return type</jc>
65   * 	ClassInfo <jv>returnType</jv> = <jv>method</jv>.getReturnType();
66   *
67   * 	<jc>// Get annotations including from parent methods</jc>
68   * 	List&lt;AnnotationInfo&lt;MyAnnotation&gt;&gt; <jv>annotations</jv> =
69   * 		<jv>method</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList();
70   *
71   * 	<jc>// Find matching methods in hierarchy</jc>
72   * 	List&lt;MethodInfo&gt; <jv>matching</jv> = <jv>method</jv>.getMatchingMethods();
73   * </p>
74   *
75   * <h5 class='section'>See Also:</h5><ul>
76   * 	<li class='jc'>{@link ClassInfo} - Class introspection
77   * 	<li class='jc'>{@link FieldInfo} - Field introspection
78   * 	<li class='jc'>{@link ConstructorInfo} - Constructor introspection
79   * 	<li class='jc'>{@link ParameterInfo} - Parameter introspection
80   * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a>
81   * </ul>
82   */
83  public class MethodInfo extends ExecutableInfo implements Comparable<MethodInfo>, Annotatable {
84  	/**
85  	 * Creates a MethodInfo wrapper for the specified method.
86  	 *
87  	 * <h5 class='section'>Example:</h5>
88  	 * <p class='bjava'>
89  	 * 	Method <jv>m</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>);
90  	 * 	MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <jv>m</jv>);
91  	 * </p>
92  	 *
93  	 * @param declaringClass The class that declares this method. Must not be <jk>null</jk>.
94  	 * @param inner The method being wrapped. Must not be <jk>null</jk>.
95  	 * @return A new MethodInfo object wrapping the method.
96  	 */
97  	public static MethodInfo of(Class<?> declaringClass, Method inner) {
98  		return ClassInfo.of(declaringClass).getMethod(inner);
99  	}
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 }