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<AnnotationInfo<MyAnnotation>> <jv>annotations</jv> = 073 * <jv>method</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList(); 074 * 075 * <jc>// Find matching methods in hierarchy</jc> 076 * List<MethodInfo> <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<AnnotationInfo<Bean>> <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<String> 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 * @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<MethodInfo> <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<T></c> 801 * <li>Parameters of type <c>T[]</c>, <c>List<T></c>, <c>Set<T></c>, or <c>Map<String,T></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<T></c> 839 * <li>Parameters of type <c>T[]</c>, <c>List<T></c>, <c>Set<T></c>, or <c>Map<String,T></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}