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.function.Suppliers.*; 020import static org.apache.juneau.commons.reflect.ClassArrayFormat.*; 021import static org.apache.juneau.commons.reflect.ClassNameFormat.*; 022import static org.apache.juneau.commons.utils.CollectionUtils.*; 023import static org.apache.juneau.commons.utils.Shorts.*; 024 025import java.lang.annotation.*; 026import java.lang.reflect.*; 027import java.util.*; 028import java.util.function.*; 029import java.util.stream.*; 030 031import org.apache.juneau.commons.function.*; 032import org.apache.juneau.commons.inject.*; 033import org.apache.juneau.commons.settings.*; 034import org.apache.juneau.commons.utils.*; 035 036/** 037 * Lightweight utility class for introspecting information about a method or constructor parameter. 038 * 039 * <p> 040 * This class provides a convenient wrapper around {@link Parameter} that extends the standard Java reflection 041 * API with additional functionality for parameter introspection, annotation handling, and name resolution. 042 * It supports resolving parameter names from bytecode (when compiled with <c>-parameters</c>) or from 043 * <ja>@Name</ja> annotations. 044 * 045 * <h5 class='section'>Features:</h5> 046 * <ul class='spaced-list'> 047 * <li>Parameter introspection - access parameter metadata, type, annotations 048 * <li>Name resolution - resolve parameter names from bytecode or annotations 049 * <li>Qualifier support - extract qualifier names from annotations 050 * <li>Hierarchy traversal - find matching parameters in parent methods/constructors 051 * <li>Annotation support - get annotations declared on the parameter 052 * </ul> 053 * 054 * <h5 class='section'>Use Cases:</h5> 055 * <ul class='spaced-list'> 056 * <li>Introspecting parameter metadata for code generation or analysis 057 * <li>Resolving parameter names for dependency injection frameworks 058 * <li>Finding annotations on parameters 059 * <li>Working with parameter types and qualifiers 060 * <li>Building frameworks that need to analyze method/constructor signatures 061 * </ul> 062 * 063 * <h5 class='section'>Usage:</h5> 064 * <p class='bjava'> 065 * <jc>// Get ParameterInfo from a method</jc> 066 * MethodInfo <jv>mi</jv> = ...; 067 * ParameterInfo <jv>param</jv> = <jv>mi</jv>.getParameters().get(0); 068 * 069 * <jc>// Get parameter type</jc> 070 * ClassInfo <jv>type</jv> = <jv>param</jv>.getParameterType(); 071 * 072 * <jc>// Get resolved name (from bytecode or @Name annotation)</jc> 073 * String <jv>name</jv> = <jv>param</jv>.getResolvedName(); 074 * 075 * <jc>// Get annotations</jc> 076 * List<AnnotationInfo<MyAnnotation>> <jv>annotations</jv> = 077 * <jv>param</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList(); 078 * </p> 079 * 080 * <h5 class='section'>Parameter Name Resolution:</h5> 081 * <p> 082 * Parameter names are resolved in the following order: 083 * <ol class='spaced-list'> 084 * <li><ja>@Name</ja> annotation value (if present) 085 * <li>Bytecode parameter names (if compiled with <c>-parameters</c> flag) 086 * <li><c>arg0</c>, <c>arg1</c>, etc. (fallback if names unavailable) 087 * </ol> 088 * 089 * <h5 class='section'>See Also:</h5><ul> 090 * <li class='jc'>{@link MethodInfo} - Method introspection 091 * <li class='jc'>{@link ConstructorInfo} - Constructor introspection 092 * <li class='jc'>{@link ExecutableInfo} - Common executable functionality 093 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a> 094 * </ul> 095 */ 096@SuppressWarnings({ 097 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 098 "unchecked" // Casts to AnnotationInfo<A> in the annotation stream and to Supplier<Object>/Class<Object>/Provider<Object> in the JSR-330 Provider bean resolution are guarded by the requested type 099}) 100public final class ParameterInfo extends ElementInfo implements Annotatable { 101 102 /** 103 * Resettable supplier for the system property to disable bytecode parameter name detection. 104 * 105 * <p> 106 * When the value is <jk>true</jk>, parameter names will only come from <ja>@Name</ja> 107 * annotations and not from bytecode parameter names (even if compiled with <c>-parameters</c> flag). 108 * 109 * <p> 110 * This can be set via system property: <c>juneau.disableParamNameDetection=true</c> 111 * 112 * <p> 113 * The supplier can be reset for testing purposes using {@link #resetDisableParamNameDetection()}. 114 */ 115 static final Memoizer<Boolean> DISABLE_PARAM_NAME_DETECTION = memoizer(() -> Settings.get().get("juneau.disableParamNameDetection").asBoolean().orElse(false)); 116 117 /** 118 * Creates a ParameterInfo wrapper for the specified parameter. 119 * 120 * <p> 121 * This convenience method automatically determines the declaring executable from the parameter 122 * and finds the matching ParameterInfo. 123 * 124 * <h5 class='section'>Example:</h5> 125 * <p class='bjava'> 126 * Parameter <jv>p</jv> = ...; 127 * ParameterInfo <jv>pi</jv> = ParameterInfo.<jsm>of</jsm>(<jv>p</jv>); 128 * </p> 129 * 130 * @param inner The parameter being wrapped. Must not be <jk>null</jk>. 131 * @return A ParameterInfo object wrapping the parameter. 132 * @throws IllegalArgumentException If the parameter is <jk>null</jk> or cannot be found in its declaring executable. 133 */ 134 public static ParameterInfo of(Parameter inner) { 135 reqnn("inner", inner); 136 var exec = inner.getDeclaringExecutable(); 137 ExecutableInfo execInfo; 138 if (exec instanceof Constructor<?> exec2) 139 execInfo = ConstructorInfo.of(exec2); 140 else 141 execInfo = MethodInfo.of((Method)exec); 142 return execInfo.getParameters().stream() 143 .filter(x -> eq(x.inner(), inner)) 144 .findFirst() 145 .orElse(null); 146 } 147 148 static void reset() { 149 DISABLE_PARAM_NAME_DETECTION.reset(); 150 } 151 152 private final ExecutableInfo executable; 153 private final Parameter inner; 154 155 private final int index; 156 private final ClassInfo type; 157 private final Supplier<List<AnnotationInfo<Annotation>>> annotations; // All annotations declared directly on this parameter. 158 private final Supplier<List<ParameterInfo>> matchingParameters; // Matching parameters in parent methods. 159 160 private final Memoizer<String> resolvedName = memoizer(this::findNameInternal); // Resolved name from @Name annotation or bytecode. 161 162 private final Memoizer<String> resolvedQualifier = memoizer(this::findQualifierInternal); // Resolved qualifier from @Named or @Qualifier annotation. 163 164 private final Supplier<String> toString; // String representation with modifiers, type, name, and varargs flag. 165 166 /** 167 * Constructor. 168 * 169 * <p> 170 * Creates a new ParameterInfo wrapper for the specified parameter. This constructor is protected 171 * and should not be called directly. ParameterInfo instances are typically obtained from 172 * {@link ExecutableInfo#getParameters()} or {@link #of(Parameter)}. 173 * 174 * @param executable The ExecutableInfo (MethodInfo or ConstructorInfo) that contains this parameter. Must not be <jk>null</jk>. 175 * @param inner The parameter being wrapped. Must not be <jk>null</jk>. 176 * @param index The zero-based index of this parameter in the method/constructor signature. 177 * @param type The ClassInfo representing the parameter type. 178 */ 179 protected ParameterInfo(ExecutableInfo executable, Parameter inner, int index, ClassInfo type) { 180 super(inner.getModifiers()); 181 this.executable = executable; 182 this.inner = inner; 183 this.index = index; 184 this.type = type; 185 this.annotations = memoize(() -> stream(inner.getAnnotations()).flatMap(AnnotationUtils::streamRepeated).map(a -> ai(this, a)).toList()); 186 this.matchingParameters = memoize(this::findMatchingParameters); 187 this.toString = memoize(this::findToString); 188 } 189 190 /** 191 * Returns <jk>true</jk> if this parameter can accept the specified value. 192 * 193 * @param value The value to check. 194 * @return <jk>true</jk> if this parameter can accept the specified value. 195 */ 196 public boolean canAccept(Object value) { 197 return getParameterType().canAcceptArg(value); 198 } 199 200 @Override /* Annotatable */ 201 public AnnotatableType getAnnotatableType() { return AnnotatableType.PARAMETER_TYPE; } 202 203 /** 204 * Returns an {@link AnnotatedType} object that represents the use of a type to specify the type of this parameter. 205 * 206 * <p> 207 * Same as calling {@link Parameter#getAnnotatedType()}. 208 * 209 * <h5 class='section'>Example:</h5> 210 * <p class='bjava'> 211 * <jc>// Get annotated type: void method(@NotNull String value)</jc> 212 * ParameterInfo <jv>pi</jv> = ...; 213 * AnnotatedType <jv>aType</jv> = <jv>pi</jv>.getAnnotatedType(); 214 * <jc>// Check for @NotNull on the type</jc> 215 * </p> 216 * 217 * @return An {@link AnnotatedType} object representing the type of this parameter. 218 * @see Parameter#getAnnotatedType() 219 */ 220 public AnnotatedType getAnnotatedType() { return inner.getAnnotatedType(); } 221 222 /** 223 * Returns all annotations declared on this parameter. 224 * 225 * <p> 226 * Returns annotations directly declared on this parameter, wrapped as {@link AnnotationInfo} objects. 227 * 228 * <p> 229 * <b>Note on Repeatable Annotations:</b> 230 * Repeatable annotations (those marked with {@link Repeatable @Repeatable}) are automatically 231 * expanded into their individual annotation instances. For example, if a parameter has multiple {@code @Marshalled} annotations, 232 * this method returns each {@code @Marshalled} annotation separately, rather than the container annotation. 233 * 234 * @return 235 * An unmodifiable list of annotations on this parameter, never <jk>null</jk>. 236 * <br>Repeatable annotations are expanded into individual instances. 237 */ 238 public List<AnnotationInfo<Annotation>> getAnnotations() { return annotations.get(); } 239 240 /** 241 * Returns a stream of annotation infos of the specified type declared on this parameter. 242 * 243 * @param <A> The annotation type. 244 * @param type The annotation type. 245 * @return A stream of annotation infos, never <jk>null</jk>. 246 */ 247 public <A extends Annotation> Stream<AnnotationInfo<A>> getAnnotations(Class<A> type) { 248 return getAnnotations().stream().filter(x -> x.isType(type)).map(x -> (AnnotationInfo<A>)x); 249 } 250 251 /** 252 * Returns the constructor that this parameter belongs to. 253 * 254 * @return The constructor that this parameter belongs to, or <jk>null</jk> if it belongs to a method. 255 */ 256 public ConstructorInfo getConstructor() { return executable.isConstructor() ? (ConstructorInfo)executable : null; } 257 258 /** 259 * Returns the {@link ExecutableInfo} which declares this parameter. 260 * 261 * <p> 262 * Same as calling {@link Parameter#getDeclaringExecutable()} but returns {@link ExecutableInfo} instead. 263 * 264 * <h5 class='section'>Example:</h5> 265 * <p class='bjava'> 266 * <jc>// Get the method or constructor that declares this parameter</jc> 267 * ParameterInfo <jv>pi</jv> = ...; 268 * ExecutableInfo <jv>executable</jv> = <jv>pi</jv>.getDeclaringExecutable(); 269 * <jk>if</jk> (<jv>executable</jv>.isConstructor()) { 270 * ConstructorInfo <jv>ci</jv> = (ConstructorInfo)<jv>executable</jv>; 271 * } 272 * </p> 273 * 274 * @return The {@link ExecutableInfo} declaring this parameter. 275 * @see Parameter#getDeclaringExecutable() 276 */ 277 public ExecutableInfo getDeclaringExecutable() { return executable; } 278 279 /** 280 * Returns the index position of this parameter. 281 * 282 * @return The index position of this parameter. 283 */ 284 public int getIndex() { return index; } 285 286 @Override /* Annotatable */ 287 public String getLabel() { 288 var exec = getDeclaringExecutable(); 289 var label = exec.getDeclaringClass().getNameSimple() + "." + exec.getNameShort(); 290 return label + "[" + index + "]"; 291 } 292 293 /** 294 * Returns this parameter and all matching parameters in parent classes. 295 * 296 * <p> 297 * For constructors, searches parent class constructors for parameters with matching name and type, 298 * regardless of parameter count or position. This allows finding annotated parameters that may be 299 * inherited by child classes even when constructor signatures differ. 300 * 301 * <p> 302 * For methods, searches matching methods (same signature) in parent classes/interfaces 303 * for parameters at the same index with matching name and type. 304 * 305 * <h5 class='section'>Examples:</h5> 306 * <p class='bjava'> 307 * <jc>// Constructor with different parameter counts:</jc> 308 * <jk>class</jk> A { 309 * A(String <jv>foo</jv>, <jk>int</jk> <jv>bar</jv>) {} 310 * } 311 * <jk>class</jk> B <jk>extends</jk> A { 312 * B(String <jv>foo</jv>) {} 313 * } 314 * <jc>// For B's foo parameter, returns: [B.foo, A.foo]</jc> 315 * ParameterInfo <jv>pi</jv> = ...; 316 * List<ParameterInfo> <jv>matching</jv> = <jv>pi</jv>.getMatchingParameters(); 317 * </p> 318 * 319 * @return A list of matching parameters including this one, in child-to-parent order. 320 */ 321 public List<ParameterInfo> getMatchingParameters() { return matchingParameters.get(); } 322 323 /** 324 * Returns the method that this parameter belongs to. 325 * 326 * @return The method that this parameter belongs to, or <jk>null</jk> if it belongs to a constructor. 327 */ 328 public MethodInfo getMethod() { return executable.isConstructor() ? null : (MethodInfo)executable; } 329 330 /** 331 * Returns the Java language modifiers for the parameter represented by this object, as an integer. 332 * 333 * <p> 334 * The {@link Modifier} class should be used to decode the modifiers. 335 * 336 * <p> 337 * Same as calling {@link Parameter#getModifiers()}. 338 * 339 * <h5 class='section'>Example:</h5> 340 * <p class='bjava'> 341 * <jc>// Check if parameter is final</jc> 342 * ParameterInfo <jv>pi</jv> = ...; 343 * <jk>int</jk> <jv>modifiers</jv> = <jv>pi</jv>.getModifiers(); 344 * <jk>boolean</jk> <jv>isFinal</jv> = Modifier.<jsm>isFinal</jsm>(<jv>modifiers</jv>); 345 * </p> 346 * 347 * @return The Java language modifiers for this parameter. 348 * @see Parameter#getModifiers() 349 * @see Modifier 350 */ 351 @Override 352 public int getModifiers() { return inner.getModifiers(); } 353 354 /** 355 * Returns the name of the parameter. 356 * 357 * <p> 358 * Searches for the name in the following order: 359 * <ol> 360 * <li>@Name annotation value (takes precedence over bytecode parameter names) 361 * <li>Bytecode parameter name (if compiled with -parameters flag) 362 * <li>Matching parameters in parent classes/interfaces (for methods) 363 * <li>Synthetic name like "arg0", "arg1", etc. (fallback) 364 * </ol> 365 * 366 * <p> 367 * This method works with any annotation named "Name" (from any package) that has a <c>String value()</c> method. 368 * 369 * @return The name of the parameter, never <jk>null</jk>. 370 * @see Parameter#getName() 371 */ 372 public String getName() { 373 var name = getResolvedName(); 374 return name != null ? name : inner.getName(); 375 } 376 377 /** 378 * Returns a {@link Type} object that identifies the parameterized type for this parameter. 379 * 380 * <p> 381 * Same as calling {@link Parameter#getParameterizedType()}. 382 * 383 * <h5 class='section'>Example:</h5> 384 * <p class='bjava'> 385 * <jc>// Get generic type information for parameter: List<String> values</jc> 386 * ParameterInfo <jv>pi</jv> = ...; 387 * Type <jv>type</jv> = <jv>pi</jv>.getParameterizedType(); 388 * <jk>if</jk> (<jv>type</jv> <jk>instanceof</jk> ParameterizedType) { 389 * ParameterizedType <jv>pType</jv> = (ParameterizedType)<jv>type</jv>; 390 * <jc>// pType.getActualTypeArguments()[0] is String.class</jc> 391 * } 392 * </p> 393 * 394 * @return A {@link Type} object identifying the parameterized type. 395 * @see Parameter#getParameterizedType() 396 */ 397 public Type getParameterizedType() { return inner.getParameterizedType(); } 398 399 /** 400 * Returns the class type of this parameter. 401 * 402 * @return The class type of this parameter. 403 */ 404 public ClassInfo getParameterType() { return type; } 405 406 /** 407 * Finds the name of this parameter for bean property mapping. 408 * 409 * <p> 410 * Searches for the parameter name in the following order: 411 * <ol> 412 * <li><ja>@Name</ja> annotation value 413 * <li>Bytecode parameter name (if available and not disabled via system property) 414 * <li>Matching parameters in parent classes/interfaces 415 * </ol> 416 * 417 * <p> 418 * This method is used for mapping constructor parameters to bean properties. 419 * 420 * <p> 421 * <b>Note:</b> This is different from {@link #getResolvedQualifier()} which looks for <ja>@Named</ja> 422 * annotations for bean injection purposes. 423 * 424 * @return The parameter name if found, or <jk>null</jk> if not available. 425 * @see #getResolvedQualifier() 426 * @see #getName() 427 */ 428 public String getResolvedName() { return resolvedName.get(); } 429 430 /** 431 * Finds the bean injection qualifier for this parameter. 432 * 433 * <p> 434 * Searches for the <ja>@Named</ja> or <ja>@Qualifier</ja> 435 * annotation value to determine which named bean should be injected. 436 * 437 * <p> 438 * This method is used by the {@link BeanStore} for bean injection. 439 * 440 * <p> 441 * <b>Note:</b> This is different from {@link #getResolvedName()} which looks for <ja>@Name</ja> 442 * annotations for bean property mapping. 443 * 444 * @return The bean qualifier name if {@code @Named} or {@code @Qualifier} annotation is found, or <jk>null</jk> if not annotated. 445 * @see #getResolvedName() 446 */ 447 public String getResolvedQualifier() { return resolvedQualifier.get(); } 448 449 /** 450 * Returns <jk>true</jk> if the parameter has a name. 451 * 452 * <p> 453 * This returns <jk>true</jk> if the parameter has an annotation with the simple name "Name", 454 * or if the parameter's name is present in the class file. 455 * 456 * @return <jk>true</jk> if the parameter has a name. 457 */ 458 public boolean hasName() { 459 return getResolvedName() != null; 460 } 461 462 /** 463 * Returns the wrapped {@link Parameter} object. 464 * 465 * @return The wrapped {@link Parameter} object. 466 */ 467 public Parameter inner() { 468 return inner; 469 } 470 471 /** 472 * Compares this ParameterInfo with the specified object for equality. 473 * 474 * <p> 475 * Two ParameterInfo objects are considered equal if they wrap the same underlying {@link Parameter} object. 476 * This delegates to the underlying {@link Parameter#equals(Object)} method. 477 * 478 * <p> 479 * This method makes ParameterInfo suitable for use as keys in hash-based collections such as {@link HashMap} 480 * and {@link HashSet}. 481 * 482 * @param obj The object to compare with. 483 * @return <jk>true</jk> if the objects are equal, <jk>false</jk> otherwise. 484 */ 485 @Override 486 public boolean equals(Object obj) { 487 return obj instanceof ParameterInfo obj2 && eq(this, obj2, (x, y) -> eq(x.inner, y.inner)); 488 } 489 490 /** 491 * Returns a hash code value for this ParameterInfo. 492 * 493 * <p> 494 * This delegates to the underlying {@link Parameter#hashCode()} method. 495 * 496 * <p> 497 * This method makes ParameterInfo suitable for use as keys in hash-based collections such as {@link HashMap} 498 * and {@link HashSet}. 499 * 500 * @return A hash code value for this ParameterInfo. 501 */ 502 @Override 503 public int hashCode() { 504 return inner.hashCode(); 505 } 506 507 @Override 508 public boolean is(ElementFlag flag) { 509 return switch (flag) { 510 case SYNTHETIC -> isSynthetic(); 511 case NOT_SYNTHETIC -> ! isSynthetic(); // HTT 512 case VARARGS -> isVarArgs(); 513 case NOT_VARARGS -> ! isVarArgs(); 514 default -> super.is(flag); 515 }; 516 } 517 518 /** 519 * Returns <jk>true</jk> if this parameter is implicitly declared in source code. 520 * 521 * <p> 522 * Returns <jk>true</jk> if this parameter is neither explicitly nor implicitly declared in source code. 523 * 524 * <p> 525 * Same as calling {@link Parameter#isImplicit()}. 526 * 527 * <h5 class='section'>Example:</h5> 528 * <p class='bjava'> 529 * <jc>// Filter out implicit parameters</jc> 530 * ParameterInfo <jv>pi</jv> = ...; 531 * <jk>if</jk> (! <jv>pi</jv>.isImplicit()) { 532 * <jc>// Process explicit parameter</jc> 533 * } 534 * </p> 535 * 536 * @return <jk>true</jk> if this parameter is implicitly declared. 537 * @see Parameter#isImplicit() 538 */ 539 public boolean isImplicit() { return inner.isImplicit(); } 540 541 /** 542 * Returns <jk>true</jk> if the parameter has a name according to the <c>.class</c> file. 543 * 544 * <p> 545 * Same as calling {@link Parameter#isNamePresent()}. 546 * 547 * <p> 548 * <b>Note:</b> This method is different from {@link #hasName()} which also checks for the presence 549 * of a <c>@Name</c> annotation. This method only checks if the name is present in the bytecode. 550 * 551 * <h5 class='section'>Example:</h5> 552 * <p class='bjava'> 553 * <jc>// Check if parameter name is in bytecode</jc> 554 * ParameterInfo <jv>pi</jv> = ...; 555 * <jk>if</jk> (<jv>pi</jv>.isNamePresent()) { 556 * String <jv>name</jv> = <jv>pi</jv>.getName(); 557 * } 558 * </p> 559 * 560 * @return <jk>true</jk> if the parameter has a name in the bytecode. 561 * @see Parameter#isNamePresent() 562 * @see #hasName() 563 */ 564 public boolean isNamePresent() { return inner.isNamePresent(); } 565 566 /** 567 * Returns <jk>true</jk> if this parameter is a synthetic construct as defined by the Java Language Specification. 568 * 569 * <p> 570 * Same as calling {@link Parameter#isSynthetic()}. 571 * 572 * <h5 class='section'>Example:</h5> 573 * <p class='bjava'> 574 * <jc>// Filter out compiler-generated parameters</jc> 575 * ParameterInfo <jv>pi</jv> = ...; 576 * <jk>if</jk> (! <jv>pi</jv>.isSynthetic()) { 577 * <jc>// Process real parameter</jc> 578 * } 579 * </p> 580 * 581 * @return <jk>true</jk> if this parameter is a synthetic construct. 582 * @see Parameter#isSynthetic() 583 */ 584 public boolean isSynthetic() { return inner.isSynthetic(); } 585 586 /** 587 * Returns <jk>true</jk> if the parameter type is an exact match for the specified class. 588 * 589 * @param c The type to check. 590 * @return <jk>true</jk> if the parameter type is an exact match for the specified class. 591 */ 592 public boolean isType(Class<?> c) { 593 return getParameterType().is(c); 594 } 595 //----------------------------------------------------------------------------------------------------------------- 596 // High Priority Methods (direct Parameter API compatibility) 597 //----------------------------------------------------------------------------------------------------------------- 598 599 /** 600 * Returns <jk>true</jk> if this parameter represents a variable argument list. 601 * 602 * <p> 603 * Same as calling {@link Parameter#isVarArgs()}. 604 * 605 * <p> 606 * Only returns <jk>true</jk> for the last parameter of a variable arity method. 607 * 608 * <h5 class='section'>Example:</h5> 609 * <p class='bjava'> 610 * <jc>// Check if this is a varargs parameter</jc> 611 * ParameterInfo <jv>pi</jv> = ...; 612 * <jk>if</jk> (<jv>pi</jv>.isVarArgs()) { 613 * <jc>// Handle variable arguments</jc> 614 * } 615 * </p> 616 * 617 * @return <jk>true</jk> if this parameter represents a variable argument list. 618 * @see Parameter#isVarArgs() 619 */ 620 public boolean isVarArgs() { return inner.isVarArgs(); } 621 622 /** 623 * Returns a detailed string representation of this parameter. 624 * 625 * <p> 626 * The returned string includes: 627 * <ul> 628 * <li>Modifiers (final) 629 * <li>Parameter type with generics (e.g., "List<String>") 630 * <li>Parameter name (if available) 631 * <li>Varargs indicator (if applicable) 632 * </ul> 633 * 634 * <h5 class='section'>Examples:</h5> 635 * <p class='bjava'> 636 * <jc>// Simple parameter</jc> 637 * ParameterInfo <jv>pi</jv> = ...; 638 * <jv>pi</jv>.toString(); 639 * <jc>// Returns: "java.lang.String name"</jc> 640 * 641 * <jc>// Final parameter</jc> 642 * <jc>// Returns: "final java.lang.String name"</jc> 643 * 644 * <jc>// Generic parameter</jc> 645 * <jc>// Returns: "java.util.List<java.lang.String> items"</jc> 646 * 647 * <jc>// Varargs parameter</jc> 648 * <jc>// Returns: "java.lang.String... values"</jc> 649 * 650 * <jc>// Parameter without name (fallback)</jc> 651 * <jc>// Returns: "int arg0"</jc> 652 * </p> 653 * 654 * @return A detailed string representation including modifiers, type, name, and varargs flag. 655 */ 656 @Override 657 public String toString() { 658 return toString.get(); 659 } 660 661 private String findToString() { 662 var sb = new StringBuilder(128); 663 664 // Modifiers (final is common for parameters) 665 var mods = Modifier.toString(getModifiers()); 666 if (nn(mods) && ! mods.isEmpty()) { 667 sb.append(mods).append(" "); 668 } 669 670 // Parameter type (use generic type if available to show generics) 671 var paramType = getParameterizedType(); 672 673 // For varargs, we need to get the component type and display it with "..." instead of "[]" 674 if (isVarArgs()) { 675 // Get the component type of the array 676 var typeInfo = ClassInfo.of(paramType); 677 var componentType = typeInfo.getComponentType(); 678 // Display the component type without array brackets, then add "..." 679 componentType.appendNameFormatted(sb, FULL, true, '$', BRACKETS); 680 sb.append("..."); 681 } else { 682 ClassInfo.of(paramType).appendNameFormatted(sb, FULL, true, '$', BRACKETS); 683 } 684 685 // Parameter name (if available) 686 var name = getResolvedName(); 687 if (nn(name)) { 688 sb.append(" ").append(name); 689 } else { 690 // Fallback to index-based name if no name available 691 sb.append(" arg").append(index); 692 } 693 694 return sb.toString(); 695 } 696 697 private List<ParameterInfo> findMatchingParameters() { 698 if (executable instanceof ConstructorInfo executable2) { 699 // For constructors: search parent class constructors for parameters with matching index and type 700 // Note: We match by index and type only, not by name, to avoid circular dependency 701 // (getName() needs getMatchingParameters() which needs getName()) 702 var list = new ArrayList<ParameterInfo>(); 703 704 // Add this parameter first 705 list.add(this); 706 707 // Search parent classes for matching parameters 708 var cc = executable2.getDeclaringClass().getSuperclass(); 709 while (nn(cc)) { 710 // Check all constructors in parent class 711 for (var pc : cc.getDeclaredConstructors()) { 712 // Check if constructor has parameter at this index with matching type 713 var params = pc.getParameters(); 714 if (index < params.size() && getParameterType().is(params.get(index).getParameterType())) { 715 list.add(params.get(index)); 716 } 717 } 718 cc = cc.getSuperclass(); 719 } 720 721 return list; 722 } 723 // For methods: use matching methods from parent classes 724 return ((MethodInfo)executable).getMatchingMethods().stream().map(m -> m.getParameter(index)).toList(); 725 } 726 727 //----------------------------------------------------------------------------------------------------------------- 728 // Annotatable interface methods 729 //----------------------------------------------------------------------------------------------------------------- 730 731 private String findNameInternal() { 732 // Search through matching parameters in hierarchy for @Name annotations only. 733 // Note: We intentionally prioritize @Name annotations over bytecode parameter names 734 // because bytecode names are unreliable - users may or may not compile with -parameters flag. 735 for (var mp : getMatchingParameters()) { 736 for (var ai : mp.getAnnotations()) { 737 if (ai.hasNameSimple("Name")) { 738 var value = ai.getValue().orElse(null); 739 if (value != null) // HTT 740 return value; 741 } 742 } 743 } 744 745 return o(inner).filter(x -> x.isNamePresent()).filter(x -> ! DISABLE_PARAM_NAME_DETECTION.get()).map(x -> x.getName()).orElse(null); 746 } 747 748 private String findQualifierInternal() { 749 // Search through matching parameters in hierarchy for @Named or qualifier-meta annotations. 750 // @formatter:off 751 return getMatchingParameters().stream() 752 .flatMap(mp -> mp.getAnnotations().stream()) 753 .map(JsrSupport::qualifierValue) 754 .filter(Objects::nonNull) 755 .findFirst() 756 .orElse(null); 757 // @formatter:on 758 } 759 760 /** 761 * Checks if this parameter can be resolved from the bean store. 762 * 763 * <p> 764 * The following parameter types are considered optional and always return <jk>true</jk>: 765 * <ul> 766 * <li>Parameters of type <c>Optional<T></c> 767 * <li>Parameters of type <c>T[]</c>, <c>List<T></c>, <c>Set<T></c>, or <c>Map<String,T></c> 768 * </ul> 769 * 770 * <p> 771 * If a parameter has a <ja>@Named</ja>or <ja>@Qualifier</ja> annotation, 772 * the method checks for a bean with that specific name in the bean store. Otherwise, it checks for an unnamed bean 773 * of the parameter type in the bean store, and if not found, checks the <c>otherBeans</c> parameter. 774 * 775 * @param beanStore The bean store to check for beans. 776 * @param otherBeans Optional additional bean instances to check if not found in the bean store. 777 * These are checked after the bean store but before returning <jk>false</jk>. 778 * @return <jk>true</jk> if this parameter can be resolved, <jk>false</jk> otherwise. 779 */ 780 public boolean canResolve(BeanStore beanStore, Object... otherBeans) { 781 var pt = getParameterType(); 782 783 // @Value sites resolve from Settings/Env, not the bean store — always considered resolvable 784 // here. (Mutual-exclusion with @Inject is enforced in resolveValue(...).) 785 if (ValueResolver.hasValueAnnotation(getAnnotations())) 786 return true; 787 788 if (pt.is(Optional.class) || pt.isInjectCollectionType()) // Optional/Collection/array types are always satisfied (even if empty). 789 return true; 790 791 var bq = getResolvedQualifier(); 792 793 if (nn(bq)) 794 return beanStore.hasBean(pt.inner(), bq); 795 796 if (beanStore.hasBean(pt.inner())) 797 return true; 798 799 for (var o : otherBeans) 800 if (canAccept(o)) 801 return true; 802 return false; 803 } 804 805 /** 806 * Returns the missing type name if this parameter cannot be resolved, <jk>null</jk> otherwise. 807 * 808 * <p> 809 * The following parameter types are considered optional and always return <jk>null</jk>: 810 * <ul> 811 * <li>Parameters of type <c>Optional<T></c> 812 * <li>Parameters of type <c>T[]</c>, <c>List<T></c>, <c>Set<T></c>, or <c>Map<String,T></c> 813 * </ul> 814 * 815 * <p> 816 * If a parameter has a <ja>@Named</ja> or <ja>@Qualifer</ja> annotation, 817 * the method checks for a bean with that specific name in the bean store. Otherwise, it checks for an unnamed bean 818 * of the parameter type in the bean store, and if not found, checks the <c>otherBeans</c> parameter. 819 * 820 * @param beanStore The bean store to check for beans. 821 * @param otherBeans Optional additional bean instances to check if not found in the bean store. 822 * These are checked after the bean store but before marking the parameter as missing. 823 * @return Missing type name (e.g., <js>"String"</js> or <js>"String@name"</js> for qualified beans) or <jk>null</jk> if resolvable. 824 */ 825 public String getMissingType(BeanStore beanStore, Object... otherBeans) { 826 var pt = getParameterType(); 827 if (ValueResolver.hasValueAnnotation(getAnnotations())) 828 return null; 829 if (pt.is(Optional.class) || pt.isInjectCollectionType()) // Optional/Collection/array types are always satisfied (even if empty). 830 return null; 831 var bq = getResolvedQualifier(); // Use @Named/@Qualified for bean injection 832 if (nn(bq)) { 833 if (! beanStore.hasBean(pt.inner(), bq)) 834 return pt.getNameSimple() + '@' + bq; 835 return null; 836 } 837 if (beanStore.hasBean(pt.inner())) 838 return null; 839 for (var o : otherBeans) 840 if (canAccept(o)) 841 return null; 842 return pt.getNameSimple(); 843 } 844 845 /** 846 * Resolves the parameter value from the bean store. 847 * 848 * <p> 849 * For each parameter, this method: 850 * <ul> 851 * <li>If the parameter is a collection/array/map type, collects all beans of the element type. 852 * <li>Otherwise, looks up a single bean by type and optional qualifier name in the bean store. 853 * <li>If not found in the bean store, checks the <c>otherBeans</c> parameter. 854 * </ul> 855 * 856 * <h5 class='section'>Parameter Resolution:</h5> 857 * <ul class='spaced-list'> 858 * <li><b>Single beans</b> - Resolved using {@link BeanStore#getBean(Class)} or {@link BeanStore#getBean(Class, String)}. 859 * If not found, checks <c>otherBeans</c> for a compatible instance. 860 * Throws {@link ExecutableException} if not found in either location. 861 * <li><b>Optional beans</b> - Wrapped in <c>Optional</c>, or <c>Optional.empty()</c> if not found. 862 * Never throws an exception. 863 * <li><b>Arrays</b> - All beans of the element type are collected into an array (may be empty). 864 * Never throws an exception. 865 * <li><b>Lists</b> - All beans of the element type are collected into a <c>List</c> (may be empty). 866 * Never throws an exception. 867 * <li><b>Sets</b> - All beans of the element type are collected into a <c>LinkedHashSet</c> (may be empty). 868 * Never throws an exception. 869 * <li><b>Maps</b> - All beans of the value type are collected into a <c>LinkedHashMap</c> keyed by bean name (may be empty). 870 * Unnamed beans use an empty string as the key. Never throws an exception. 871 * </ul> 872 * 873 * @param beanStore The bean store to resolve beans from. 874 * @param otherBeans Optional additional bean instances to use if not found in the bean store. 875 * These are checked after the bean store but before throwing an exception. 876 * @return The resolved parameter value. 877 * @throws ExecutableException If a required parameter (non-Optional, non-collection) cannot be resolved 878 * from the bean store or <c>otherBeans</c>. 879 */ 880 881 @SuppressWarnings({ 882 "java:S3776", // Cognitive complexity acceptable for this specific logic 883 "java:S6541" // Single-threaded context; synchronization unnecessary 884 }) 885 public Object resolveValue(BeanStore beanStore, Object... otherBeans) { 886 var pt = getParameterType(); 887 var bq = getResolvedQualifier(); 888 var ptu = pt.unwrap(Optional.class); 889 890 // @Value sites resolve from Settings/VarResolver, not the bean store. 891 var annos = getAnnotations(); 892 var valueExpr = ValueResolver.findValueExpression(annos); 893 if (valueExpr != null) { 894 ValueResolver.checkInjectConflict(annos, this.toString()); 895 // Pass the declared (generic) parameter type so @Value Supplier<String> autodetects 896 // Supplier<String> field/parameter type opts in to re-evaluating reads. 897 // BeanStore is forwarded so caller-scoped PropertySource beans (e.g. per-RestContext 898 // @Rest(config=...) Configs) participate in expression resolution alongside Settings. 899 var resolved = ValueResolver.resolve(valueExpr, ptu.inner(), inner.getParameterizedType(), this.toString(), beanStore); 900 // Mirror FieldInfo.inject: wrap in Optional when the declared parameter type is Optional<T>. 901 // VarResolver substitutes "" for a missing key with no default — collapse both null and "" 902 // to Optional.empty() so @Value("${maybe}") Optional<T> behaves the same as Spring's. 903 if (pt.is(Optional.class)) 904 return (resolved == null || (resolved instanceof String resolved2 && resolved2.isEmpty())) 905 ? oe() 906 : o(resolved); 907 return resolved; 908 } 909 910 if (JsrSupport.isProviderType(ptu.inner())) { 911 var valueType = ptu.innerType() instanceof ParameterizedType parameterizedType 912 && parameterizedType.getActualTypeArguments().length == 1 913 && parameterizedType.getActualTypeArguments()[0] instanceof Class<?> c 914 ? c 915 : Object.class; 916 var supplier = (Supplier<Object>)() -> beanStore.getBean((Class<Object>)valueType, bq).orElse(null); 917 var providerType = ptu.inner(); 918 if (eq(providerType.getName(), JsrSupport.JUNEAU_PROVIDER)) 919 return (Provider<Object>)supplier::get; 920 var handler = (InvocationHandler)(proxy, method, args) -> { 921 if (eq(method.getName(), "get") && method.getParameterCount() == 0) 922 return supplier.get(); 923 if (eq(method.getName(), "toString") && method.getParameterCount() == 0) 924 return providerType.getSimpleName() + "(" + valueType.getSimpleName() + ")"; 925 if (eq(method.getName(), "hashCode") && method.getParameterCount() == 0) 926 return System.identityHashCode(proxy); 927 if (eq(method.getName(), "equals") && method.getParameterCount() == 1) 928 return proxy == args[0]; 929 throw exex("Unsupported provider method: %s", method.getName()); 930 }; 931 return Proxy.newProxyInstance(providerType.getClassLoader(), new Class<?>[] {providerType}, handler); 932 } 933 934 // Handle collections and arrays 935 Object collectionValue = null; 936 if (ptu.isInjectCollectionType()) { 937 // Extract element type from collection/array/map parameter type 938 Class<?> elementType = null; 939 940 // Handle arrays 941 if (ptu.isArray()) { 942 elementType = ptu.getComponentType().inner(); 943 } else { 944 // Get the parameterized type 945 Type parameterizedType = getParameterizedType(); 946 if (parameterizedType instanceof ParameterizedType parameterizedType2) { 947 var rawType = parameterizedType2.getRawType(); 948 // If wrapped in Optional, unwrap it to get the nested type 949 if (rawType instanceof Class<?> rawType2 && rawType2 == Optional.class) { 950 var typeArgs = parameterizedType2.getActualTypeArguments(); 951 if (typeArgs.length > 0 && typeArgs[0] instanceof ParameterizedType typeArgs2) { 952 // Optional<List<T>> -> List<T> 953 parameterizedType = typeArgs2; 954 } else { 955 // Optional<SomeClass> - not a collection, elementType remains null 956 parameterizedType = null; 957 } 958 } 959 } else if (pt.innerType() instanceof ParameterizedType) { 960 // If pt is already a parameterized type (e.g., List<TestService> after unwrapping Optional), 961 // use pt.innerType() as the parameterizedType 962 parameterizedType = pt.innerType(); 963 } else { 964 parameterizedType = null; 965 } 966 967 // Handle List<T> or Set<T> 968 var inner2 = o(ptu.inner()).orElse(Object.class); 969 if (eqa(inner2, List.class, Set.class)) { 970 if (parameterizedType instanceof ParameterizedType parameterizedType2) { 971 var typeArgs = parameterizedType2.getActualTypeArguments(); 972 if (typeArgs.length > 0 && typeArgs[0] instanceof Class<?> elementClass) { 973 elementType = elementClass; 974 } 975 } 976 } else if (eq(inner2, Map.class) && parameterizedType instanceof ParameterizedType parameterizedType2) { 977 // Handle Map<String,T> - extract value type (second type argument) 978 var typeArgs = parameterizedType2.getActualTypeArguments(); 979 // Verify key type is String and get value type 980 if (typeArgs.length >= 2 && typeArgs[0] == String.class && typeArgs[1] instanceof Class<?> valueClass) { 981 elementType = valueClass; 982 } 983 } 984 } 985 986 collectionValue = ReflectionUtils.resolveCollectionValue(elementType, beanStore, ptu); 987 } 988 if (nn(collectionValue)) 989 return pt.is(Optional.class) ? o(collectionValue) : collectionValue; 990 991 // Handle single bean 992 var ptc = ptu.inner(); 993 994 Optional<Object> r; 995 996 if (nn(bq)) { 997 r = beanStore.getBean(ptc, bq); 998 } else { 999 r = beanStore.getBean(ptc); 1000 if (r.isEmpty()) { 1001 for (var r2 : otherBeans) 1002 if (canAccept(r2)) { 1003 r = o(r2); 1004 break; 1005 } 1006 } 1007 } 1008 1009 if (pt.is(Optional.class)) 1010 return r; 1011 if (r.isPresent()) 1012 return r.get(); 1013 1014 throw exex("Could not resolve value for parameter %s", this); 1015 } 1016 1017 /** 1018 * Resolves the parameter value from the bean store (constructor version with enclosingInstance). 1019 * 1020 * <p> 1021 * This method is used for constructor parameters. If the first parameter type matches the <c>enclosingInstance</c> 1022 * object type, it uses the <c>enclosingInstance</c> object (for non-static inner class constructors). 1023 * Otherwise, it delegates to {@link #resolveValue(BeanStore, Object...)}. 1024 * 1025 * @param beanStore The bean store to resolve beans from. 1026 * @param enclosingInstance The outer class instance for non-static inner class constructors. 1027 * If the first parameter type matches this object's type, it is used as the first parameter value. 1028 * Can be <jk>null</jk> for regular classes or static inner classes. 1029 * @param otherBeans Optional additional bean instances to use if not found in the bean store. 1030 * These are checked after the bean store but before throwing an exception. 1031 * @return The resolved parameter value. 1032 * @throws ExecutableException If a required parameter (non-Optional, non-collection) cannot be resolved 1033 * from the bean store or <c>otherBeans</c>. 1034 */ 1035 public Object resolveValue(BeanStore beanStore, Object enclosingInstance, Object... otherBeans) { 1036 var pt = getParameterType(); 1037 if (getIndex() == 0 && nn(enclosingInstance) && pt.isInstance(enclosingInstance)) 1038 return enclosingInstance; 1039 return resolveValue(beanStore, otherBeans); 1040 } 1041}