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<AnnotationInfo<MyAnnotation>> <jv>annotations</jv> =
69 * <jv>method</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList();
70 *
71 * <jc>// Find matching methods in hierarchy</jc>
72 * List<MethodInfo> <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<AnnotationInfo<Bean>> <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<String> 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 * @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<MethodInfo> <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 }