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.reflect.ClassArrayFormat.*; 022import static org.apache.juneau.commons.reflect.ClassNameFormat.*; 023import static org.apache.juneau.commons.utils.CollectionUtils.*; 024import static org.apache.juneau.commons.utils.Shorts.*; 025import static org.apache.juneau.commons.utils.ThrowableUtils.*; 026 027import java.lang.annotation.*; 028import java.lang.reflect.*; 029import java.util.*; 030import java.util.function.*; 031import java.util.stream.*; 032 033import org.apache.juneau.commons.utils.*; 034 035/** 036 * Abstract base class containing common functionality for {@link ConstructorInfo} and {@link MethodInfo}. 037 * 038 * <p> 039 * This class provides shared functionality for both constructors and methods, which are both types of 040 * {@link Executable} in Java. It extends {@link AccessibleInfo} to provide {@link AccessibleObject} 041 * functionality for accessing private methods and constructors. 042 * 043 * <h5 class='section'>Features:</h5> 044 * <ul class='spaced-list'> 045 * <li>Parameter introspection - access method/constructor parameters 046 * <li>Exception handling - get declared exceptions 047 * <li>Annotation support - get annotations declared on the executable 048 * <li>Name formatting - get short and fully qualified names 049 * <li>Parameter matching - match parameters by type (strict and lenient) 050 * <li>Accessibility control - make private executables accessible 051 * </ul> 052 * 053 * <h5 class='section'>Use Cases:</h5> 054 * <ul class='spaced-list'> 055 * <li>Working with both methods and constructors in a unified way 056 * <li>Finding methods/constructors that match specific parameter types 057 * <li>Introspecting parameter and exception information 058 * <li>Building frameworks that need to analyze executable signatures 059 * </ul> 060 * 061 * <h5 class='section'>Usage:</h5> 062 * <p class='bjava'> 063 * <jc>// Get ExecutableInfo (could be MethodInfo or ConstructorInfo)</jc> 064 * MethodInfo <jv>mi</jv> = ...; 065 * ExecutableInfo <jv>ei</jv> = <jv>mi</jv>; <jc>// MethodInfo extends ExecutableInfo</jc> 066 * 067 * <jc>// Get parameters</jc> 068 * List<ParameterInfo> <jv>params</jv> = <jv>ei</jv>.getParameters(); 069 * 070 * <jc>// Get exceptions</jc> 071 * List<ClassInfo> <jv>exceptions</jv> = <jv>ei</jv>.getExceptions(); 072 * 073 * <jc>// Check parameter matching</jc> 074 * <jk>boolean</jk> <jv>matches</jv> = <jv>ei</jv>.parameterMatches(String.<jk>class</jk>, Integer.<jk>class</jk>); 075 * </p> 076 * 077 * <h5 class='section'>See Also:</h5><ul> 078 * <li class='jc'>{@link MethodInfo} - Method introspection 079 * <li class='jc'>{@link ConstructorInfo} - Constructor introspection 080 * <li class='jc'>{@link ParameterInfo} - Parameter introspection 081 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a> 082 * </ul> 083 */ 084@SuppressWarnings({ 085 "java:S1119", // Labeled 'continue outer' is the clearest way to skip to the next parameter after a match 086 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 087 "java:S3011" // Reflection access needed for executable introspection 088}) 089public abstract sealed class ExecutableInfo extends AccessibleInfo permits ConstructorInfo, MethodInfo { 090 091 protected final ClassInfo declaringClass; 092 private final Executable inner; 093 private final boolean isConstructor; 094 095 private final Supplier<List<ParameterInfo>> parameters; // All parameters of this executable. 096 private final Supplier<List<ClassInfo>> parameterTypes; // All parameter types of this executable. 097 private final Supplier<List<ClassInfo>> exceptions; // All exceptions declared by this executable. 098 private final Supplier<List<AnnotationInfo<Annotation>>> declaredAnnotations; // All annotations declared directly on this executable. 099 private final Supplier<String> nameShort; // Short name (method/constructor name with parameters). 100 private final Supplier<String> nameFull; // Fully qualified name (declaring-class.method-name with parameters). 101 private final Supplier<String> toString; // String representation with modifiers, return type, name, and throws declarations. 102 103 /** 104 * Constructor. 105 * 106 * <p> 107 * Creates a new ExecutableInfo wrapper for the specified executable (method or constructor). 108 * This constructor is protected and should not be called directly. Use the constructors 109 * of {@link MethodInfo} or {@link ConstructorInfo} instead. 110 * 111 * @param declaringClass The ClassInfo for the class that declares this method or constructor. Must not be <jk>null</jk>. 112 * @param inner The constructor or method that this info represents. Must not be <jk>null</jk>. 113 */ 114 protected ExecutableInfo(ClassInfo declaringClass, Executable inner) { 115 super(inner, reqnn("inner", inner).getModifiers()); 116 this.declaringClass = declaringClass; 117 this.inner = inner; 118 this.isConstructor = inner instanceof Constructor; 119 this.parameters = memoize(this::findParameters); 120 this.parameterTypes = memoize(() -> getParameters().stream().map(ParameterInfo::getParameterType).toList()); 121 this.exceptions = memoize(() -> stream(inner.getExceptionTypes()).map(ClassInfo::of).map(ClassInfo.class::cast).toList()); 122 this.declaredAnnotations = memoize(() -> stream(inner.getDeclaredAnnotations()).flatMap(AnnotationUtils::streamRepeated).map(a -> ai((Annotatable)this, a)).toList()); 123 this.nameShort = memoize(() -> f("%s(%s)", getNameSimple(), getParameters().stream().map(p -> p.getParameterType().getNameSimple()).collect(joining(",")))); 124 this.nameFull = memoize(this::findNameFull); 125 this.toString = memoize(this::findToString); 126 } 127 128 /** 129 * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions. 130 * 131 * @return This object. 132 */ 133 public ExecutableInfo accessible() { 134 setAccessible(); 135 return this; 136 } 137 138 /** 139 * Returns <jk>true</jk> if this executable can accept the specified arguments in the specified order. 140 * 141 * <p> 142 * This method checks if the provided arguments are compatible with the executable's parameter types 143 * in exact order, using {@link Class#isInstance(Object)} for type checking. 144 * 145 * <p> 146 * <strong>Important:</strong> For non-static inner class constructors, the first parameter is the 147 * implicit outer class instance (e.g., {@code Outer.this}). This method checks against the 148 * <em>actual</em> parameters including this implicit parameter. 149 * 150 * <h5 class='section'>Examples:</h5> 151 * <p class='bjava'> 152 * <jc>// Regular method</jc> 153 * <jk>public void</jk> foo(String <jv>s</jv>, Integer <jv>i</jv>); 154 * <jv>methodInfo</jv>.canAccept(<js>"hello"</js>, 42); <jc>// true</jc> 155 * 156 * <jc>// Non-static inner class constructor</jc> 157 * <jk>class</jk> Outer { 158 * <jk>class</jk> Inner { 159 * Inner(String <jv>s</jv>) {} 160 * } 161 * } 162 * <jc>// Constructor actually has signature: Inner(Outer this$0, String s)</jc> 163 * Outer <jv>outer</jv> = <jk>new</jk> Outer(); 164 * <jv>constructorInfo</jv>.canAccept(<js>"hello"</js>); <jc>// false - missing outer instance</jc> 165 * <jv>constructorInfo</jv>.canAccept(<jv>outer</jv>, <js>"hello"</js>); <jc>// true</jc> 166 * </p> 167 * 168 * @param args The arguments to check. 169 * @return <jk>true</jk> if this executable can accept the specified arguments in the specified order. 170 */ 171 public final boolean canAccept(Object...args) { 172 Class<?>[] pt = inner.getParameterTypes(); 173 if (pt.length != args.length) 174 return false; 175 for (var i = 0; i < pt.length; i++) 176 if (! pt[i].isInstance(args[i])) 177 return false; 178 return true; 179 } 180 181 /** 182 * Returns an array of {@link AnnotatedType} objects that represent the use of types to specify the declared exceptions. 183 * 184 * <p> 185 * The order of the objects corresponds to the order of the exception types in the executable declaration. 186 * 187 * <p> 188 * Same as calling {@link Executable#getAnnotatedExceptionTypes()}. 189 * 190 * <h5 class='section'>Example:</h5> 191 * <p class='bjava'> 192 * <jc>// Get annotated exception types from method: void myMethod() throws @NotNull IOException</jc> 193 * MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"myMethod"</js>); 194 * AnnotatedType[] <jv>exTypes</jv> = <jv>mi</jv>.getAnnotatedExceptionTypes(); 195 * </p> 196 * 197 * @return An array of {@link AnnotatedType} objects, or an empty array if the executable declares no exceptions. 198 * @see Executable#getAnnotatedExceptionTypes() 199 */ 200 public final AnnotatedType[] getAnnotatedExceptionTypes() { return inner.getAnnotatedExceptionTypes(); } 201 202 /** 203 * Returns an array of {@link AnnotatedType} objects that represent the use of types to specify formal parameter types. 204 * 205 * <p> 206 * The order of the objects corresponds to the order of the formal parameter types in the executable declaration. 207 * 208 * <p> 209 * Same as calling {@link Executable#getAnnotatedParameterTypes()}. 210 * 211 * <h5 class='section'>Example:</h5> 212 * <p class='bjava'> 213 * <jc>// Get annotated parameter types from method: void myMethod(@NotNull String s, @Range(min=0) int i)</jc> 214 * MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"myMethod"</js>, String.<jk>class</jk>, <jk>int</jk>.<jk>class</jk>); 215 * AnnotatedType[] <jv>paramTypes</jv> = <jv>mi</jv>.getAnnotatedParameterTypes(); 216 * </p> 217 * 218 * @return An array of {@link AnnotatedType} objects, or an empty array if the executable has no parameters. 219 * @see Executable#getAnnotatedParameterTypes() 220 */ 221 public final AnnotatedType[] getAnnotatedParameterTypes() { return inner.getAnnotatedParameterTypes(); } 222 223 /** 224 * Returns an {@link AnnotatedType} object that represents the use of a type to specify the receiver type of the method/constructor. 225 * 226 * <p> 227 * Returns <jk>null</jk> if this executable object represents a top-level type or static member. 228 * 229 * <p> 230 * Same as calling {@link Executable#getAnnotatedReceiverType()}. 231 * 232 * <h5 class='section'>Example:</h5> 233 * <p class='bjava'> 234 * <jc>// Get annotated receiver type from method: void myMethod(@MyAnnotation MyClass this)</jc> 235 * MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"myMethod"</js>); 236 * AnnotatedType <jv>receiverType</jv> = <jv>mi</jv>.getAnnotatedReceiverType(); 237 * </p> 238 * 239 * @return An {@link AnnotatedType} object representing the receiver type, or <jk>null</jk> if not applicable. 240 * @see Executable#getAnnotatedReceiverType() 241 */ 242 public final AnnotatedType getAnnotatedReceiverType() { return inner.getAnnotatedReceiverType(); } 243 244 /** 245 * Returns the declared annotations on this executable. 246 * 247 * <p> 248 * <b>Note on Repeatable Annotations:</b> 249 * Repeatable annotations (those marked with {@link Repeatable @Repeatable}) are automatically 250 * expanded into their individual annotation instances. For example, if a method has multiple {@code @Marshalled} annotations, 251 * this method returns each {@code @Marshalled} annotation separately, rather than the container annotation. 252 * 253 * @return 254 * The declared annotations on this executable as {@link AnnotationInfo} objects. 255 * <br>Repeatable annotations are expanded into individual instances. 256 */ 257 public final List<AnnotationInfo<Annotation>> getDeclaredAnnotations() { return declaredAnnotations.get(); } 258 259 /** 260 * Returns the declared annotations of the specified type on this executable. 261 * 262 * @param <A> The annotation type. 263 * @param type The annotation type. Must not be <jk>null</jk>. 264 * @return A stream of matching annotations. 265 */ 266 @SuppressWarnings({ 267 "unchecked" // Type erasure requires cast for annotation stream 268 }) 269 public final <A extends Annotation> Stream<AnnotationInfo<A>> getDeclaredAnnotations(Class<A> type) { 270 reqnn("type", type); 271 // @formatter:off 272 return declaredAnnotations.get().stream() 273 .filter(x -> type.isInstance(x.inner())) 274 .map(x -> (AnnotationInfo<A>)x); 275 // @formatter:on 276 } 277 278 /** 279 * Returns metadata about the class that declared this method or constructor. 280 * 281 * @return Metadata about the class that declared this method or constructor. 282 */ 283 public final ClassInfo getDeclaringClass() { return declaringClass; } 284 285 /** 286 * Returns <jk>true</jk> if the declaring class of this executable matches the specified class. 287 * 288 * @param value The class to compare against. 289 * @return <jk>true</jk> if the declaring class matches the specified class. 290 */ 291 public final boolean isDeclaringClass(ClassInfo value) { return eq(declaringClass, value); } 292 293 /** 294 * Returns the exception types on this executable. 295 * 296 * @return The exception types on this executable. 297 */ 298 public final List<ClassInfo> getExceptionTypes() { return exceptions.get(); } 299 300 /** 301 * Returns the full name of this executable. 302 * 303 * <h5 class='section'>Examples:</h5> 304 * <ul> 305 * <li><js>"com.foo.MyClass.get(java.util.String)"</js> - Method. 306 * <li><js>"com.foo.MyClass(java.util.String)"</js> - Constructor. 307 * </ul> 308 * 309 * @return The underlying executable name. 310 */ 311 public final String getNameFull() { return nameFull.get(); } 312 313 /** 314 * Returns parameter information at the specified index. 315 * 316 * @param index The parameter index. 317 * @return The parameter information, never <jk>null</jk>. 318 */ 319 public final ParameterInfo getParameter(int index) { 320 checkIndex(index); 321 return getParameters().get(index); 322 } 323 324 /** 325 * Returns the number of parameters in this executable. 326 * 327 * <p> 328 * Same as calling {@link Executable#getParameterCount()}. 329 * 330 * @return The number of parameters in this executable. 331 */ 332 public final int getParameterCount() { return inner.getParameterCount(); } 333 334 /** 335 * Returns the parameters defined on this executable. 336 * 337 * <p> 338 * Same as calling {@link Executable#getParameters()} but wraps the results 339 * 340 * @return An array of parameter information, never <jk>null</jk>. 341 */ 342 public final List<ParameterInfo> getParameters() { return parameters.get(); } 343 344 /** 345 * Returns the parameter types for this executable. 346 * 347 * <p> 348 * This is a convenience method that extracts the parameter types from {@link #getParameters()}. 349 * 350 * <h5 class='section'>Example:</h5> 351 * <p class='bjava'> 352 * <jc>// Get parameter types: void myMethod(String s, int i)</jc> 353 * MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"myMethod"</js>, String.<jk>class</jk>, <jk>int</jk>.<jk>class</jk>); 354 * List<ClassInfo> <jv>paramTypes</jv> = <jv>mi</jv>.getParameterTypes(); 355 * <jc>// paramTypes contains ClassInfo for String and int</jc> 356 * </p> 357 * 358 * @return A list of parameter types, never <jk>null</jk>. 359 */ 360 public final List<ClassInfo> getParameterTypes() { 361 return parameterTypes.get(); 362 } 363 364 /** 365 * Returns the short name of this executable. 366 * 367 * <h5 class='section'>Examples:</h5> 368 * <ul> 369 * <li><js>"MyClass.get(String)"</js> - Method. 370 * <li><js>"MyClass(String)"</js> - Constructor. 371 * </ul> 372 * 373 * @return The underlying executable name. 374 */ 375 public final String getNameShort() { return nameShort.get(); } 376 377 /** 378 * Returns the simple name of the underlying method. 379 * 380 * @return The simple name of the underlying method; 381 */ 382 public final String getNameSimple() { return isConstructor ? cns(inner.getDeclaringClass()) : inner.getName(); } 383 384 /** 385 * Returns an array of {@link TypeVariable} objects that represent the type variables declared by the generic declaration. 386 * 387 * <p> 388 * Returns an empty array if the generic declaration declares no type variables. 389 * 390 * <p> 391 * Same as calling {@link Executable#getTypeParameters()}. 392 * 393 * <h5 class='section'>Example:</h5> 394 * <p class='bjava'> 395 * <jc>// Get type parameters from method: <T extends Number> void myMethod(T value)</jc> 396 * MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"myMethod"</js>, Number.<jk>class</jk>); 397 * TypeVariable<?>[] <jv>typeParams</jv> = <jv>mi</jv>.getTypeParameters(); 398 * <jc>// typeParams[0].getName() returns "T"</jc> 399 * </p> 400 * 401 * @return An array of {@link TypeVariable} objects, or an empty array if none. 402 * @see Executable#getTypeParameters() 403 */ 404 @SuppressWarnings({ 405 "java:S1452" // Wildcard required - TypeVariable<?> from Executable.getTypeParameters() 406 }) 407 public final TypeVariable<?>[] getTypeParameters() { return inner.getTypeParameters(); } 408 409 /** 410 * Returns <jk>true</jk> if this executable has the specified annotation. 411 * 412 * @param <A> The annotation type. 413 * @param type The annotation type. 414 * @return <jk>true</jk> if this executable has the specified annotation. 415 */ 416 public <A extends Annotation> boolean hasAnnotation(Class<A> type) { 417 return getDeclaredAnnotations(type).findFirst().isPresent(); 418 } 419 420 /** 421 * Returns <jk>true</jk> if this method has a name in the specified set. 422 * 423 * @param names The names to test for. 424 * @return <jk>true</jk> if this method has one of the names. 425 */ 426 public final boolean hasAnyName(Collection<String> names) { 427 return names.contains(getNameSimple()); 428 } 429 430 /** 431 * Returns <jk>true</jk> if this method has a name in the specified list. 432 * 433 * @param names The names to test for. 434 * @return <jk>true</jk> if this method has one of the names. 435 */ 436 public final boolean hasAnyName(String...names) { 437 return stream(names).anyMatch(n -> eq(n, getNameSimple())); 438 } 439 440 /** 441 * Returns <jk>true</jk> if this executable has matching parameter types with the provided parameter list. 442 * 443 * @param params The parameters to match against. 444 * @return <jk>true</jk> if this executable has matching parameter types. 445 */ 446 public final boolean hasMatchingParameters(List<ParameterInfo> params) { 447 var myParams = getParameters(); 448 return myParams.size() == params.size() && IntStream.range(0, params.size()).allMatch(i -> myParams.get(i).getParameterType().is(params.get(i).getParameterType())); 449 } 450 451 /** 452 * Returns <jk>true</jk> if this method has this name. 453 * 454 * @param name The name to test for. 455 * @return <jk>true</jk> if this method has this name. 456 */ 457 @SuppressWarnings({ 458 "javabugs:S2259" // getNameSimple() returns the declared name or the declaring class simple name and is never null. 459 }) 460 public final boolean hasName(String name) { 461 return getNameSimple().equals(name); 462 } 463 464 /** 465 * Returns <jk>true</jk> if this executable has this number of arguments. 466 * 467 * <p> 468 * Same as calling {@link Executable#getParameterCount()} and comparing the count. 469 * 470 * @param number The number of expected arguments. 471 * @return <jk>true</jk> if this executable has this number of arguments. 472 */ 473 public final boolean hasNumParameters(int number) { 474 return getParameterCount() == number; 475 } 476 477 /** 478 * Returns <jk>true</jk> if this executable has at least one parameter. 479 * 480 * <p> 481 * Same as calling {@link Executable#getParameterCount()} and comparing with zero. 482 * 483 * @return <jk>true</jk> if this executable has at least one parameter. 484 */ 485 public final boolean hasParameters() { 486 return getParameterCount() != 0; 487 } 488 489 /** 490 * Returns <jk>true</jk> if this method has the specified argument parent classes. 491 * 492 * @param args The arguments to test for. 493 * @return <jk>true</jk> if this method has this arguments in the exact order. 494 */ 495 public final boolean hasParameterTypeParents(Class<?>...args) { 496 var params = getParameters(); 497 return params.size() == args.length && params.stream().allMatch(p -> stream(args).anyMatch(a -> p.getParameterType().isParentOfLenient(a))); 498 } 499 500 /** 501 * Returns <jk>true</jk> if this method has the specified argument parent classes. 502 * 503 * @param args The arguments to test for. 504 * @return <jk>true</jk> if this method has this arguments in the exact order. 505 */ 506 public final boolean hasParameterTypeParents(ClassInfo...args) { 507 var params = getParameters(); 508 return params.size() == args.length && params.stream().allMatch(p -> stream(args).anyMatch(a -> p.getParameterType().isParentOfLenient(a))); 509 } 510 511 /** 512 * Returns <jk>true</jk> if this method has the specified arguments. 513 * 514 * @param args The arguments to test for. 515 * @return <jk>true</jk> if this method has this arguments in the exact order. 516 */ 517 public final boolean hasParameterTypes(Class<?>...args) { 518 var params = getParameters(); 519 return params.size() == args.length && IntStream.range(0, args.length).allMatch(i -> params.get(i).getParameterType().is(args[i])); 520 } 521 522 /** 523 * Returns <jk>true</jk> if this method has the specified arguments. 524 * 525 * @param args The arguments to test for. 526 * @return <jk>true</jk> if this method has this arguments in the exact order. 527 */ 528 public final boolean hasParameterTypes(ClassInfo...args) { 529 var params = getParameters(); 530 return params.size() == args.length && IntStream.range(0, args.length).allMatch(i -> params.get(i).getParameterType().is(args[i])); 531 } 532 533 /** 534 * Returns <jk>true</jk> if this executable has a parameter of the specified type in any position. 535 * 536 * @param type The type to test for. 537 * @return <jk>true</jk> if this executable has a parameter of the specified type. 538 */ 539 public final boolean hasParameterType(ClassInfo type) { 540 return getParameters().stream().anyMatch(p -> p.getParameterType().is(type)); 541 } 542 543 /** 544 * Returns <jk>true</jk> if this executable has a parameter that is a parent of the specified type in any position. 545 * 546 * <p> 547 * This means the parameter type can accept an instance of the specified type. 548 * 549 * @param type The type to test for. 550 * @return <jk>true</jk> if this executable has a parameter that can accept the specified type. 551 */ 552 public final boolean hasParameterTypeParent(ClassInfo type) { 553 return getParameters().stream().anyMatch(p -> p.getParameterType().isParentOfLenient(type)); 554 } 555 556 /** 557 * Returns <jk>true</jk> if this method has at most only these arguments using lenient matching. 558 * 559 * <p> 560 * Lenient matching allows arguments to be matched to parameters based on type compatibility, 561 * where arguments can be in any order. 562 * 563 * @param args The arguments to test for. 564 * @return <jk>true</jk> if this method has at most only these arguments in any order. 565 */ 566 public final boolean hasParameterTypesLenient(Class<?>...args) { 567 return parameterMatchesLenientCount(args) != -1; 568 } 569 570 /** 571 * Returns <jk>true</jk> if this method has at most only these arguments using lenient matching. 572 * 573 * <p> 574 * Lenient matching allows arguments to be matched to parameters based on type compatibility, 575 * where arguments can be in any order. 576 * 577 * @param args The arguments to test for. 578 * @return <jk>true</jk> if this method has at most only these arguments in any order. 579 */ 580 public final boolean hasParameterTypesLenient(ClassInfo...args) { 581 return parameterMatchesLenientCount(args) != -1; 582 } 583 584 /** 585 * Returns <jk>true</jk> if all specified flags are applicable to this method. 586 * 587 * @param flag The flag to test for. 588 * @return <jk>true</jk> if all specified flags are applicable to this method. 589 */ 590 @Override 591 public boolean is(ElementFlag flag) { 592 return switch (flag) { 593 case CONSTRUCTOR -> isConstructor(); 594 case NOT_CONSTRUCTOR -> ! isConstructor(); 595 case DEPRECATED -> isDeprecated(); 596 case NOT_DEPRECATED -> isNotDeprecated(); 597 case HAS_PARAMS -> hasParameters(); 598 case HAS_NO_PARAMS -> getParameterCount() == 0; 599 case SYNTHETIC -> isSynthetic(); 600 case NOT_SYNTHETIC -> ! isSynthetic(); // HTT 601 case VARARGS -> isVarArgs(); 602 case NOT_VARARGS -> ! isVarArgs(); 603 default -> super.is(flag); 604 }; 605 } 606 607 /** 608 * Returns <jk>true</jk> if this executable represents a {@link Constructor}. 609 * 610 * @return 611 * <jk>true</jk> if this executable represents a {@link Constructor} and can be cast to {@link ConstructorInfo}. 612 * <jk>false</jk> if this executable represents a {@link Method} and can be cast to {@link MethodInfo}. 613 */ 614 public final boolean isConstructor() { return isConstructor; } 615 616 /** 617 * Returns <jk>true</jk> if this method has the {@link Deprecated @Deprecated} annotation on it. 618 * 619 * @return <jk>true</jk> if this method has the {@link Deprecated @Deprecated} annotation on it. 620 */ 621 public final boolean isDeprecated() { 622 return inner.isAnnotationPresent(Deprecated.class); 623 624 } 625 626 /** 627 * Returns <jk>true</jk> if this method doesn't have the {@link Deprecated @Deprecated} annotation on it. 628 * 629 * @return <jk>true</jk> if this method doesn't have the {@link Deprecated @Deprecated} annotation on it. 630 */ 631 public final boolean isNotDeprecated() { 632 return ! inner.isAnnotationPresent(Deprecated.class); 633 634 } 635 636 /** 637 * Returns <jk>true</jk> if this executable is a synthetic construct as defined by the Java Language Specification. 638 * 639 * <p> 640 * Same as calling {@link Executable#isSynthetic()}. 641 * 642 * <h5 class='section'>Example:</h5> 643 * <p class='bjava'> 644 * <jc>// Check if method is compiler-generated</jc> 645 * MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"access$000"</js>); 646 * <jk>boolean</jk> <jv>isSynthetic</jv> = <jv>mi</jv>.isSynthetic(); 647 * </p> 648 * 649 * @return <jk>true</jk> if this executable is a synthetic construct. 650 * @see Executable#isSynthetic() 651 */ 652 public final boolean isSynthetic() { return inner.isSynthetic(); } 653 654 /** 655 * Returns <jk>true</jk> if this executable was declared to take a variable number of arguments. 656 * 657 * <p> 658 * Same as calling {@link Executable#isVarArgs()}. 659 * 660 * <h5 class='section'>Example:</h5> 661 * <p class='bjava'> 662 * <jc>// Check if method accepts varargs</jc> 663 * MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"myMethod"</js>, String[].<jk>class</jk>); 664 * <jk>boolean</jk> <jv>isVarArgs</jv> = <jv>mi</jv>.isVarArgs(); 665 * </p> 666 * 667 * @return <jk>true</jk> if this executable was declared to take a variable number of arguments. 668 * @see Executable#isVarArgs() 669 */ 670 public final boolean isVarArgs() { return inner.isVarArgs(); } 671 672 /** 673 * Identifies if the specified visibility matches this method. 674 * 675 * @param v The visibility to validate against. 676 * @return <jk>true</jk> if this visibility matches the modifier attribute of this method. 677 */ 678 public final boolean isVisible(Visibility v) { 679 return v.isVisible(inner); 680 } 681 682 /** 683 * Returns how well this method matches the specified arg types using lenient matching. 684 * 685 * <p> 686 * Lenient matching allows arguments to be matched to parameters based on type compatibility, 687 * where arguments can be in any order. 688 * 689 * <p> 690 * The number returned is the number of method arguments that match the passed in arg types. 691 * <br>Returns <c>-1</c> if the method cannot take in one or more of the specified arguments. 692 * 693 * @param argTypes The arg types to check against. 694 * @return How many parameters match or <c>-1</c> if method cannot handle one or more of the arguments. 695 */ 696 public final int parameterMatchesLenientCount(Class<?>...argTypes) { 697 int matches = 0; 698 outer: for (var param : getParameters()) { 699 for (var a : argTypes) { 700 if (param.getParameterType().isParentOfLenient(a)) { 701 matches++; 702 continue outer; 703 } 704 } 705 return -1; 706 } 707 return matches; 708 } 709 710 /** 711 * Returns how well this method matches the specified arg types using lenient matching. 712 * 713 * <p> 714 * Lenient matching allows arguments to be matched to parameters based on type compatibility, 715 * where arguments can be in any order. 716 * 717 * <p> 718 * The number returned is the number of method arguments that match the passed in arg types. 719 * <br>Returns <c>-1</c> if the method cannot take in one or more of the specified arguments. 720 * 721 * @param argTypes The arg types to check against. 722 * @return How many parameters match or <c>-1</c> if method cannot handle one or more of the arguments. 723 */ 724 public final int parameterMatchesLenientCount(ClassInfo...argTypes) { 725 int matches = 0; 726 outer: for (var param : getParameters()) { 727 for (var a : argTypes) { 728 if (param.getParameterType().isParentOfLenient(a)) { 729 matches++; 730 continue outer; 731 } 732 } 733 return -1; 734 } 735 return matches; 736 } 737 738 /** 739 * Returns how well this method matches the specified arg types using lenient matching. 740 * 741 * <p> 742 * Lenient matching allows arguments to be matched to parameters based on type compatibility, 743 * where arguments can be in any order. 744 * 745 * <p> 746 * The number returned is the number of method arguments that match the passed in arg types. 747 * <br>Returns <c>-1</c> if the method cannot take in one or more of the specified arguments. 748 * 749 * @param argTypes The arg types to check against. 750 * @return How many parameters match or <c>-1</c> if method cannot handle one or more of the arguments. 751 */ 752 public final int parameterMatchesLenientCount(Object...argTypes) { 753 int matches = 0; 754 outer: for (var param : getParameters()) { 755 for (var a : argTypes) { 756 if (param.getParameterType().canAcceptArg(a)) { 757 matches++; 758 continue outer; 759 } 760 } 761 return -1; 762 } 763 return matches; 764 } 765 766 /** 767 * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions. 768 * 769 * @return <jk>true</jk> if call was successful. 770 */ 771 @Override 772 public final boolean setAccessible() { 773 // inner can never be null - constructor asserts non-null via reqnn 774 return safeOpt(() -> { 775 inner.setAccessible(true); 776 return true; 777 }).orElse(false); 778 } 779 780 /** 781 * Returns a string describing this executable, including type parameters. 782 * 783 * <p> 784 * The string includes the method/constructor name, parameter types (with generic information), and return type (for methods). 785 * 786 * <p> 787 * Same as calling {@link Executable#toGenericString()}. 788 * 789 * <h5 class='section'>Example:</h5> 790 * <p class='bjava'> 791 * <jc>// Get generic string for: public <T> List<T> myMethod(T value) throws IOException</jc> 792 * MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"myMethod"</js>, Object.<jk>class</jk>); 793 * String <jv>str</jv> = <jv>mi</jv>.toGenericString(); 794 * <jc>// Returns: "public <T> java.util.List<T> com.example.MyClass.myMethod(T) throws java.io.IOException"</jc> 795 * </p> 796 * 797 * @return A string describing this executable. 798 * @see Executable#toGenericString() 799 */ 800 public final String toGenericString() { 801 return inner.toGenericString(); 802 } 803 804 /** 805 * Returns a detailed string representation of this executable (method or constructor). 806 * 807 * <p> 808 * The returned string includes: 809 * <ul> 810 * <li>Modifiers (public, private, protected, static, final, abstract, synchronized, etc.) 811 * <li>Synthetic and bridge flags (if applicable) 812 * <li>Generic type parameters with bounds (if any, e.g., "<T extends Comparable<T>>") 813 * <li>Return type (for methods only, not constructors) 814 * <li>Fully qualified name with parameters 815 * <li>Throws declarations (if any) 816 * </ul> 817 * 818 * <h5 class='section'>Examples:</h5> 819 * <p class='bjava'> 820 * <jc>// Constructor</jc> 821 * ConstructorInfo <jv>ci</jv> = ConstructorInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>.getConstructor(String.<jk>class</jk>)); 822 * <jv>ci</jv>.toString(); 823 * <jc>// Returns: "public org.example.MyClass(java.lang.String)"</jc> 824 * 825 * <jc>// Method</jc> 826 * MethodInfo <jv>mi</jv> = MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <js>"myMethod"</js>, String.<jk>class</jk>); 827 * <jv>mi</jv>.toString(); 828 * <jc>// Returns: "public java.lang.String org.example.MyClass.myMethod(java.lang.String)"</jc> 829 * 830 * <jc>// Method with throws</jc> 831 * MethodInfo <jv>mi2</jv> = MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <js>"riskyMethod"</js>); 832 * <jv>mi2</jv>.toString(); 833 * <jc>// Returns: "public void org.example.MyClass.riskyMethod() throws java.io.IOException, java.lang.Exception"</jc> 834 * 835 * <jc>// Generic method</jc> 836 * MethodInfo <jv>mi3</jv> = MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <js>"genericMethod"</js>); 837 * <jv>mi3</jv>.toString(); 838 * <jc>// Returns: "public <T> void org.example.MyClass.genericMethod(T)"</jc> 839 * 840 * <jc>// Generic method with bounds</jc> 841 * MethodInfo <jv>mi4</jv> = MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <js>"boundedMethod"</js>); 842 * <jv>mi4</jv>.toString(); 843 * <jc>// Returns: "public <T extends java.lang.Comparable<T>> void org.example.MyClass.boundedMethod(T)"</jc> 844 * </p> 845 * 846 * <h5 class='section'>Comparison with Other Methods:</h5> 847 * <ul class='spaced-list'> 848 * <li>{@link #getNameShort()} - Returns short name with parameters (e.g., "myMethod(int, String)") 849 * <li>{@link #getNameFull()} - Returns fully qualified name with parameters (e.g., "org.example.MyClass.myMethod(int, java.lang.String)") 850 * <li>{@link #toGenericString()} - Returns generic string from underlying Executable 851 * </ul> 852 * 853 * @return A detailed string representation including modifiers, return type, name, and throws declarations. 854 */ 855 @Override 856 public String toString() { 857 return toString.get(); 858 } 859 860 @SuppressWarnings({ 861 "java:S3776", // Cognitive complexity acceptable for toString formatting logic 862 "java:S6541" // Synchronization not needed for local string building 863 }) 864 private String findToString() { 865 var sb = new StringBuilder(256); 866 867 // Modifiers 868 var mods = Modifier.toString(getModifiers()); 869 if (nn(mods) && ! mods.isEmpty()) { 870 sb.append(mods).append(" "); 871 } 872 873 // Add synthetic, bridge, varargs, and default flags (not actual modifiers but useful to show) 874 if (isSynthetic()) 875 sb.append("synthetic "); 876 if (this instanceof MethodInfo this2) { 877 if (this2.isBridge()) 878 sb.append("bridge "); 879 if (this2.isDefault()) 880 sb.append("default "); 881 } 882 if (isVarArgs()) 883 sb.append("varargs "); 884 885 // Type parameters (if any) 886 var typeParams = getTypeParameters(); 887 var hasTypeParams = typeParams.length > 0; 888 if (hasTypeParams) { 889 sb.append('<'); 890 var first = true; 891 for (var tv : typeParams) { 892 if (! first) 893 sb.append(", "); 894 first = false; 895 sb.append(tv.getName()); 896 var bounds = tv.getBounds(); 897 if (bounds.length > 0 && (bounds.length > 1 || ! bounds[0].equals(Object.class))) { 898 sb.append(" extends "); 899 var firstBound = true; 900 for (var bound : bounds) { 901 if (! firstBound) 902 sb.append(" & "); 903 firstBound = false; 904 if (bound instanceof Class<?> bound2) { 905 ClassInfo.of(bound2).appendNameFormatted(sb, FULL, true, '$', BRACKETS); 906 } else { 907 sb.append(bound.getTypeName()); 908 } 909 } 910 } 911 } 912 sb.append("> "); 913 } 914 915 // Return type (skip for constructors) 916 if (this instanceof MethodInfo this2) 917 this2.getReturnType().appendNameFormatted(sb, FULL, true, '$', BRACKETS).append(" "); 918 919 // Full name - use generic parameter types if we have type parameters 920 if (hasTypeParams) { 921 // Build signature with generic parameter types to show type variables instead of erased types 922 var dc = getDeclaringClass(); 923 var pi = dc.getPackage(); 924 if (nn(pi)) 925 sb.append(pi.getName()).append('.'); 926 dc.appendNameFormatted(sb, SHORT, true, '$', BRACKETS); 927 if (! isConstructor) 928 sb.append('.').append(getNameSimple()); 929 sb.append('('); 930 var genericParamTypes = inner.getGenericParameterTypes(); 931 var first = true; 932 for (var gpt : genericParamTypes) { 933 if (! first) 934 sb.append(','); 935 first = false; 936 ClassInfo.of(gpt).appendNameFormatted(sb, FULL, true, '$', BRACKETS); 937 } 938 sb.append(')'); 939 } else { 940 // Use regular getNameFull() for non-generic methods 941 sb.append(getNameFull()); 942 } 943 944 // Throws declarations 945 var exTypes = getExceptionTypes(); 946 if (! exTypes.isEmpty()) { 947 sb.append(" throws "); 948 sb.append(exTypes.stream() 949 .map(e -> e.getNameFormatted(FULL, true, '$', BRACKETS)) 950 .collect(joining(", "))); 951 } 952 953 return sb.toString(); 954 } 955 956 957 958 private void checkIndex(int index) { 959 int pc = getParameterCount(); 960 if (pc == 0) 961 throw new IndexOutOfBoundsException(f("Invalid index '%d'. No parameters.", index)); 962 if (index < 0 || index >= pc) 963 throw new IndexOutOfBoundsException(f("Invalid index '%d'. Parameter count: %d", index, pc)); 964 } 965 966 private String findNameFull() { 967 var sb = new StringBuilder(128); 968 var dc = declaringClass; 969 var pi = dc.getPackage(); 970 if (nn(pi)) 971 sb.append(pi.getName()).append('.'); 972 dc.appendNameFormatted(sb, SHORT, true, '$', BRACKETS); 973 if (! isConstructor) 974 sb.append('.').append(getNameSimple()); 975 sb.append('('); 976 sb.append(getParameters().stream().map(p -> p.getParameterType().getNameFormatted(FULL, true, '$', BRACKETS)).collect(joining(","))); 977 sb.append(')'); 978 return sb.toString(); 979 } 980 981 private List<ParameterInfo> findParameters() { 982 var rp = inner.getParameters(); 983 var ptc = inner.getParameterTypes(); // Class<?>[] - includes all parameters (including synthetic enclosing instance) 984 var ptt = inner.getGenericParameterTypes(); // Type[] - generic type information 985 Type[] genericTypes; 986 987 if (ptt.length == ptc.length) { 988 genericTypes = ptt; 989 } else { 990 var ptt2 = new Type[ptc.length]; 991 ptt2[0] = ptc[0]; // Enclosing instance type (Class<?>, not a generic Type) 992 for (var i = 0; i < ptt.length; i++) 993 ptt2[i + 1] = ptt[i]; // Copy remaining generic types 994 genericTypes = ptt2; 995 } 996 return IntStream.range(0, rp.length).mapToObj(i -> new ParameterInfo(this, rp[i], i, ClassInfo.of(ptc[i], genericTypes[i]))).toList(); 997 } 998}