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 62d65b262a Add findGetter/Setter methods to ClassInfo
62d65b262a is described below

commit 62d65b262af96dbc6d1e7f0a6939d63abdbdfee5
Author: James Bognar <[email protected]>
AuthorDate: Thu Mar 19 09:49:48 2026 -0400

    Add findGetter/Setter methods to ClassInfo
---
 .../apache/juneau/commons/reflect/ClassInfo.java   | 41 ++++++++++++++++++
 .../apache/juneau/commons/reflect/MethodInfo.java  | 35 ++++++++++++++++
 .../juneau/commons/reflect/ClassInfo_Test.java     | 40 ++++++++++++++++++
 .../juneau/commons/reflect/MethodInfo_Test.java    | 48 ++++++++++++++++++++++
 todo/TODO.md                                       |  1 -
 5 files changed, 164 insertions(+), 1 deletion(-)

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 fb577325da..8c8fa2ef7a 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
@@ -1667,6 +1667,47 @@ public class ClassInfo extends ElementInfo implements 
Annotatable, Type, Compara
                return publicMethods.get().stream().filter(x -> test(filter, 
x)).findFirst();
        }
 
+       /**
+        * Finds a public getter method for the specified bean property name.
+        *
+        * <p>
+        * Matches any public no-argument method named {@code 
get<PropertyName>} with a non-void return type,
+        * or {@code is<PropertyName>} with a {@code boolean}/{@link Boolean} 
return type.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Find the getter for the "name" property on MyBean</jc>
+        *      Optional&lt;MethodInfo&gt; <jv>getter</jv> = 
ClassInfo.<jsm>info</jsm>(MyBean.<jk>class</jk>).findGetter(<js>"name"</js>);
+        *      <jv>getter</jv>.ifPresent(<jv>m</jv> -&gt; 
System.<jsf>out</jsf>.println(<jv>m</jv>.getName()));  <jc>// "getName"</jc>
+        * </p>
+        *
+        * @param propertyName The bean property name (e.g. <js>"name"</js> to 
match {@code getName()} or {@code isName()}).
+        * @return The matching getter method, or an empty {@link Optional} if 
not found.
+        */
+       public Optional<MethodInfo> findGetter(String propertyName) {
+               return getPublicMethod(m -> m.isGetter() && 
m.getPropertyName().equals(propertyName));
+       }
+
+       /**
+        * Finds a public setter method for the specified bean property name.
+        *
+        * <p>
+        * Matches any public single-argument method named {@code 
set<PropertyName>}.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Find the setter for the "name" property on MyBean</jc>
+        *      Optional&lt;MethodInfo&gt; <jv>setter</jv> = 
ClassInfo.<jsm>info</jsm>(MyBean.<jk>class</jk>).findSetter(<js>"name"</js>);
+        *      <jv>setter</jv>.ifPresent(<jv>m</jv> -&gt; 
System.<jsf>out</jsf>.println(<jv>m</jv>.getName()));  <jc>// "setName"</jc>
+        * </p>
+        *
+        * @param propertyName The bean property name (e.g. <js>"name"</js> to 
match {@code setName(X)}).
+        * @return The matching setter method, or an empty {@link Optional} if 
not found.
+        */
+       public Optional<MethodInfo> findSetter(String propertyName) {
+               return getPublicMethod(m -> m.isSetter() && 
m.getPropertyName().equals(propertyName));
+       }
+
        /**
         * Returns all public methods on this class and parent classes.
         *
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/MethodInfo.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/MethodInfo.java
index ab21d7c6af..9de17c9e69 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/MethodInfo.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/MethodInfo.java
@@ -405,6 +405,41 @@ public class MethodInfo extends ExecutableInfo implements 
Comparable<MethodInfo>
                return n;
        }
 
+       /**
+        * Returns <jk>true</jk> if this method follows the Java Bean getter 
convention.
+        *
+        * <p>
+        * A getter is a public no-argument method whose name starts with 
{@code get} (with a non-void return type)
+        * or {@code is} (with a {@code boolean}/{@link Boolean} return type).
+        *
+        * @return <jk>true</jk> if this method is a bean getter.
+        */
+       public boolean isGetter() {
+               var n = inner.getName();
+               if (getParameterCount() != 0)
+                       return false;
+               if (n.startsWith("get") && n.length() > 3)
+                       return !getReturnType().is(Void.TYPE);
+               if (n.startsWith("is") && n.length() > 2) {
+                       var rt = getReturnType();
+                       return rt.is(Boolean.TYPE) || rt.is(Boolean.class);
+               }
+               return false;
+       }
+
+       /**
+        * Returns <jk>true</jk> if this method follows the Java Bean setter 
convention.
+        *
+        * <p>
+        * A setter is a method whose name starts with {@code set} and accepts 
exactly one argument.
+        *
+        * @return <jk>true</jk> if this method is a bean setter.
+        */
+       public boolean isSetter() {
+               var n = inner.getName();
+               return n.startsWith("set") && n.length() > 3 && 
getParameterCount() == 1;
+       }
+
        /**
         * Returns the generic return type of this method as a {@link 
ClassInfo} object.
         *
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 c157656666..22cfdc91e9 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
@@ -4273,5 +4273,45 @@ public class ClassInfo_Test extends TestBase {
                assertSame(service, bean.injectedService, "PostConstruct should 
see injected field");
                assertTrue(bean.postConstructCalled, "PostConstruct should be 
called after all injection");
        }
+
+       
//====================================================================================================
+       // findGetter() / findSetter()
+       
//====================================================================================================
+
+       public static class B038_FindGetterSetterBean {
+               private String name;
+               private boolean active;
+
+               public String getName() { return name; }
+               public void setName(String value) { this.name = value; }
+               public boolean isActive() { return active; }
+       }
+
+       @Test
+       void b038_findGetter() {
+               var ci = ClassInfo.of(B038_FindGetterSetterBean.class);
+
+               var getter = ci.findGetter("name");
+               assertTrue(getter.isPresent());
+               assertEquals("getName", getter.get().getName());
+
+               var isGetter = ci.findGetter("active");
+               assertTrue(isGetter.isPresent());
+               assertEquals("isActive", isGetter.get().getName());
+
+               assertFalse(ci.findGetter("nonExistent").isPresent());
+       }
+
+       @Test
+       void b039_findSetter() {
+               var ci = ClassInfo.of(B038_FindGetterSetterBean.class);
+
+               var setter = ci.findSetter("name");
+               assertTrue(setter.isPresent());
+               assertEquals("setName", setter.get().getName());
+
+               assertFalse(ci.findSetter("active").isPresent());   // no 
setActive method
+               assertFalse(ci.findSetter("nonExistent").isPresent());
+       }
 }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/MethodInfo_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/MethodInfo_Test.java
index fe36a9fa2a..63f9d26f0d 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/MethodInfo_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/MethodInfo_Test.java
@@ -1015,5 +1015,53 @@ class MethodInfo_Test extends TestBase {
                var method = 
ClassInfo.of(TestMethodClass.class).getPublicMethod(x -> x.hasName("method1") 
&& x.hasParameterTypes(TestService.class)).get();
                assertThrows(ExecutableException.class, () -> 
method.inject(beanStore, instance));
        }
+
+       
//====================================================================================================
+       // isGetter()
+       
//====================================================================================================
+
+       public static class A042_IsGetterBean {
+               public String getString() { return null; }
+               public boolean isActive() { return false; }
+               public Boolean isBoxedBoolean() { return false; }
+               public void getVoid() {}
+               public String get() { return null; }
+               public boolean is() { return false; }
+               public void foo() {}
+       }
+
+       @Test
+       void a042_isGetter() throws NoSuchMethodException {
+               var c = A042_IsGetterBean.class;
+               assertTrue(MethodInfo.of(c.getMethod("getString")).isGetter()); 
      // get + non-void return
+               assertTrue(MethodInfo.of(c.getMethod("isActive")).isGetter());  
      // is + boolean return
+               
assertTrue(MethodInfo.of(c.getMethod("isBoxedBoolean")).isGetter());  // is + 
Boolean return
+               assertFalse(MethodInfo.of(c.getMethod("getVoid")).isGetter());  
      // get + void return
+               assertFalse(MethodInfo.of(c.getMethod("get")).isGetter());      
      // "get" alone — too short
+               assertFalse(MethodInfo.of(c.getMethod("is")).isGetter());       
      // "is" alone — too short
+               assertFalse(MethodInfo.of(c.getMethod("foo")).isGetter());      
      // no getter prefix
+       }
+
+       
//====================================================================================================
+       // isSetter()
+       
//====================================================================================================
+
+       public static class A043_IsSetterBean {
+               public void setName(String value) {}
+               public void set() {}
+               public void setMulti(String a, String b) {}
+               public String getName() { return null; }
+               public void foo(String value) {}
+       }
+
+       @Test
+       void a043_isSetter() throws NoSuchMethodException {
+               var c = A043_IsSetterBean.class;
+               assertTrue(MethodInfo.of(c.getMethod("setName", 
String.class)).isSetter());               // set + one param
+               assertFalse(MethodInfo.of(c.getMethod("set")).isSetter());      
                           // "set" alone — too short
+               assertFalse(MethodInfo.of(c.getMethod("setMulti", String.class, 
String.class)).isSetter()); // set + two params
+               assertFalse(MethodInfo.of(c.getMethod("getName")).isSetter());  
                           // wrong prefix
+               assertFalse(MethodInfo.of(c.getMethod("foo", 
String.class)).isSetter());                   // no setter prefix
+       }
 }
 
diff --git a/todo/TODO.md b/todo/TODO.md
index 6d936744e0..9e8b2bff04 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -3,7 +3,6 @@
 
 - Investigate navlinks URL generation issue: Either 
"request:?Accept=text/json&plainText=true" should be supported, or 
"request:/?Accept=text/json&plainText=true" should not append '/' to the 
request URL. Currently, "request:/?Accept=..." generates URLs like 
"http://localhost:5000/rest/db/request:?Accept=..."; which is incorrect.
 - Update REST server API to use new BeanStore2.
-- ClassInfo should have a findGetter(String propertyName) convenience method.
 - Make sure @Beanp("*") works on plain fields.
 - Need an easier way to specify this header:
 Content-Disposition: attachment; filename="example.pdf"

Reply via email to