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 {
+ * @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<MethodInfo> <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<ParameterInfo> <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<AnnotationInfo<MyAnnotation>>
<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
//-----------------------------------------------------------------------------------------------------------------