This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 288b21f16b Utility class modernization
288b21f16b is described below

commit 288b21f16b4b93dc3c015973ba1835f99b425cd2
Author: James Bognar <[email protected]>
AuthorDate: Tue Nov 4 09:27:03 2025 -0500

    Utility class modernization
---
 .../juneau/common/reflect/ExecutableInfo.java      |  13 +
 .../apache/juneau/common/reflect/MethodInfo.java   |  89 +++++++
 .../juneau/common/reflect/ParameterInfo.java       | 131 +++++++++-
 .../juneau/common/reflect/MethodInfo_Test.java     | 139 +++++++++++
 .../juneau/common/reflect/ParamInfoTest.java       | 266 +++++++++++++++++++++
 5 files changed, 630 insertions(+), 8 deletions(-)

diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
index 8fde4898d3..6495f2451c 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
@@ -411,6 +411,19 @@ public abstract class ExecutableInfo extends 
AccessibleInfo {
                        && IntStream.range(0, args.length).allMatch(i -> 
params.get(i).getParameterType().is(args[i]));
        }
 
+       /**
+        * Returns <jk>true</jk> if this executable has matching parameter 
types with the provided parameter list.
+        *
+        * @param params The parameters to match against.
+        * @return <jk>true</jk> if this executable has matching parameter 
types.
+        */
+       public final boolean hasMatchingParameters(List<ParameterInfo> params) {
+               var myParams = getParameters();
+               return myParams.size() == params.size()
+                       && IntStream.range(0, params.size()).allMatch(i -> 
+                               
myParams.get(i).getParameterType().is(params.get(i).getParameterType()));
+       }
+
        /**
         * Returns <jk>true</jk> if all specified flags are applicable to this 
method.
         *
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
index ca42063828..47a55eface 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
@@ -92,6 +92,8 @@ public class MethodInfo extends ExecutableInfo implements 
Comparable<MethodInfo>
        private final Supplier<List<MethodInfo>> matchingCache =
                memoize(() -> findMatching(list(), this, getDeclaringClass()));
 
+       private final Supplier<List<MethodInfo>> matchingMethods = 
memoize(this::_findMatchingMethods);
+
        /**
         * Constructor.
         *
@@ -103,6 +105,83 @@ public class MethodInfo extends ExecutableInfo implements 
Comparable<MethodInfo>
                this.m = m;
        }
 
+       /**
+        * Returns this method and all matching methods up the hierarchy chain.
+        *
+        * <p>
+        * Searches parent classes and interfaces for methods with matching 
name and parameter types.
+        * Results are returned in the following order:
+        * <ol>
+        *      <li>This method
+        *      <li>Any matching methods on declared interfaces of this class
+        *      <li>Matching method on the parent class
+        *      <li>Any matching methods on the declared interfaces of the 
parent class
+        *      <li>Continue up the hierarchy
+        * </ol>
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bjava'>
+        *      <jc>// Interface and class hierarchy:</jc>
+        *      <jk>interface</jk> I1 {
+        *              <jk>void</jk> foo(String <jv>s</jv>);
+        *      }
+        *      <jk>class</jk> A {
+        *              <jk>void</jk> foo(String <jv>s</jv>) {}
+        *      }
+        *      <jk>interface</jk> I2 {
+        *              <jk>void</jk> foo(String <jv>s</jv>);
+        *      }
+        *      <jk>class</jk> B <jk>extends</jk> A <jk>implements</jk> I2 {
+        *              &#64;Override
+        *              <jk>void</jk> foo(String <jv>s</jv>) {}
+        *      }
+        *      <jc>// For B.foo(), returns: [B.foo, I2.foo, A.foo, I1.foo]</jc>
+        *      MethodInfo <jv>mi</jv> = ...;
+        *      List&lt;MethodInfo&gt; <jv>matching</jv> = 
<jv>mi</jv>.getMatchingMethods();
+        * </p>
+        *
+        * @return A list of matching methods including this one, in 
child-to-parent order.
+        */
+       public List<MethodInfo> getMatchingMethods() {
+               return matchingMethods.get();
+       }
+
+       private List<MethodInfo> _findMatchingMethods() {
+               var result = new ArrayList<MethodInfo>();
+               result.add(this); // 1. This method
+
+               var cc = getDeclaringClass();
+
+               while (nn(cc)) {
+                       // 2. Add matching methods from declared interfaces of 
current class
+                       cc.getDeclaredInterfaces().stream()
+                               .forEach(di -> 
addMatchingMethodsFromInterface(result, di));
+
+                       // 3. Move to parent class
+                       cc = cc.getSuperclass();
+                       if (nn(cc)) {
+                               // Add matching method from parent class
+                               cc.getDeclaredMethods().stream()
+                                       .filter(this::matches)
+                                       .findFirst()
+                                       .ifPresent(result::add);
+                       }
+               }
+
+               return result;
+       }
+
+       private void addMatchingMethodsFromInterface(List<MethodInfo> result, 
ClassInfo iface) {
+               // Add matching methods from this interface
+               iface.getDeclaredMethods().stream()
+                       .filter(this::matches)
+                       .forEach(result::add);
+
+               // Recursively search parent interfaces
+               iface.getDeclaredInterfaces().stream()
+                       .forEach(pi -> addMatchingMethodsFromInterface(result, 
pi));
+       }
+
        /**
         * Performs an action on this object if the specified predicate test 
passes.
         *
@@ -687,6 +766,16 @@ public class MethodInfo extends ExecutableInfo implements 
Comparable<MethodInfo>
                return test(test, this);
        }
 
+       /**
+        * Returns <jk>true</jk> if this method matches the specified method by 
name and parameter types.
+        *
+        * @param m The method to compare against.
+        * @return <jk>true</jk> if this method has the same name and parameter 
types as the specified method.
+        */
+       public boolean matches(MethodInfo m) {
+               return hasName(m.getName()) && 
hasMatchingParameters(m.getParameters());
+       }
+
        
//-----------------------------------------------------------------------------------------------------------------
        // High Priority Methods (direct Method API compatibility)
        
//-----------------------------------------------------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
index 134a149066..674a59a6c8 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
@@ -48,7 +48,8 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
                        .supplier(k -> opt(findAnnotation(k)))
                        .build();
 
-       private final Supplier<List<AnnotationInfo<Annotation>>> annotations = 
memoize(this::findAnnotations);
+       private final Supplier<List<AnnotationInfo<Annotation>>> annotations = 
memoize(this::_findAnnotations);
+       private final Supplier<List<ParameterInfo>> matchingParameters = 
memoize(this::_findMatchingParameters);
 
        /**
         * Constructor.
@@ -75,7 +76,7 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
                return parameter;
        }
 
-       private List<AnnotationInfo<Annotation>> findAnnotations() {
+       private List<AnnotationInfo<Annotation>> _findAnnotations() {
                return stream(parameter.getAnnotations()).map(a -> 
AnnotationInfo.of(this, a)).toList();
        }
 
@@ -113,6 +114,119 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
                return getAnnotationInfos().stream().filter(x -> 
x.isType(type)).map(x -> (AnnotationInfo<A>)x);
        }
 
+       /**
+        * Returns this parameter and all matching parameters in parent classes.
+        *
+        * <p>
+        * For constructors, searches parent class constructors for parameters 
with matching name and type,
+        * regardless of parameter count or position. This allows finding 
annotated parameters that may be
+        * inherited by child classes even when constructor signatures differ.
+        *
+        * <p>
+        * For methods, searches matching methods (same signature) in parent 
classes/interfaces
+        * for parameters at the same index with matching name and type.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bjava'>
+        *      <jc>// Constructor with different parameter counts:</jc>
+        *      <jk>class</jk> A {
+        *              A(String <jv>foo</jv>, <jk>int</jk> <jv>bar</jv>) {}
+        *      }
+        *      <jk>class</jk> B <jk>extends</jk> A {
+        *              B(String <jv>foo</jv>) {}
+        *      }
+        *      <jc>// For B's foo parameter, returns: [B.foo, A.foo]</jc>
+        *      ParameterInfo <jv>pi</jv> = ...;
+        *      List&lt;ParameterInfo&gt; <jv>matching</jv> = 
<jv>pi</jv>.getMatchingParameters();
+        * </p>
+        *
+        * @return A list of matching parameters including this one, in 
child-to-parent order.
+        */
+       public List<ParameterInfo> getMatchingParameters() {
+               return matchingParameters.get();
+       }
+
+       private List<ParameterInfo> _findMatchingParameters() {
+               if (executable.isConstructor()) {
+                       // For constructors: search parent class constructors 
for parameters with matching name and type
+                       var ci = (ConstructorInfo)executable;
+                       var list = new ArrayList<ParameterInfo>();
+
+                       // Add this parameter first
+                       list.add(this);
+
+                       // Search parent classes for matching parameters
+                       var cc = ci.getDeclaringClass().getSuperclass();
+                       while (nn(cc)) {
+                               // Check all constructors in parent class
+                               for (var pc : cc.getDeclaredConstructors()) {
+                                       // Check all parameters in each 
constructor for name and type match
+                                       for (var pp : pc.getParameters()) {
+                                               if (eq(getName(), pp.getName()) 
&& getParameterType().is(pp.getParameterType())) {
+                                                       list.add(pp);
+                                               }
+                                       }
+                               }
+                               cc = cc.getSuperclass();
+                       }
+
+                       return list;
+               }
+               // For methods: use matching methods from parent classes
+               return 
((MethodInfo)executable).getMatchingMethods().stream().map(m -> 
m.getParameter(index)).toList();
+       }
+
+       /**
+        * Finds all annotations of the specified type using enhanced search.
+        *
+        * <p>
+        * Search order:
+        * <ol>
+        *      <li>Annotations on the parameter itself
+        *      <li>Annotations on the parameter's type (after unwrapping 
Value/Optional)
+        *      <li>For methods: Annotations on matching parameters in parent 
class methods (child-to-parent order)
+        * </ol>
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Find all @MyAnnotation annotations with hierarchy 
search</jc>
+        *      ParameterInfo <jv>pi</jv> = ...;
+        *      Stream&lt;AnnotationInfo&lt;MyAnnotation&gt;&gt; 
<jv>annotations</jv> = <jv>pi</jv>.findAnnotations(MyAnnotation.<jk>class</jk>);
+        * </p>
+        *
+        * @param <A> The annotation type to look for.
+        * @param type The annotation type to look for.
+        * @return A stream of matching annotations, or an empty stream if none 
found.
+        */
+       @SuppressWarnings("unchecked")
+       public <A extends Annotation> Stream<AnnotationInfo<A>> 
findAnnotations(Class<A> type) {
+               var paramType = 
executable.getParameter(index).getParameterType().unwrap(Value.class, 
Optional.class);
+
+               if (executable.isConstructor()) {
+                       // For constructors: parameter annotations, then 
parameter type annotations
+                       return Stream.concat(
+                               getAnnotations(type),
+                               paramType.getDeclaredAnnotationInfos().stream()
+                                       .filter(x -> x.isType(type))
+                                       .map(x -> (AnnotationInfo<A>)x)
+                       );
+               } else {
+                       // For methods: parameter annotations, parameter type 
annotations, then matching methods on parent classes
+                       var mi = (MethodInfo)executable;
+                       var matchingMethods = new ArrayList<MethodInfo>();
+                       mi.forEachMatchingParentFirst(null, 
matchingMethods::add);
+
+                       return Stream.of(
+                               getAnnotations(type),
+                               paramType.getDeclaredAnnotationInfos().stream()
+                                       .filter(x -> x.isType(type))
+                                       .map(x -> (AnnotationInfo<A>)x),
+                               matchingMethods.stream()
+                                       .flatMap(m -> 
m.getParameter(index).getAnnotations(type))
+                       ).flatMap(s -> s);
+               }
+       }
+
        /**
         * Finds the annotation of the specified type defined on this method 
parameter.
         *
@@ -243,22 +357,23 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
         * <p>
         * If the parameter has an annotation with the simple name "Name" and a 
"value()" method,
         * then this method returns the value from that annotation.
-        * Otherwise, if the parameter's name is present in the class file, 
then this method returns that name.
-        * Otherwise, this method returns <jk>null</jk>.
+        * Otherwise, this method returns the parameter name from the class 
file.
+        *
+        * <p>
+        * Note: If the code was not compiled with the <c>-parameters</c> flag, 
the parameter name
+        * will be a synthetic name like "arg0", "arg1", etc.
         *
         * <p>
         * This method works with any annotation named "Name" (from any 
package) that has a <c>String value()</c> method.
         *
-        * @return The name of the parameter, or <jk>null</jk> if not available.
+        * @return The name of the parameter, never <jk>null</jk>.
         * @see Parameter#getName()
         */
        public String getName() {
                String name = getNameFromAnnotation();
                if (name != null)
                        return name;
-               if (parameter.isNamePresent())
-                       return parameter.getName();
-               return null;
+               return parameter.getName();
        }
 
        /**
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/MethodInfo_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/MethodInfo_Test.java
index 5593941bc0..ad23dea2d8 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/MethodInfo_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/MethodInfo_Test.java
@@ -159,6 +159,145 @@ class MethodInfo_Test extends TestBase {
                check("B3.foo(int),B2.foo(int),B1.foo(int)", l);
        }
 
+       
//-----------------------------------------------------------------------------------------------------------------
+       // getMatchingMethods()
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Nested
+       class GetMatchingMethodsTests {
+
+               public interface BM1 {
+                       void foo(String s);
+               }
+
+               public interface BM2 {
+                       void foo(String s);
+               }
+
+               public class BM3 {
+                       public void foo(String s) {}  // NOSONAR
+               }
+
+               public interface BM4 extends BM1 {
+                       @Override void foo(String s);
+               }
+
+               public class BM5 extends BM3 implements BM2 {
+                       @Override public void foo(String s) {}  // NOSONAR
+               }
+
+               public class BM6 extends BM5 implements BM4 {
+                       @Override public void foo(String s) {}  // NOSONAR
+               }
+
+               @Test void simpleHierarchy() throws Exception {
+                       var mi = MethodInfo.of(B3.class.getMethod("foo", 
int.class));
+                       check("B3.foo(int),B1.foo(int),B2.foo(int)", 
mi.getMatchingMethods());
+               }
+
+               @Test void multipleInterfaces() throws Exception {
+                       var mi = MethodInfo.of(BM5.class.getMethod("foo", 
String.class));
+                       
check("BM5.foo(String),BM2.foo(String),BM3.foo(String)", 
mi.getMatchingMethods());
+               }
+
+               @Test void nestedInterfaces() throws Exception {
+                       var mi = MethodInfo.of(BM6.class.getMethod("foo", 
String.class));
+                       
check("BM6.foo(String),BM4.foo(String),BM1.foo(String),BM5.foo(String),BM2.foo(String),BM3.foo(String)",
 mi.getMatchingMethods());
+               }
+
+               @Test void onlyThis() throws Exception {
+                       var mi = 
MethodInfo.of(Object.class.getMethod("toString"));
+                       check("Object.toString()", mi.getMatchingMethods());
+               }
+
+               public interface BM7 {
+                       void bar();
+               }
+
+               public class BM8 implements BM7 {
+                       @Override public void bar() {}  // NOSONAR
+                       public void baz() {}  // NOSONAR
+               }
+
+               @Test void withInterface() throws Exception {
+                       var mi = MethodInfo.of(BM8.class.getMethod("bar"));
+                       check("BM8.bar(),BM7.bar()", mi.getMatchingMethods());
+               }
+
+               @Test void noMatchInParent() throws Exception {
+                       var mi = MethodInfo.of(BM8.class.getMethod("baz"));
+                       check("BM8.baz()", mi.getMatchingMethods());
+               }
+
+               // False match tests - different method names
+               public class BM9 {
+                       public void foo(String s) {}  // NOSONAR
+                       public void bar(String s) {}  // NOSONAR
+               }
+
+               public class BM10 extends BM9 {
+                       @Override public void foo(String s) {}  // NOSONAR
+               }
+
+               @Test void differentMethodName() throws Exception {
+                       var mi = MethodInfo.of(BM10.class.getMethod("foo", 
String.class));
+                       // Should not match bar() even though it has same 
parameters
+                       check("BM10.foo(String),BM9.foo(String)", 
mi.getMatchingMethods());
+               }
+
+               // False match tests - same method name, different argument 
types
+               public class BM11 {
+                       public void foo(int x) {}  // NOSONAR
+                       public void foo(String s) {}  // NOSONAR
+               }
+
+               public class BM12 extends BM11 {
+                       @Override public void foo(int x) {}  // NOSONAR
+               }
+
+               @Test void sameNameDifferentArgs() throws Exception {
+                       var mi = MethodInfo.of(BM12.class.getMethod("foo", 
int.class));
+                       // Should not match foo(String) even though same method 
name
+                       check("BM12.foo(int),BM11.foo(int)", 
mi.getMatchingMethods());
+               }
+
+               // False match tests - different method name, same argument 
types
+               public interface BM13 {
+                       void bar(String s);
+               }
+
+               public class BM14 {
+                       public void baz(String s) {}  // NOSONAR
+               }
+
+               public class BM15 extends BM14 implements BM13 {
+                       @Override public void bar(String s) {}  // NOSONAR
+                       public void foo(String s) {}  // NOSONAR
+               }
+
+               @Test void differentNameSameArgs() throws Exception {
+                       var mi = MethodInfo.of(BM15.class.getMethod("foo", 
String.class));
+                       // Should not match bar() or baz() even though they 
have same parameters
+                       check("BM15.foo(String)", mi.getMatchingMethods());
+               }
+
+               // False match tests - different parameter count
+               public class BM16 {
+                       public void foo(String s) {}  // NOSONAR
+                       public void foo(String s, int x) {}  // NOSONAR
+               }
+
+               public class BM17 extends BM16 {
+                       @Override public void foo(String s) {}  // NOSONAR
+               }
+
+               @Test void differentParameterCount() throws Exception {
+                       var mi = MethodInfo.of(BM17.class.getMethod("foo", 
String.class));
+                       // Should not match foo(String, int)
+                       check("BM17.foo(String),BM16.foo(String)", 
mi.getMatchingMethods());
+               }
+       }
+
        
//-----------------------------------------------------------------------------------------------------------------
        // Annotations
        
//-----------------------------------------------------------------------------------------------------------------
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
index fddaf49920..a55c2dcd19 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
@@ -31,6 +31,8 @@ import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
 import org.junit.jupiter.api.*;
 
+import org.apache.juneau.annotation.Name;
+
 /**
  * ParamInfo tests.
  */
@@ -348,6 +350,270 @@ class ParamInfoTest extends TestBase {
                assertEquals("a1[1]", e_a1_b.toString());
        }
 
+       
//-----------------------------------------------------------------------------------------------------------------
+       // getMatchingParameters()
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Nested
+       class GetMatchingParametersTests {
+
+               // Method hierarchy tests
+               public interface PM1 {
+                       void foo(String s);
+               }
+
+               public static class PM2 implements PM1 {
+                       @Override public void foo(String s) {}  // NOSONAR
+               }
+
+               public static class PM3 extends PM2 {
+                       @Override public void foo(String s) {}  // NOSONAR
+               }
+
+               @Test void method_simpleHierarchy() throws Exception {
+                       var mi = MethodInfo.of(PM3.class.getMethod("foo", 
String.class));
+                       var pi = mi.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       assertEquals(3, matching.size());
+                       check("PM3", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+                       check("PM2", 
matching.get(1).getDeclaringExecutable().getDeclaringClass());
+                       check("PM1", 
matching.get(2).getDeclaringExecutable().getDeclaringClass());
+               }
+
+               // Method with multiple interfaces
+               public interface PM4 {
+                       void bar(int x, String s);
+               }
+
+               public interface PM5 {
+                       void bar(int x, String s);
+               }
+
+               public static class PM6 implements PM4, PM5 {
+                       @Override public void bar(int x, String s) {}  // 
NOSONAR
+               }
+
+               @Test void method_multipleInterfaces() throws Exception {
+                       var mi = MethodInfo.of(PM6.class.getMethod("bar", 
int.class, String.class));
+                       var pi0 = mi.getParameter(0);
+                       var matching0 = pi0.getMatchingParameters();
+                       assertEquals(3, matching0.size());
+                       check("PM6", 
matching0.get(0).getDeclaringExecutable().getDeclaringClass());
+                       check("PM4", 
matching0.get(1).getDeclaringExecutable().getDeclaringClass());
+                       check("PM5", 
matching0.get(2).getDeclaringExecutable().getDeclaringClass());
+
+                       var pi1 = mi.getParameter(1);
+                       var matching1 = pi1.getMatchingParameters();
+                       assertEquals(3, matching1.size());
+                       check("PM6", 
matching1.get(0).getDeclaringExecutable().getDeclaringClass());
+                       check("PM4", 
matching1.get(1).getDeclaringExecutable().getDeclaringClass());
+                       check("PM5", 
matching1.get(2).getDeclaringExecutable().getDeclaringClass());
+               }
+
+               // Constructor hierarchy with same parameter count
+               public static class PC1 {
+                       public PC1(String foo) {}  // NOSONAR
+               }
+
+               public static class PC2 extends PC1 {
+                       public PC2(String foo) { super(foo); }  // NOSONAR
+               }
+
+               public static class PC3 extends PC2 {
+                       public PC3(String foo) { super(foo); }  // NOSONAR
+               }
+
+               @Test void constructor_simpleHierarchy() throws Exception {
+                       var ci = 
ConstructorInfo.of(PC3.class.getConstructor(String.class));
+                       var pi = ci.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       assertEquals(3, matching.size());
+                       check("PC3", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+                       check("PC2", 
matching.get(1).getDeclaringExecutable().getDeclaringClass());
+                       check("PC1", 
matching.get(2).getDeclaringExecutable().getDeclaringClass());
+               }
+
+               // Constructor hierarchy with different parameter counts
+               public static class PC4 {
+                       public PC4(String foo, int bar) {}  // NOSONAR
+               }
+
+               public static class PC5 extends PC4 {
+                       public PC5(String foo) { super(foo, 0); }  // NOSONAR
+               }
+
+               @Test void constructor_differentParameterCounts() throws 
Exception {
+                       var ci = 
ConstructorInfo.of(PC5.class.getConstructor(String.class));
+                       var pi = ci.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       // Should find matching "foo" parameter in PC4 even 
though PC4 has more parameters
+                       assertEquals(2, matching.size());
+                       check("PC5", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+                       check("PC4", 
matching.get(1).getDeclaringExecutable().getDeclaringClass());
+               }
+
+               // Constructor with multiple constructors in parent
+               public static class PC6 {
+                       public PC6(String foo) {}  // NOSONAR
+                       public PC6(String foo, int bar) {}  // NOSONAR
+               }
+
+               public static class PC7 extends PC6 {
+                       public PC7(String foo) { super(foo); }  // NOSONAR
+               }
+
+               @Test void constructor_multipleParentConstructors() throws 
Exception {
+                       var ci = 
ConstructorInfo.of(PC7.class.getConstructor(String.class));
+                       var pi = ci.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       // Should find "foo" parameter in both PC6 constructors
+                       assertEquals(3, matching.size());
+                       check("PC7", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+                       check("PC6", 
matching.get(1).getDeclaringExecutable().getDeclaringClass());
+                       check("PC6", 
matching.get(2).getDeclaringExecutable().getDeclaringClass());
+               }
+
+               // False match tests - different parameter names
+               public interface PM7 {
+                       void baz(String differentName);
+               }
+
+               public static class PM8 implements PM7 {
+                       @Override public void baz(String differentName) {}  // 
NOSONAR
+                       public void foo(String s) {}  // NOSONAR
+               }
+
+               @Test void method_differentParameterName() throws Exception {
+                       var mi = MethodInfo.of(PM8.class.getMethod("foo", 
String.class));
+                       var pi = mi.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       // Should only find this parameter
+                       assertEquals(1, matching.size());
+                       check("PM8", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+               }
+
+               // False match tests - different parameter types
+               public interface PM9 {
+                       void qux(int x);
+               }
+
+               public static class PM10 implements PM9 {
+                       @Override public void qux(int x) {}  // NOSONAR
+               }
+
+               public static class PM11 extends PM10 {
+                       public void qux(String x) {}  // NOSONAR - different 
overload
+               }
+
+               @Test void method_differentParameterType() throws Exception {
+                       var mi = MethodInfo.of(PM11.class.getMethod("qux", 
String.class));
+                       var pi = mi.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       // Should only find this parameter (different type from 
parent)
+                       assertEquals(1, matching.size());
+                       check("PM11", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+               }
+
+               // Constructor false match - different parameter type
+               public static class PC8 {
+                       public PC8(int foo) {}  // NOSONAR
+               }
+
+               public static class PC9 extends PC8 {
+                       public PC9(String foo) { super(0); }  // NOSONAR
+               }
+
+               @Test void constructor_differentParameterType() throws 
Exception {
+                       var ci = 
ConstructorInfo.of(PC9.class.getConstructor(String.class));
+                       var pi = ci.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       // Should only find this parameter (different type from 
parent)
+                       assertEquals(1, matching.size());
+                       check("PC9", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+               }
+
+               // Constructor false match - different parameter name
+               public static class PC10 {
+                       public PC10(String bar) {}  // NOSONAR
+               }
+
+               public static class PC11 extends PC10 {
+                       public PC11(String foo) { super(foo); }  // NOSONAR
+               }
+
+               @Test void constructor_differentParameterName() throws 
Exception {
+                       var ci = 
ConstructorInfo.of(PC11.class.getConstructor(String.class));
+                       var pi = ci.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       // Note: Parameter names are not retained without 
-parameters flag,
+                       // so this will match by type and synthetic name (e.g., 
"arg0")
+                       assertEquals(2, matching.size());
+                       check("PC11", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+                       check("PC10", 
matching.get(1).getDeclaringExecutable().getDeclaringClass());
+               }
+
+               // Test with @Name annotation
+               public static class PC12 {
+                       public PC12(@Name("foo") String x) {}  // NOSONAR
+                       public PC12(@Name("bar") String x, int y) {}  // NOSONAR
+               }
+
+               public static class PC13 extends PC12 {
+                       public PC13(@Name("foo") String x) { super(x); }  // 
NOSONAR
+               }
+
+               @Test void constructor_withNameAnnotation_matching() throws 
Exception {
+                       var ci = 
ConstructorInfo.of(PC13.class.getConstructor(String.class));
+                       var pi = ci.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       // Should match PC12 constructor with @Name("foo"), not 
the one with @Name("bar")
+                       assertEquals(2, matching.size());
+                       check("PC13", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+                       check("PC12", 
matching.get(1).getDeclaringExecutable().getDeclaringClass());
+                       // Verify the matched parameter has name "foo"
+                       assertEquals("foo", matching.get(1).getName());
+               }
+
+               // Test with @Name annotation - different names
+               public static class PC14 {
+                       public PC14(@Name("bar") String x) {}  // NOSONAR
+               }
+
+               public static class PC15 extends PC14 {
+                       public PC15(@Name("foo") String x) { super(x); }  // 
NOSONAR
+               }
+
+               @Test void constructor_withNameAnnotation_differentNames() 
throws Exception {
+                       var ci = 
ConstructorInfo.of(PC15.class.getConstructor(String.class));
+                       var pi = ci.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       // Should not match parent parameter because names 
differ ("foo" vs "bar")
+                       assertEquals(1, matching.size());
+                       check("PC15", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+                       assertEquals("foo", matching.get(0).getName());
+               }
+
+               // Test with @Name annotation on methods
+               public interface PM12 {
+                       void test(@Name("param1") String x);
+               }
+
+               public static class PM13 implements PM12 {
+                       @Override public void test(@Name("param1") String x) {} 
 // NOSONAR
+               }
+
+               @Test void method_withNameAnnotation_matching() throws 
Exception {
+                       var mi = MethodInfo.of(PM13.class.getMethod("test", 
String.class));
+                       var pi = mi.getParameter(0);
+                       var matching = pi.getMatchingParameters();
+                       assertEquals(2, matching.size());
+                       check("PM13", 
matching.get(0).getDeclaringExecutable().getDeclaringClass());
+                       check("PM12", 
matching.get(1).getDeclaringExecutable().getDeclaringClass());
+                       assertEquals("param1", matching.get(0).getName());
+                       assertEquals("param1", matching.get(1).getName());
+               }
+       }
+
        
//-----------------------------------------------------------------------------------------------------------------
        // Helpers
        
//-----------------------------------------------------------------------------------------------------------------

Reply via email to