001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.reflect; 018 019import static org.apache.juneau.commons.utils.AssertionUtils.*; 020import static org.apache.juneau.commons.utils.ThrowableUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022 023import java.beans.*; 024import java.lang.annotation.*; 025import java.lang.reflect.*; 026import java.util.*; 027import java.util.function.*; 028import java.util.stream.*; 029 030import org.apache.juneau.commons.utils.*; 031 032/** 033 * Lightweight utility class for introspecting information about a Java method. 034 * 035 * <p> 036 * This class provides a convenient wrapper around {@link Method} that extends the standard Java reflection 037 * API with additional functionality for method introspection, annotation handling, and hierarchy traversal. 038 * It's designed to be lightweight, thread-safe, and cached for efficient reuse. 039 * 040 * <h5 class='section'>Features:</h5> 041 * <ul class='spaced-list'> 042 * <li>Method introspection - access method metadata, parameters, return type, exceptions 043 * <li>Annotation support - get annotations from method and overridden methods in hierarchy 044 * <li>Hierarchy traversal - find matching methods in parent classes and interfaces 045 * <li>Type-safe access - wrapper around reflection with convenient methods 046 * <li>Thread-safe - instances are immutable and safe for concurrent access 047 * </ul> 048 * 049 * <h5 class='section'>Use Cases:</h5> 050 * <ul class='spaced-list'> 051 * <li>Introspecting method metadata for code generation or analysis 052 * <li>Finding annotations on methods including those from parent classes 053 * <li>Discovering method hierarchies and overridden methods 054 * <li>Working with method parameters and return types 055 * <li>Building frameworks that need to analyze method signatures 056 * </ul> 057 * 058 * <h5 class='section'>Usage:</h5> 059 * <p class='bjava'> 060 * <jc>// Get MethodInfo from a class</jc> 061 * ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>); 062 * MethodInfo <jv>method</jv> = <jv>ci</jv>.getMethod(<js>"myMethod"</js>); 063 * 064 * <jc>// Get return type</jc> 065 * ClassInfo <jv>returnType</jv> = <jv>method</jv>.getReturnType(); 066 * 067 * <jc>// Get annotations including from parent methods</jc> 068 * List<AnnotationInfo<MyAnnotation>> <jv>annotations</jv> = 069 * <jv>method</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList(); 070 * 071 * <jc>// Find matching methods in hierarchy</jc> 072 * List<MethodInfo> <jv>matching</jv> = <jv>method</jv>.getMatchingMethods(); 073 * </p> 074 * 075 * <h5 class='section'>See Also:</h5><ul> 076 * <li class='jc'>{@link ClassInfo} - Class introspection 077 * <li class='jc'>{@link FieldInfo} - Field introspection 078 * <li class='jc'>{@link ConstructorInfo} - Constructor introspection 079 * <li class='jc'>{@link ParameterInfo} - Parameter introspection 080 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a> 081 * </ul> 082 */ 083public class MethodInfo extends ExecutableInfo implements Comparable<MethodInfo>, Annotatable { 084 /** 085 * Creates a MethodInfo wrapper for the specified method. 086 * 087 * <h5 class='section'>Example:</h5> 088 * <p class='bjava'> 089 * Method <jv>m</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>); 090 * MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <jv>m</jv>); 091 * </p> 092 * 093 * @param declaringClass The class that declares this method. Must not be <jk>null</jk>. 094 * @param inner The method being wrapped. Must not be <jk>null</jk>. 095 * @return A new MethodInfo object wrapping the method. 096 */ 097 public static MethodInfo of(Class<?> declaringClass, Method inner) { 098 return ClassInfo.of(declaringClass).getMethod(inner); 099 } 100 101 /** 102 * Creates a MethodInfo wrapper for the specified method. 103 * 104 * <h5 class='section'>Example:</h5> 105 * <p class='bjava'> 106 * ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>); 107 * Method <jv>m</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>); 108 * MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(<jv>ci</jv>, <jv>m</jv>); 109 * </p> 110 * 111 * @param declaringClass The ClassInfo for the class that declares this method. Must not be <jk>null</jk>. 112 * @param inner The method being wrapped. Must not be <jk>null</jk>. 113 * @return A new MethodInfo object wrapping the method. 114 */ 115 public static MethodInfo of(ClassInfo declaringClass, Method inner) { 116 assertArgNotNull("declaringClass", declaringClass); 117 return declaringClass.getMethod(inner); 118 } 119 120 /** 121 * Creates a MethodInfo wrapper for the specified method. 122 * 123 * <p> 124 * This convenience method automatically determines the declaring class from the method. 125 * 126 * <h5 class='section'>Example:</h5> 127 * <p class='bjava'> 128 * Method <jv>m</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>); 129 * MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(<jv>m</jv>); 130 * </p> 131 * 132 * @param inner The method being wrapped. Must not be <jk>null</jk>. 133 * @return A new MethodInfo object wrapping the method. 134 */ 135 public static MethodInfo of(Method inner) { 136 assertArgNotNull("inner", inner); 137 return ClassInfo.of(inner.getDeclaringClass()).getMethod(inner); 138 } 139 140 private final Method inner; 141 private final Supplier<ClassInfo> returnType; 142 private final Supplier<List<MethodInfo>> matchingMethods; 143 private final Supplier<List<AnnotationInfo<Annotation>>> annotations; 144 145 /** 146 * Constructor. 147 * 148 * <p> 149 * Creates a new MethodInfo wrapper for the specified method. This constructor is protected 150 * and should not be called directly. Use the static factory methods {@link #of(Method)} or 151 * obtain MethodInfo instances from {@link ClassInfo#getMethod(Method)}. 152 * 153 * @param declaringClass The ClassInfo for the class that declares this method. 154 * @param inner The method being wrapped. 155 */ 156 protected MethodInfo(ClassInfo declaringClass, Method inner) { 157 super(declaringClass, inner); 158 this.inner = inner; 159 this.returnType = mem(() -> ClassInfo.of(inner.getReturnType(), inner.getGenericReturnType())); 160 this.matchingMethods = mem(this::findMatchingMethods); 161 this.annotations = mem(() -> getMatchingMethods().stream().flatMap(m -> m.getDeclaredAnnotations().stream()).toList()); 162 } 163 164 @Override /* Overridden from ExecutableInfo */ 165 public MethodInfo accessible() { 166 super.accessible(); 167 return this; 168 } 169 170 @Override 171 public int compareTo(MethodInfo o) { 172 int i = cmp(getSimpleName(), o.getSimpleName()); 173 if (i == 0) { 174 var params = getParameters(); 175 var oParams = o.getParameters(); 176 i = params.size() - oParams.size(); 177 if (i == 0) { 178 for (var j = 0; j < params.size() && i == 0; j++) { 179 i = cmp(params.get(j).getParameterType().getName(), oParams.get(j).getParameterType().getName()); 180 } 181 } 182 } 183 return i; 184 } 185 186 @Override /* Annotatable */ 187 public AnnotatableType getAnnotatableType() { return AnnotatableType.METHOD_TYPE; } 188 189 /** 190 * Returns an {@link AnnotatedType} object that represents the use of a type to specify the return type of the method. 191 * 192 * <p> 193 * Same as calling {@link Method#getAnnotatedReturnType()}. 194 * 195 * <h5 class='section'>Example:</h5> 196 * <p class='bjava'> 197 * <jc>// For method: public @NotNull String getName()</jc> 198 * MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"getName"</js>); 199 * AnnotatedType <jv>aType</jv> = <jv>mi</jv>.getAnnotatedReturnType(); 200 * <jc>// Check for @NotNull on the return type</jc> 201 * </p> 202 * 203 * @return An {@link AnnotatedType} object representing the return type. 204 * @see Method#getAnnotatedReturnType() 205 */ 206 public AnnotatedType getAnnotatedReturnType() { return inner.getAnnotatedReturnType(); } 207 208 /** 209 * Returns all annotations on this method and parent overridden methods in child-to-parent order. 210 * 211 * <p> 212 * Results include annotations from: 213 * <ul> 214 * <li>This method 215 * <li>Matching methods in parent classes 216 * <li>Matching methods in interfaces 217 * </ul> 218 * 219 * <p> 220 * <b>Note on Repeatable Annotations:</b> 221 * Repeatable annotations (those marked with {@link java.lang.annotation.Repeatable @Repeatable}) are automatically 222 * expanded into their individual annotation instances. For example, if a method has multiple {@code @Bean} annotations, 223 * this method returns each {@code @Bean} annotation separately, rather than the container annotation. 224 * 225 * <p> 226 * List is unmodifiable. 227 * 228 * @return 229 * A list of all annotations on this method and overridden methods. 230 * <br>Repeatable annotations are expanded into individual instances. 231 */ 232 public List<AnnotationInfo<Annotation>> getAnnotations() { return annotations.get(); } 233 234 /** 235 * Returns all annotations of the specified type on this method and parent overridden methods in child-to-parent order. 236 * 237 * <p> 238 * Results include annotations from: 239 * <ul> 240 * <li>This method 241 * <li>Matching methods in parent classes 242 * <li>Matching methods in interfaces 243 * </ul> 244 * 245 * <p> 246 * <b>Note on Repeatable Annotations:</b> 247 * If the specified annotation type is repeatable (marked with {@link java.lang.annotation.Repeatable @Repeatable}), 248 * this method automatically expands container annotations into individual instances. This allows you to filter for 249 * a repeatable annotation and get back all individual occurrences without manually handling the container. 250 * 251 * <h5 class='section'>Example:</h5> 252 * <p class='bjava'> 253 * <jc>// Get all @Bean annotations on this method and overridden methods</jc> 254 * Stream<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}