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 b0c71c4ee2 New BeanCreator API
b0c71c4ee2 is described below
commit b0c71c4ee26eb98f1395bcd741f967f9ea8eadeb
Author: James Bognar <[email protected]>
AuthorDate: Wed Jan 21 10:40:11 2026 -0500
New BeanCreator API
---
.../apache/juneau/commons/reflect/ClassInfo.java | 133 ++++++++++++++++++++-
.../juneau/commons/reflect/ExecutableInfo.java | 119 +++++++++++++++++-
.../juneau/commons/reflect/ClassInfo_Test.java | 60 +++++++++-
.../commons/reflect/ExecutableInfo_Test.java | 67 +++++++++++
4 files changed, 371 insertions(+), 8 deletions(-)
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ClassInfo.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ClassInfo.java
index c147c9b080..624e87c356 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ClassInfo.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ClassInfo.java
@@ -189,6 +189,7 @@ public class ClassInfo extends ElementInfo implements
Annotatable, Type, Compara
private final Supplier<String> fullName; // Fully qualified class name
with generics (e.g., "java.util.List<java.lang.String>").
private final Supplier<String> shortName; // Simple class name with
generics (e.g., "List<String>").
private final Supplier<String> readableName; // Human-readable class
name without generics (e.g., "List").
+ private final Supplier<String> toString; // String representation with
modifiers, class type, name, and type parameters.
private final Supplier<List<ClassInfo>> declaredInterfaces; // All
interfaces declared directly by this class.
private final Supplier<List<ClassInfo>> interfaces; // All interfaces
implemented by this class and its parents, in child-to-parent order.
private final Supplier<List<ClassInfo>> allParents; // All parent
classes and interfaces, classes first, then in child-to-parent order.
@@ -234,6 +235,7 @@ public class ClassInfo extends ElementInfo implements
Annotatable, Type, Compara
this.fullName = mem(() -> getNameFormatted(FULL, true, '$',
BRACKETS));
this.shortName = mem(() -> getNameFormatted(SHORT, true, '$',
BRACKETS));
this.readableName = mem(() -> getNameFormatted(SIMPLE, false,
'$', WORD));
+ this.toString = mem(this::findToString);
this.declaredInterfaces = mem(() -> opt(inner).map(x ->
stream(x.getGenericInterfaces()).map(ClassInfo::of).map(ClassInfo.class::cast).toList()).orElse(liste()));
this.interfaces = mem(() -> getParents().stream().flatMap(x ->
x.getDeclaredInterfaces().stream()).flatMap(ci2 -> concat(Stream.of(ci2),
ci2.getInterfaces().stream())).distinct().toList());
this.allParents = mem(() -> concat(getParents().stream(),
getInterfaces().stream()).toList());
@@ -2345,9 +2347,138 @@ public class ClassInfo extends ElementInfo implements
Annotatable, Type, Compara
}
}
+ /**
+ * Returns a detailed string representation of this class.
+ *
+ * <p>
+ * The returned string includes:
+ * <ul>
+ * <li>Modifiers (public, private, protected, static, final,
abstract, etc.)
+ * <li>Class type (class, interface, enum, annotation, or record)
+ * <li>Fully qualified class name
+ * <li>Generic type parameters with bounds (if any)
+ * </ul>
+ *
+ * <h5 class='section'>Examples:</h5>
+ * <p class='bjava'>
+ * <jc>// Regular class</jc>
+ * ClassInfo.<jsm>of</jsm>(String.<jk>class</jk>).toString();
+ * <jc>// Returns: "public final class java.lang.String"</jc>
+ *
+ * <jc>// Interface</jc>
+ * ClassInfo.<jsm>of</jsm>(List.<jk>class</jk>).toString();
+ * <jc>// Returns: "public interface java.util.List"</jc>
+ *
+ * <jc>// Generic class</jc>
+ * ClassInfo.<jsm>of</jsm>(ArrayList.<jk>class</jk>).toString();
+ * <jc>// Returns: "public class java.util.ArrayList<E>"</jc>
+ *
+ * <jc>// Generic class with bounds</jc>
+ * ClassInfo <jv>ci</jv> =
ClassInfo.<jsm>of</jsm>(Comparable.<jk>class</jk>);
+ * <jv>ci</jv>.toString();
+ * <jc>// Returns: "public interface
java.lang.Comparable<T>"</jc>
+ *
+ * <jc>// Enum</jc>
+ * ClassInfo.<jsm>of</jsm>(Month.<jk>class</jk>).toString();
+ * <jc>// Returns: "public enum java.time.Month"</jc>
+ *
+ * <jc>// Record</jc>
+ * ClassInfo <jv>record</jv> =
ClassInfo.<jsm>of</jsm>(MyRecord.<jk>class</jk>);
+ * <jv>record</jv>.toString();
+ * <jc>// Returns: "public record org.example.MyRecord"</jc>
+ * </p>
+ *
+ * <h5 class='section'>Notes:</h5>
+ * <ul class='spaced-list'>
+ * <li>For interfaces, the "abstract" modifier is omitted since
it's implicit.
+ * <li>For non-class types (e.g., TypeVariable,
ParameterizedType), returns the result of {@link Type#toString()}.
+ * <li>Type parameters include bounds when present (e.g., "<T
extends Comparable<T>>").
+ * </ul>
+ *
+ * <h5 class='section'>Comparison with Other Methods:</h5>
+ * <ul class='spaced-list'>
+ * <li>{@link #getNameSimple()} - Returns simple class name
without package (e.g., "String")
+ * <li>{@link #getNameShort()} - Returns short name with outer
classes (e.g., "MyClass$Inner")
+ * <li>{@link #getNameFull()} - Returns fully qualified name with
type parameters (e.g., "java.util.List<String>")
+ * </ul>
+ *
+ * @return A detailed string representation including modifiers, class
type, name, and type parameters.
+ */
@Override
public String toString() {
- return innerType.toString();
+ return toString.get();
+ }
+
+ private String findToString() {
+ var sb = new StringBuilder(256);
+
+ // Class type (class, interface, enum, annotation, record)
+ if (inner == null) {
+ // For non-class types (e.g., TypeVariable,
ParameterizedType), use innerType.toString()
+ return innerType.toString();
+ }
+
+ // Modifiers (filter out implicit modifiers)
+ var mods = Modifier.toString(getModifiers());
+ if (nn(mods) && ! mods.isEmpty()) {
+ // Remove "abstract" for interfaces since it's implicit
and redundant
+ // Remove "final" for enums and records since it's
implicit and redundant
+ // Also remove "interface" if it somehow got included
(shouldn't happen, but be safe)
+ if (isInterface()) {
+ mods = mods.replace("abstract",
"").replace("interface", "").trim().replaceAll("\\s+", " ");
+ } else if (isEnum() || isRecord()) {
+ mods = mods.replace("final",
"").trim().replaceAll("\\s+", " ");
+ }
+ if (! mods.isEmpty()) {
+ sb.append(mods).append(" ");
+ }
+ }
+
+ if (isRecord()) {
+ sb.append("record ");
+ } else if (isAnnotation()) {
+ sb.append("@interface ");
+ } else if (isEnum()) {
+ sb.append("enum ");
+ } else if (isInterface()) {
+ sb.append("interface ");
+ } else {
+ sb.append("class ");
+ }
+
+ // Full name
+ appendNameFormatted(sb, FULL, false, '$', BRACKETS);
+
+ // Type parameters (if declared on the class itself, not as a
parameterized type)
+ var typeParams = getTypeParameters();
+ if (! typeParams.isEmpty()) {
+ sb.append('<');
+ var first = true;
+ for (var tv : typeParams) {
+ if (! first)
+ sb.append(", ");
+ first = false;
+ sb.append(tv.getName());
+ var bounds = tv.getBounds();
+ if (bounds.length > 0 && (bounds.length > 1 ||
! bounds[0].equals(Object.class))) {
+ sb.append(" extends ");
+ var firstBound = true;
+ for (var bound : bounds) {
+ if (! firstBound)
+ sb.append(" & ");
+ firstBound = false;
+ if (bound instanceof Class<?>
boundClass) {
+
of(boundClass).appendNameFormatted(sb, FULL, true, '$', BRACKETS);
+ } else {
+
sb.append(bound.getTypeName());
+ }
+ }
+ }
+ }
+ sb.append('>');
+ }
+
+ return sb.toString();
}
/**
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
index 636defd46b..8a768e0f37 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
@@ -787,6 +787,57 @@ public abstract class ExecutableInfo extends
AccessibleInfo {
return inner.toGenericString();
}
+ /**
+ * Returns a detailed string representation of this executable (method
or constructor).
+ *
+ * <p>
+ * The returned string includes:
+ * <ul>
+ * <li>Modifiers (public, private, protected, static, final,
abstract, synchronized, etc.)
+ * <li>Synthetic and bridge flags (if applicable)
+ * <li>Generic type parameters with bounds (if any, e.g., "<T
extends Comparable<T>>")
+ * <li>Return type (for methods only, not constructors)
+ * <li>Fully qualified name with parameters
+ * <li>Throws declarations (if any)
+ * </ul>
+ *
+ * <h5 class='section'>Examples:</h5>
+ * <p class='bjava'>
+ * <jc>// Constructor</jc>
+ * ConstructorInfo <jv>ci</jv> =
ConstructorInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>.getConstructor(String.<jk>class</jk>));
+ * <jv>ci</jv>.toString();
+ * <jc>// Returns: "public
org.example.MyClass(java.lang.String)"</jc>
+ *
+ * <jc>// Method</jc>
+ * MethodInfo <jv>mi</jv> =
MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <js>"myMethod"</js>,
String.<jk>class</jk>);
+ * <jv>mi</jv>.toString();
+ * <jc>// Returns: "public java.lang.String
org.example.MyClass.myMethod(java.lang.String)"</jc>
+ *
+ * <jc>// Method with throws</jc>
+ * MethodInfo <jv>mi2</jv> =
MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <js>"riskyMethod"</js>);
+ * <jv>mi2</jv>.toString();
+ * <jc>// Returns: "public void org.example.MyClass.riskyMethod()
throws java.io.IOException, java.lang.Exception"</jc>
+ *
+ * <jc>// Generic method</jc>
+ * MethodInfo <jv>mi3</jv> =
MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <js>"genericMethod"</js>);
+ * <jv>mi3</jv>.toString();
+ * <jc>// Returns: "public <T> void
org.example.MyClass.genericMethod(T)"</jc>
+ *
+ * <jc>// Generic method with bounds</jc>
+ * MethodInfo <jv>mi4</jv> =
MethodInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>, <js>"boundedMethod"</js>);
+ * <jv>mi4</jv>.toString();
+ * <jc>// Returns: "public <T extends
java.lang.Comparable<T>> void
org.example.MyClass.boundedMethod(T)"</jc>
+ * </p>
+ *
+ * <h5 class='section'>Comparison with Other Methods:</h5>
+ * <ul class='spaced-list'>
+ * <li>{@link #getNameShort()} - Returns short name with
parameters (e.g., "myMethod(int, String)")
+ * <li>{@link #getNameFull()} - Returns fully qualified name with
parameters (e.g., "org.example.MyClass.myMethod(int, java.lang.String)")
+ * <li>{@link #toGenericString()} - Returns generic string from
underlying Executable
+ * </ul>
+ *
+ * @return A detailed string representation including modifiers, return
type, name, and throws declarations.
+ */
@Override
public String toString() {
return toString.get();
@@ -801,18 +852,76 @@ public abstract class ExecutableInfo extends
AccessibleInfo {
sb.append(mods).append(" ");
}
- // Add synthetic and bridge flags (not actual modifiers but
useful to show)
+ // Add synthetic, bridge, varargs, and default flags (not
actual modifiers but useful to show)
if (isSynthetic())
sb.append("synthetic ");
- if (this instanceof MethodInfo mi && mi.isBridge())
- sb.append("bridge ");
+ if (this instanceof MethodInfo mi) {
+ if (mi.isBridge())
+ sb.append("bridge ");
+ if (mi.isDefault())
+ sb.append("default ");
+ }
+ if (isVarArgs())
+ sb.append("varargs ");
+
+ // Type parameters (if any)
+ var typeParams = getTypeParameters();
+ var hasTypeParams = typeParams.length > 0;
+ if (hasTypeParams) {
+ sb.append('<');
+ var first = true;
+ for (var tv : typeParams) {
+ if (! first)
+ sb.append(", ");
+ first = false;
+ sb.append(tv.getName());
+ var bounds = tv.getBounds();
+ if (bounds.length > 0 && (bounds.length > 1 ||
! bounds[0].equals(Object.class))) {
+ sb.append(" extends ");
+ var firstBound = true;
+ for (var bound : bounds) {
+ if (! firstBound)
+ sb.append(" & ");
+ firstBound = false;
+ if (bound instanceof Class<?>
boundClass) {
+
ClassInfo.of(boundClass).appendNameFormatted(sb, FULL, true, '$', BRACKETS);
+ } else {
+
sb.append(bound.getTypeName());
+ }
+ }
+ }
+ }
+ sb.append("> ");
+ }
// Return type (skip for constructors)
if (this instanceof MethodInfo mi)
mi.getReturnType().appendNameFormatted(sb, FULL, true,
'$', BRACKETS).append(" ");
- // Full name
- sb.append(getNameFull());
+ // Full name - use generic parameter types if we have type
parameters
+ if (hasTypeParams) {
+ // Build signature with generic parameter types to show
type variables instead of erased types
+ var dc = getDeclaringClass();
+ var pi = dc.getPackage();
+ if (nn(pi))
+ sb.append(pi.getName()).append('.');
+ dc.appendNameFormatted(sb, SHORT, true, '$', BRACKETS);
+ if (! isConstructor)
+ sb.append('.').append(getNameSimple());
+ sb.append('(');
+ var genericParamTypes =
inner.getGenericParameterTypes();
+ var first = true;
+ for (var gpt : genericParamTypes) {
+ if (! first)
+ sb.append(',');
+ first = false;
+ ClassInfo.of(gpt).appendNameFormatted(sb, FULL,
true, '$', BRACKETS);
+ }
+ sb.append(')');
+ } else {
+ // Use regular getNameFull() for non-generic methods
+ sb.append(getNameFull());
+ }
// Throws declarations
var exTypes = getExceptionTypes();
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ClassInfo_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ClassInfo_Test.java
index 311900110e..55fce15ea2 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ClassInfo_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ClassInfo_Test.java
@@ -3335,8 +3335,8 @@ public class ClassInfo_Test extends TestBase {
//====================================================================================================
@Test
void a105_toString() {
- assertEquals("class org.apache.juneau.commons.reflect.AClass",
aClass.toString());
- assertEquals("interface
org.apache.juneau.commons.reflect.AInterface", aInterface.toString());
+ assertEquals("public class
org.apache.juneau.commons.reflect.AClass", aClass.toString());
+ assertEquals("public interface
org.apache.juneau.commons.reflect.AInterface", aInterface.toString());
assertEquals("class
org.apache.juneau.commons.reflect.ClassInfo_Test$A1", aType.toString());
assertEquals("java.util.Map<java.lang.String,
java.util.List<java.lang.String>>", pType.toString());
assertEquals("java.util.Map<java.lang.String,
java.lang.String[][]>", pTypeDimensional.toString());
@@ -3344,6 +3344,62 @@ public class ClassInfo_Test extends TestBase {
assertEquals("V", pTypeGenericArg.toString());
}
+ // Test classes for comprehensive toString() testing
+ public static class ToStringTestPublic {}
+ static class ToStringTestPackage {}
+ protected static class ToStringTestProtected {}
+ private static class ToStringTestPrivate {}
+ public static final class ToStringTestFinal {}
+ public abstract static class ToStringTestAbstract {}
+ public static class ToStringTestGeneric<T> {}
+ public static class ToStringTestGenericWithBounds<T extends
Comparable<T> & java.io.Serializable> {}
+ public enum ToStringTestEnum { VALUE1, VALUE2 }
+ public @interface ToStringTestAnnotation {}
+ public static record ToStringTestRecord(String name, int value) {}
+
+ @Test
+ void a106_toString_comprehensive() {
+ // Different visibility modifiers (note: inner classes are
static)
+ assertEquals("public static class
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestPublic",
ClassInfo.of(ToStringTestPublic.class).toString());
+ assertEquals("static class
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestPackage",
ClassInfo.of(ToStringTestPackage.class).toString());
+ assertEquals("protected static class
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestProtected",
ClassInfo.of(ToStringTestProtected.class).toString());
+ assertEquals("private static class
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestPrivate",
ClassInfo.of(ToStringTestPrivate.class).toString());
+
+ // Final class
+ assertEquals("public static final class
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestFinal",
ClassInfo.of(ToStringTestFinal.class).toString());
+
+ // Abstract class (Modifier.toString() returns "public abstract
static" in standard Java modifier order)
+ assertEquals("public abstract static class
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestAbstract",
ClassInfo.of(ToStringTestAbstract.class).toString());
+
+ // Generic class
+ assertEquals("public static class
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestGeneric<T>",
ClassInfo.of(ToStringTestGeneric.class).toString());
+
+ // Generic class with bounds
+ assertEquals("public static class
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestGenericWithBounds<T
extends java.lang.Comparable<T> & java.io.Serializable>",
ClassInfo.of(ToStringTestGenericWithBounds.class).toString());
+
+ // Enum
+ assertEquals("public static enum
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestEnum",
ClassInfo.of(ToStringTestEnum.class).toString());
+
+ // Annotation
+ assertEquals("public static @interface
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestAnnotation",
ClassInfo.of(ToStringTestAnnotation.class).toString());
+
+ // Record
+ assertEquals("public static record
org.apache.juneau.commons.reflect.ClassInfo_Test$ToStringTestRecord",
ClassInfo.of(ToStringTestRecord.class).toString());
+
+ // Test existing classes with different modifiers
+ assertEquals("public static class
org.apache.juneau.commons.reflect.ClassInfo_Test$H_Public", hPublic.toString());
+ assertEquals("static class
org.apache.juneau.commons.reflect.ClassInfo_Test$H_Package",
hPackage.toString());
+ assertEquals("protected static class
org.apache.juneau.commons.reflect.ClassInfo_Test$H_Protected",
hProtected.toString());
+ assertEquals("private static class
org.apache.juneau.commons.reflect.ClassInfo_Test$H_Private",
hPrivate.toString());
+ assertEquals("public abstract class
org.apache.juneau.commons.reflect.ClassInfo_Test$H_AbstractPublic",
hAbstractPublic.toString());
+
+ // Test standard library classes
+ assertEquals("public final class java.lang.String",
ClassInfo.of(String.class).toString());
+ assertEquals("public interface java.util.List<E>",
ClassInfo.of(java.util.List.class).toString());
+ assertEquals("public class java.util.ArrayList<E>",
ClassInfo.of(java.util.ArrayList.class).toString());
+ assertEquals("public enum java.time.Month",
ClassInfo.of(java.time.Month.class).toString());
+ }
+
//====================================================================================================
// unwrap(Class<?>...)
//====================================================================================================
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ExecutableInfo_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ExecutableInfo_Test.java
index 45f59dce06..f55ab88433 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ExecutableInfo_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ExecutableInfo_Test.java
@@ -874,5 +874,72 @@ class ExecutableInfo_Test extends TestBase {
check("public void
org.apache.juneau.commons.reflect.ExecutableInfo_Test$B.m()", b_m1.toString());
check("public int
org.apache.juneau.commons.reflect.ExecutableInfo_Test$B.m(java.lang.String)",
b_m2.toString());
}
+
+ // Test classes for comprehensive toString() testing
+ static class ToStringTestClass {
+ public ToStringTestClass() {}
+ private ToStringTestClass(int i) {} // NOSONAR
+ protected ToStringTestClass(String s) {} // NOSONAR
+ static ToStringTestClass create() { return null; } // NOSONAR
+ public void publicMethod() {}
+ private void privateMethod() {} // NOSONAR
+ protected void protectedMethod() {} // NOSONAR
+ static void staticMethod() {} // NOSONAR
+ final void finalMethod() {} // NOSONAR
+ public void methodWithThrows() throws java.io.IOException,
java.lang.Exception {} // NOSONAR
+ public <T> void genericMethod(T t) {} // NOSONAR
+ public <T extends Comparable<T>> void genericMethodWithBounds(T
t) {} // NOSONAR
+ }
+ static abstract class ToStringTestAbstractClass {
+ abstract void abstractMethod(); // NOSONAR
+ }
+
+ @Test
+ void a046_toString_comprehensive() {
+ var ci = ClassInfo.of(ToStringTestClass.class);
+
+ // Constructors with different modifiers
+ var publicCtor = ci.getPublicConstructor(cons ->
cons.getParameterCount() == 0).get();
+ assertEquals("public
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass()",
publicCtor.toString());
+
+ var privateCtor = ci.getDeclaredConstructor(x ->
x.hasParameterTypes(int.class)).get();
+ assertEquals("private
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass(int)",
privateCtor.toString());
+
+ var protectedCtor = ci.getDeclaredConstructor(x ->
x.hasParameterTypes(String.class)).get();
+ assertEquals("protected
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass(java.lang.String)",
protectedCtor.toString());
+
+ // Methods with different modifiers
+ var publicMethod = ci.getPublicMethod(x ->
x.hasName("publicMethod")).get();
+ assertEquals("public void
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass.publicMethod()",
publicMethod.toString());
+
+ var privateMethod = ci.getDeclaredMethod(x ->
x.hasName("privateMethod")).get();
+ assertEquals("private void
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass.privateMethod()",
privateMethod.toString());
+
+ var protectedMethod = ci.getDeclaredMethod(x ->
x.hasName("protectedMethod")).get();
+ assertEquals("protected void
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass.protectedMethod()",
protectedMethod.toString());
+
+ var staticMethod = ci.getDeclaredMethod(x ->
x.hasName("staticMethod")).get();
+ assertEquals("static void
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass.staticMethod()",
staticMethod.toString());
+
+ var finalMethod = ci.getDeclaredMethod(x ->
x.hasName("finalMethod")).get();
+ assertEquals("final void
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass.finalMethod()",
finalMethod.toString());
+
+ // Method with throws
+ var methodWithThrows = ci.getPublicMethod(x ->
x.hasName("methodWithThrows")).get();
+ assertEquals("public void
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass.methodWithThrows()
throws java.io.IOException, java.lang.Exception", methodWithThrows.toString());
+
+ // Generic method
+ var genericMethod = ci.getPublicMethod(x ->
x.hasName("genericMethod")).get();
+ assertEquals("public <T> void
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass.genericMethod(T)",
genericMethod.toString());
+
+ // Generic method with bounds
+ var genericMethodWithBounds = ci.getPublicMethod(x ->
x.hasName("genericMethodWithBounds")).get();
+ assertEquals("public <T extends java.lang.Comparable<T>> void
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestClass.genericMethodWithBounds(T)",
genericMethodWithBounds.toString());
+
+ // Abstract method
+ var abstractCi = ClassInfo.of(ToStringTestAbstractClass.class);
+ var abstractMethod = abstractCi.getDeclaredMethod(x ->
x.hasName("abstractMethod")).get();
+ assertEquals("abstract void
org.apache.juneau.commons.reflect.ExecutableInfo_Test$ToStringTestAbstractClass.abstractMethod()",
abstractMethod.toString());
+ }
}