You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2026/08/23 13:48:11 UTC

(commons-beanutils) branch master updated (684d5057 -> 1d8d04e2)

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

garydgregory pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git


    from 684d5057 Skip static methods in FluentPropertyBeanIntrospector and mapped lookup (#437)
     new 5c9c77b8 Skip static methods in FluentPropertyBeanIntrospector and mapped lookup (#437).
     new 3e1923ba Skip static methods in FluentPropertyBeanIntrospector and mapped lookup
     new 1d8d04e2 Sort members.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/changes/changes.xml                            |   3 +-
 .../commons/beanutils2/PropertyUtilsBean.java      |  14 +-
 .../FluentPropertyBeanIntrospectorTest.java        |  32 +++
 .../commons/beanutils2/LazyDynaListTest.java       | 246 ++++++++++-----------
 .../commons/beanutils2/MappedPropertyTest.java     |  44 +++-
 .../commons/beanutils2/MappedPropertyTestBean.java |  22 +-
 .../commons/beanutils2/PropertyUtilsTest.java      | 196 ++++++++--------
 7 files changed, 315 insertions(+), 242 deletions(-)


(commons-beanutils) 03/03: Sort members.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git

commit 1d8d04e26fe11567eb99dd465329f3f8704d59fe
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Aug 23 09:47:17 2026 -0400

    Sort members.
---
 .../commons/beanutils2/PropertyUtilsBean.java      |  14 +-
 .../commons/beanutils2/LazyDynaListTest.java       | 246 ++++++++++-----------
 .../commons/beanutils2/PropertyUtilsTest.java      | 196 ++++++++--------
 3 files changed, 228 insertions(+), 228 deletions(-)

diff --git a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
index e9f98d5d..0309a15d 100644
--- a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
@@ -140,22 +140,22 @@ public class PropertyUtilsBean {
     }
 
     /**
-     * Clear any cached property descriptors information for all classes loaded by any class loaders. This is useful in cases where class loaders are thrown
-     * away to implement class reloading.
+     * Discards the memoized introspection results after the registered {@link BeanIntrospector} set has changed. Unlike {@link #clearDescriptors()} this leaves
+     * the JVM-global {@link Introspector} cache untouched.
      */
-    public void clearDescriptors() {
+    private void clearDescriptorCaches() {
         descriptorsCache.clear();
         mappedDescriptorsCache.clear();
-        Introspector.flushCaches();
     }
 
     /**
-     * Discards the memoized introspection results after the registered {@link BeanIntrospector} set has changed. Unlike {@link #clearDescriptors()} this leaves
-     * the JVM-global {@link Introspector} cache untouched.
+     * Clear any cached property descriptors information for all classes loaded by any class loaders. This is useful in cases where class loaders are thrown
+     * away to implement class reloading.
      */
-    private void clearDescriptorCaches() {
+    public void clearDescriptors() {
         descriptorsCache.clear();
         mappedDescriptorsCache.clear();
+        Introspector.flushCaches();
     }
 
     /**
diff --git a/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java b/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java
index 52f1f67f..893cbd57 100644
--- a/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java
+++ b/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java
@@ -337,43 +337,6 @@ class LazyDynaListTest {
         dynaBeanTest(list, LazyDynaBean.class, bean.getDynaClass(), new BenchBean());
     }
 
-    /**
-     * Test Map Create
-     */
-    @Test
-    void testMapDynaClass() {
-
-        // Create LazyArrayList for TreeMap's
-        final LazyDynaList list = new LazyDynaList(treeMapDynaClass);
-
-        // test
-        mapTest(list, TreeMap.class, new BenchBean());
-
-    }
-
-    /**
-     * Test Map Create
-     */
-    @Test
-    void testMapType() {
-
-        // Create LazyArrayList for HashMap's
-        final LazyDynaList list = new LazyDynaList(HashMap.class);
-
-        // test
-        mapTest(list, HashMap.class, new BenchBean());
-
-    }
-
-    /**
-     * Test adding a map to List with no type set.
-     */
-    @Test
-    void testNullType() {
-        final LazyDynaList lazyList = new LazyDynaList();
-        lazyList.add(new HashMap<>());
-    }
-
     /**
      * Test that the element type is set from the first element populated on an untyped List: a later
      * element of a different type is rejected, and toArray() returns an array of the element type.
@@ -398,112 +361,40 @@ class LazyDynaListTest {
     }
 
     /**
-     * Test that a POJO first element on an untyped List takes the WrapDynaBean path: toArray()
-     * returns an array of the POJO class and toDynaBeanArray() returns a WrapDynaBean[].
+     * Test Map Create
      */
     @Test
-    void testUntypedListPojoFirstElement() {
-        final LazyDynaList lazyList = new LazyDynaList();
-        final TestBean bean = new TestBean();
-        lazyList.add(bean);
-
-        final Object[] array = lazyList.toArray();
-        assertEquals(TestBean.class, array.getClass().getComponentType(), "Not TestBean[]");
-        assertSame(bean, array[0], "Wrong element");
-
-        final DynaBean[] dynaArray = lazyList.toDynaBeanArray();
-        assertEquals(WrapDynaBean.class, dynaArray.getClass().getComponentType(), "Not WrapDynaBean[]");
-        assertSame(bean, ((WrapDynaBean) dynaArray[0]).getInstance(), "Wrong wrapped instance");
-    }
+    void testMapDynaClass() {
 
-    /**
-     * Test that a DynaBean first element on an untyped List sets both the element type and the
-     * DynaBean type to the same DynaBean subclass.
-     */
-    @Test
-    void testUntypedListDynaBeanFirstElement() throws Exception {
-        final LazyDynaList lazyList = new LazyDynaList();
-        final DynaBean bean = basicDynaClass.newInstance();
-        lazyList.add(bean);
+        // Create LazyArrayList for TreeMap's
+        final LazyDynaList list = new LazyDynaList(treeMapDynaClass);
 
-        // elementType: toArray() returns an array of the DynaBean subclass
-        final Object[] array = lazyList.toArray();
-        assertEquals(BasicDynaBean.class, array.getClass().getComponentType(), "Not BasicDynaBean[]");
-        assertSame(bean, array[0], "Wrong element");
+        // test
+        mapTest(list, TreeMap.class, new BenchBean());
 
-        // elementDynaBeanType: toDynaBeanArray() returns the same subclass
-        final DynaBean[] dynaArray = lazyList.toDynaBeanArray();
-        assertEquals(BasicDynaBean.class, dynaArray.getClass().getComponentType(), "Not BasicDynaBean[]");
-        assertSame(bean, dynaArray[0], "Wrong element");
     }
 
     /**
-     * Test addAll(Collection) and addAll(int, Collection) on an untyped List: the type is set from
-     * the first element of the Collection and mismatched types are then rejected.
+     * Test Map Create
      */
     @Test
-    void testUntypedListAddAll() {
-        final List<Object> collection = new ArrayList<>();
-        for (int i = 0; i < 2; i++) {
-            final TreeMap<String, Object> map = new TreeMap<>();
-            map.put("prop" + i, "val" + i);
-            collection.add(map);
-        }
-
-        // addAll(Collection)
-        final LazyDynaList lazyList = new LazyDynaList();
-        lazyList.addAll(collection);
-        assertEquals(2, lazyList.size(), "1. check size");
-        TreeMap<?, ?>[] mapArray = (TreeMap[]) lazyList.toArray();
-        assertEquals("val0", mapArray[0].get("prop0"), "2. Map error");
-        assertEquals("val1", mapArray[1].get("prop1"), "3. Map error");
-        assertThrows(IllegalArgumentException.class, () -> lazyList.add(new TestBean()), "4. wrong type accepted");
-
-        // addAll(int, Collection) - grows the List to the insert position first
-        final LazyDynaList indexedList = new LazyDynaList();
-        indexedList.addAll(2, collection);
-        assertEquals(4, indexedList.size(), "5. check size");
-        mapArray = (TreeMap[]) indexedList.toArray();
-        assertEquals(4, mapArray.length, "6. check size");
-        assertEquals("val0", mapArray[2].get("prop0"), "7. Map error");
-        assertEquals("val1", mapArray[3].get("prop1"), "8. Map error");
-        assertThrows(IllegalArgumentException.class, () -> indexedList.add(new TestBean()), "9. wrong type accepted");
-    }
+    void testMapType() {
 
-    /**
-     * Test that get(index) grows an untyped List with the element type fixed by the first
-     * population.
-     */
-    @Test
-    void testUntypedListGrowAfterFirstElement() {
-        final LazyDynaList lazyList = new LazyDynaList();
-        final TreeMap<String, Object> map = new TreeMap<>();
-        map.put("prop", "val");
-        lazyList.add(map);
+        // Create LazyArrayList for HashMap's
+        final LazyDynaList list = new LazyDynaList(HashMap.class);
 
-        final Object grown = lazyList.get(2);
-        assertNotNull(grown, "DynaBean Not Created");
-        assertEquals(LazyDynaMap.class, grown.getClass(), "Not LazyDynaMap");
-        assertEquals(TreeMap.class, ((LazyDynaMap) grown).getMap().getClass(), "Wrong Map");
-        assertEquals(3, lazyList.size(), "check size");
+        // test
+        mapTest(list, HashMap.class, new BenchBean());
 
-        final TreeMap<?, ?>[] mapArray = (TreeMap[]) lazyList.toArray();
-        assertEquals(3, mapArray.length, "check array size");
-        assertEquals("val", mapArray[0].get("prop"), "Map error");
     }
 
     /**
-     * Test toDynaBeanArray() type correctness for the untyped Map case.
+     * Test adding a map to List with no type set.
      */
     @Test
-    void testUntypedListToDynaBeanArray() {
+    void testNullType() {
         final LazyDynaList lazyList = new LazyDynaList();
         lazyList.add(new HashMap<>());
-
-        final DynaBean[] dynaArray = lazyList.toDynaBeanArray();
-        assertEquals(LazyDynaMap.class, dynaArray.getClass().getComponentType(), "Not LazyDynaMap[]");
-        assertEquals(1, dynaArray.length, "check size");
-        assertEquals(HashMap.class, ((LazyDynaMap) dynaArray[0]).getMap().getClass(), "Wrong Map");
     }
 
     /**
@@ -662,4 +553,113 @@ class LazyDynaListTest {
         assertEquals(1, array.length, "Wrong array size");
         assertEquals(elem, array[0], "Wrong element");
     }
+
+    /**
+     * Test addAll(Collection) and addAll(int, Collection) on an untyped List: the type is set from
+     * the first element of the Collection and mismatched types are then rejected.
+     */
+    @Test
+    void testUntypedListAddAll() {
+        final List<Object> collection = new ArrayList<>();
+        for (int i = 0; i < 2; i++) {
+            final TreeMap<String, Object> map = new TreeMap<>();
+            map.put("prop" + i, "val" + i);
+            collection.add(map);
+        }
+
+        // addAll(Collection)
+        final LazyDynaList lazyList = new LazyDynaList();
+        lazyList.addAll(collection);
+        assertEquals(2, lazyList.size(), "1. check size");
+        TreeMap<?, ?>[] mapArray = (TreeMap[]) lazyList.toArray();
+        assertEquals("val0", mapArray[0].get("prop0"), "2. Map error");
+        assertEquals("val1", mapArray[1].get("prop1"), "3. Map error");
+        assertThrows(IllegalArgumentException.class, () -> lazyList.add(new TestBean()), "4. wrong type accepted");
+
+        // addAll(int, Collection) - grows the List to the insert position first
+        final LazyDynaList indexedList = new LazyDynaList();
+        indexedList.addAll(2, collection);
+        assertEquals(4, indexedList.size(), "5. check size");
+        mapArray = (TreeMap[]) indexedList.toArray();
+        assertEquals(4, mapArray.length, "6. check size");
+        assertEquals("val0", mapArray[2].get("prop0"), "7. Map error");
+        assertEquals("val1", mapArray[3].get("prop1"), "8. Map error");
+        assertThrows(IllegalArgumentException.class, () -> indexedList.add(new TestBean()), "9. wrong type accepted");
+    }
+
+    /**
+     * Test that a DynaBean first element on an untyped List sets both the element type and the
+     * DynaBean type to the same DynaBean subclass.
+     */
+    @Test
+    void testUntypedListDynaBeanFirstElement() throws Exception {
+        final LazyDynaList lazyList = new LazyDynaList();
+        final DynaBean bean = basicDynaClass.newInstance();
+        lazyList.add(bean);
+
+        // elementType: toArray() returns an array of the DynaBean subclass
+        final Object[] array = lazyList.toArray();
+        assertEquals(BasicDynaBean.class, array.getClass().getComponentType(), "Not BasicDynaBean[]");
+        assertSame(bean, array[0], "Wrong element");
+
+        // elementDynaBeanType: toDynaBeanArray() returns the same subclass
+        final DynaBean[] dynaArray = lazyList.toDynaBeanArray();
+        assertEquals(BasicDynaBean.class, dynaArray.getClass().getComponentType(), "Not BasicDynaBean[]");
+        assertSame(bean, dynaArray[0], "Wrong element");
+    }
+
+    /**
+     * Test that get(index) grows an untyped List with the element type fixed by the first
+     * population.
+     */
+    @Test
+    void testUntypedListGrowAfterFirstElement() {
+        final LazyDynaList lazyList = new LazyDynaList();
+        final TreeMap<String, Object> map = new TreeMap<>();
+        map.put("prop", "val");
+        lazyList.add(map);
+
+        final Object grown = lazyList.get(2);
+        assertNotNull(grown, "DynaBean Not Created");
+        assertEquals(LazyDynaMap.class, grown.getClass(), "Not LazyDynaMap");
+        assertEquals(TreeMap.class, ((LazyDynaMap) grown).getMap().getClass(), "Wrong Map");
+        assertEquals(3, lazyList.size(), "check size");
+
+        final TreeMap<?, ?>[] mapArray = (TreeMap[]) lazyList.toArray();
+        assertEquals(3, mapArray.length, "check array size");
+        assertEquals("val", mapArray[0].get("prop"), "Map error");
+    }
+
+    /**
+     * Test that a POJO first element on an untyped List takes the WrapDynaBean path: toArray()
+     * returns an array of the POJO class and toDynaBeanArray() returns a WrapDynaBean[].
+     */
+    @Test
+    void testUntypedListPojoFirstElement() {
+        final LazyDynaList lazyList = new LazyDynaList();
+        final TestBean bean = new TestBean();
+        lazyList.add(bean);
+
+        final Object[] array = lazyList.toArray();
+        assertEquals(TestBean.class, array.getClass().getComponentType(), "Not TestBean[]");
+        assertSame(bean, array[0], "Wrong element");
+
+        final DynaBean[] dynaArray = lazyList.toDynaBeanArray();
+        assertEquals(WrapDynaBean.class, dynaArray.getClass().getComponentType(), "Not WrapDynaBean[]");
+        assertSame(bean, ((WrapDynaBean) dynaArray[0]).getInstance(), "Wrong wrapped instance");
+    }
+
+    /**
+     * Test toDynaBeanArray() type correctness for the untyped Map case.
+     */
+    @Test
+    void testUntypedListToDynaBeanArray() {
+        final LazyDynaList lazyList = new LazyDynaList();
+        lazyList.add(new HashMap<>());
+
+        final DynaBean[] dynaArray = lazyList.toDynaBeanArray();
+        assertEquals(LazyDynaMap.class, dynaArray.getClass().getComponentType(), "Not LazyDynaMap[]");
+        assertEquals(1, dynaArray.length, "check size");
+        assertEquals(HashMap.class, ((LazyDynaMap) dynaArray[0]).getMap().getClass(), "Wrong Map");
+    }
 }
diff --git a/src/test/java/org/apache/commons/beanutils2/PropertyUtilsTest.java b/src/test/java/org/apache/commons/beanutils2/PropertyUtilsTest.java
index 0d1753d1..9e23278d 100644
--- a/src/test/java/org/apache/commons/beanutils2/PropertyUtilsTest.java
+++ b/src/test/java/org/apache/commons/beanutils2/PropertyUtilsTest.java
@@ -199,6 +199,30 @@ class PropertyUtilsTest {
         PropertyUtils.resetBeanIntrospectors();
     }
 
+    /**
+     * Registering a {@link SuppressPropertiesBeanIntrospector} must take effect for classes already introspected. The per-class descriptor cache was populated
+     * before the introspector was added and was never invalidated, so a property suppressed for hardening stayed readable and writable when its class had been
+     * introspected earlier (the common case with the shared {@code PropertyUtils} singleton).
+     */
+    @Test
+    void testAddBeanIntrospectorInvalidatesCache() throws Exception {
+        final PropertyUtilsBean pub = new PropertyUtilsBean();
+
+        // Warm the descriptor cache for TestBean before the suppression is configured.
+        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "Property should be visible before suppression");
+
+        final SuppressPropertiesBeanIntrospector suppressor = new SuppressPropertiesBeanIntrospector(Arrays.asList("stringProperty"));
+        pub.addBeanIntrospector(suppressor);
+
+        assertNull(pub.getPropertyDescriptor(bean, "stringProperty"), "Suppressed property should have no descriptor after the introspector is added");
+        assertThrows(NoSuchMethodException.class, () -> pub.getProperty(bean, "stringProperty"), "Suppressed property must not be readable");
+        assertThrows(NoSuchMethodException.class, () -> pub.setProperty(bean, "stringProperty", "changed"), "Suppressed property must not be writable");
+
+        // Removing the introspector must likewise invalidate the cache so the property becomes visible again.
+        pub.removeBeanIntrospector(suppressor);
+        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "Property should be visible again after the introspector is removed");
+    }
+
     /**
      * Tries to add a null BeanIntrospector.
      */
@@ -347,104 +371,6 @@ class PropertyUtilsTest {
         assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "A null suppressed entry must not hide unrelated properties");
     }
 
-    /**
-     * Registering a {@link SuppressPropertiesBeanIntrospector} must take effect for classes already introspected. The per-class descriptor cache was populated
-     * before the introspector was added and was never invalidated, so a property suppressed for hardening stayed readable and writable when its class had been
-     * introspected earlier (the common case with the shared {@code PropertyUtils} singleton).
-     */
-    @Test
-    void testAddBeanIntrospectorInvalidatesCache() throws Exception {
-        final PropertyUtilsBean pub = new PropertyUtilsBean();
-
-        // Warm the descriptor cache for TestBean before the suppression is configured.
-        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "Property should be visible before suppression");
-
-        final SuppressPropertiesBeanIntrospector suppressor = new SuppressPropertiesBeanIntrospector(Arrays.asList("stringProperty"));
-        pub.addBeanIntrospector(suppressor);
-
-        assertNull(pub.getPropertyDescriptor(bean, "stringProperty"), "Suppressed property should have no descriptor after the introspector is added");
-        assertThrows(NoSuchMethodException.class, () -> pub.getProperty(bean, "stringProperty"), "Suppressed property must not be readable");
-        assertThrows(NoSuchMethodException.class, () -> pub.setProperty(bean, "stringProperty", "changed"), "Suppressed property must not be writable");
-
-        // Removing the introspector must likewise invalidate the cache so the property becomes visible again.
-        pub.removeBeanIntrospector(suppressor);
-        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "Property should be visible again after the introspector is removed");
-    }
-
-    /**
-     * {@code resetBeanIntrospectors()} must invalidate the descriptor caches so that introspection results produced by a previously registered custom
-     * introspector are re-evaluated against the restored default set.
-     */
-    @Test
-    void testResetBeanIntrospectorsInvalidatesCache() throws Exception {
-        final PropertyUtilsBean pub = new PropertyUtilsBean();
-        pub.addBeanIntrospector(new SuppressPropertiesBeanIntrospector(Arrays.asList("stringProperty")));
-
-        // Warm the descriptor cache with the suppression in effect.
-        assertNull(pub.getPropertyDescriptor(bean, "stringProperty"), "Suppressed property should have no descriptor");
-
-        pub.resetBeanIntrospectors();
-
-        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "Property should be re-evaluated and visible after the reset");
-        assertEquals(bean.getStringProperty(), pub.getProperty(bean, "stringProperty"), "Property should be readable after the reset");
-    }
-
-    /**
-     * Changing the introspector set must also invalidate the mapped descriptor cache. The suppression guard in {@code getPropertyDescriptor} already hides a
-     * suppressed mapped property, so this checks the cached {@link MappedPropertyDescriptor} itself is dropped and re-created rather than served stale.
-     */
-    @Test
-    void testIntrospectorChangeInvalidatesMappedDescriptorCache() throws Exception {
-        final PropertyUtilsBean pub = new PropertyUtilsBean();
-
-        // Warm the mapped descriptor cache for TestBean.
-        final PropertyDescriptor first = pub.getPropertyDescriptor(bean, "mappedProperty");
-        assertNotNull(first, "Mapped property should be visible before suppression");
-
-        final SuppressPropertiesBeanIntrospector suppressor = new SuppressPropertiesBeanIntrospector(Arrays.asList("mappedProperty"));
-        pub.addBeanIntrospector(suppressor);
-
-        assertNull(pub.getPropertyDescriptor(bean, "mappedProperty"), "Suppressed mapped property should have no descriptor");
-        assertThrows(NoSuchMethodException.class, () -> pub.getProperty(bean, "mappedProperty(First Key)"), "Suppressed mapped property must not be readable");
-
-        pub.removeBeanIntrospector(suppressor);
-
-        final PropertyDescriptor second = pub.getPropertyDescriptor(bean, "mappedProperty");
-        assertNotNull(second, "Mapped property should be visible again after the introspector is removed");
-        assertNotSame(first, second, "Mapped descriptor must be re-created, not served from the stale cache");
-        assertEquals("First Value", pub.getProperty(bean, "mappedProperty(First Key)"), "Mapped property should be readable again");
-    }
-
-    /**
-     * Each add and remove in a sequence of introspector changes must invalidate the caches, so the visible property set always reflects the currently
-     * registered introspectors.
-     */
-    @Test
-    void testSequentialIntrospectorChangesInvalidateCache() throws Exception {
-        final PropertyUtilsBean pub = new PropertyUtilsBean();
-        final SuppressPropertiesBeanIntrospector suppressString = new SuppressPropertiesBeanIntrospector(Arrays.asList("stringProperty"));
-        final SuppressPropertiesBeanIntrospector suppressInt = new SuppressPropertiesBeanIntrospector(Arrays.asList("intProperty"));
-
-        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "stringProperty should be visible initially");
-        assertNotNull(pub.getPropertyDescriptor(bean, "intProperty"), "intProperty should be visible initially");
-
-        pub.addBeanIntrospector(suppressString);
-        assertNull(pub.getPropertyDescriptor(bean, "stringProperty"), "stringProperty should be suppressed after the first add");
-        assertNotNull(pub.getPropertyDescriptor(bean, "intProperty"), "intProperty should be unaffected by the first add");
-
-        pub.addBeanIntrospector(suppressInt);
-        assertNull(pub.getPropertyDescriptor(bean, "stringProperty"), "stringProperty should stay suppressed after the second add");
-        assertNull(pub.getPropertyDescriptor(bean, "intProperty"), "intProperty should be suppressed after the second add");
-
-        pub.removeBeanIntrospector(suppressString);
-        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "stringProperty should be visible again after its introspector is removed");
-        assertNull(pub.getPropertyDescriptor(bean, "intProperty"), "intProperty should stay suppressed after the unrelated remove");
-
-        pub.removeBeanIntrospector(suppressInt);
-        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "stringProperty should stay visible after the last remove");
-        assertNotNull(pub.getPropertyDescriptor(bean, "intProperty"), "intProperty should be visible again after its introspector is removed");
-    }
-
     /**
      * Test the describe() method.
      */
@@ -1572,6 +1498,32 @@ class PropertyUtilsTest {
         testGetWriteMethod(beanPublicSubclass, properties, TEST_BEAN_CLASS);
     }
 
+    /**
+     * Changing the introspector set must also invalidate the mapped descriptor cache. The suppression guard in {@code getPropertyDescriptor} already hides a
+     * suppressed mapped property, so this checks the cached {@link MappedPropertyDescriptor} itself is dropped and re-created rather than served stale.
+     */
+    @Test
+    void testIntrospectorChangeInvalidatesMappedDescriptorCache() throws Exception {
+        final PropertyUtilsBean pub = new PropertyUtilsBean();
+
+        // Warm the mapped descriptor cache for TestBean.
+        final PropertyDescriptor first = pub.getPropertyDescriptor(bean, "mappedProperty");
+        assertNotNull(first, "Mapped property should be visible before suppression");
+
+        final SuppressPropertiesBeanIntrospector suppressor = new SuppressPropertiesBeanIntrospector(Arrays.asList("mappedProperty"));
+        pub.addBeanIntrospector(suppressor);
+
+        assertNull(pub.getPropertyDescriptor(bean, "mappedProperty"), "Suppressed mapped property should have no descriptor");
+        assertThrows(NoSuchMethodException.class, () -> pub.getProperty(bean, "mappedProperty(First Key)"), "Suppressed mapped property must not be readable");
+
+        pub.removeBeanIntrospector(suppressor);
+
+        final PropertyDescriptor second = pub.getPropertyDescriptor(bean, "mappedProperty");
+        assertNotNull(second, "Mapped property should be visible again after the introspector is removed");
+        assertNotSame(first, second, "Mapped descriptor must be re-created, not served from the stale cache");
+        assertEquals("First Value", pub.getProperty(bean, "mappedProperty(First Key)"), "Mapped property should be readable again");
+    }
+
     /**
      * Test isReadable() method.
      */
@@ -1841,6 +1793,54 @@ class PropertyUtilsTest {
         assertTrue(desc.length > 0, "Got no descriptors");
     }
 
+    /**
+     * {@code resetBeanIntrospectors()} must invalidate the descriptor caches so that introspection results produced by a previously registered custom
+     * introspector are re-evaluated against the restored default set.
+     */
+    @Test
+    void testResetBeanIntrospectorsInvalidatesCache() throws Exception {
+        final PropertyUtilsBean pub = new PropertyUtilsBean();
+        pub.addBeanIntrospector(new SuppressPropertiesBeanIntrospector(Arrays.asList("stringProperty")));
+
+        // Warm the descriptor cache with the suppression in effect.
+        assertNull(pub.getPropertyDescriptor(bean, "stringProperty"), "Suppressed property should have no descriptor");
+
+        pub.resetBeanIntrospectors();
+
+        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "Property should be re-evaluated and visible after the reset");
+        assertEquals(bean.getStringProperty(), pub.getProperty(bean, "stringProperty"), "Property should be readable after the reset");
+    }
+
+    /**
+     * Each add and remove in a sequence of introspector changes must invalidate the caches, so the visible property set always reflects the currently
+     * registered introspectors.
+     */
+    @Test
+    void testSequentialIntrospectorChangesInvalidateCache() throws Exception {
+        final PropertyUtilsBean pub = new PropertyUtilsBean();
+        final SuppressPropertiesBeanIntrospector suppressString = new SuppressPropertiesBeanIntrospector(Arrays.asList("stringProperty"));
+        final SuppressPropertiesBeanIntrospector suppressInt = new SuppressPropertiesBeanIntrospector(Arrays.asList("intProperty"));
+
+        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "stringProperty should be visible initially");
+        assertNotNull(pub.getPropertyDescriptor(bean, "intProperty"), "intProperty should be visible initially");
+
+        pub.addBeanIntrospector(suppressString);
+        assertNull(pub.getPropertyDescriptor(bean, "stringProperty"), "stringProperty should be suppressed after the first add");
+        assertNotNull(pub.getPropertyDescriptor(bean, "intProperty"), "intProperty should be unaffected by the first add");
+
+        pub.addBeanIntrospector(suppressInt);
+        assertNull(pub.getPropertyDescriptor(bean, "stringProperty"), "stringProperty should stay suppressed after the second add");
+        assertNull(pub.getPropertyDescriptor(bean, "intProperty"), "intProperty should be suppressed after the second add");
+
+        pub.removeBeanIntrospector(suppressString);
+        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "stringProperty should be visible again after its introspector is removed");
+        assertNull(pub.getPropertyDescriptor(bean, "intProperty"), "intProperty should stay suppressed after the unrelated remove");
+
+        pub.removeBeanIntrospector(suppressInt);
+        assertNotNull(pub.getPropertyDescriptor(bean, "stringProperty"), "stringProperty should stay visible after the last remove");
+        assertNotNull(pub.getPropertyDescriptor(bean, "intProperty"), "intProperty should be visible again after its introspector is removed");
+    }
+
     /**
      * Corner cases on setIndexedProperty invalid arguments.
      */


(commons-beanutils) 01/03: Skip static methods in FluentPropertyBeanIntrospector and mapped lookup (#437).

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git

commit 5c9c77b83d65068ceed6865b8f6e910af41abf38
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Aug 23 09:37:13 2026 -0400

    Skip static methods in FluentPropertyBeanIntrospector and mapped lookup
    (#437).
---
 src/changes/changes.xml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index e5fbcee3..e17c936d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -57,7 +57,8 @@
       <action type="fix" dev="ggregory" due-to="Naveed Khan, Gary Gregory">Floor pre-epoch Timestamp seconds in DateTimeConverter.convertToType (#433).</action>
       <action type="fix" dev="ggregory" due-to="Naveed Khan, Gary Gregory">Record element type of first element in LazyDynaList.transform (#434).</action>
       <action type="fix" dev="ggregory" due-to="Naveed Khan, Gary Gregory">Clear descriptor caches when bean introspectors change (#435).</action>
-      <action type="fix" dev="ggregory" due-to="Naveed Khan, Gary Gregory">Reject out-of-range sql date/time fields in DateTimeConverter.toDate (#436).</action>
+      <action type="fix" dev="ggregory" due-to="Naveed Khan, Gary Gregory">Reject out-of-range SQL date/time fields in DateTimeConverter.toDate (#436).</action>
+      <action type="fix" dev="ggregory" due-to="Naveed Khan, Gary Gregory">Skip static methods in FluentPropertyBeanIntrospector and mapped lookup (#437).</action>
       <!-- ADD -->
       <action type="add" dev="ggregory" due-to="Gary Gregory">Use varargs for org.apache.commons.beanutils2.MethodUtils.getAccessibleMethod(Class, String, Class...), and remove last argument variants using Class[] and Class.</action>
       <action type="add" dev="ggregory" due-to="SethFalco, Melloware, Gary Gregory">Add Instant converter and improve enum converter #49.</action>


(commons-beanutils) 02/03: Skip static methods in FluentPropertyBeanIntrospector and mapped lookup

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git

commit 3e1923bad3c249569817634d261b22d37451f45f
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Aug 23 09:46:58 2026 -0400

    Skip static methods in FluentPropertyBeanIntrospector and mapped lookup
    
    Add missing unit tests that could have been in PR #437.
    
    The tests explicitly cover the mixed static/instance accessor cases that
    were missing:
    
    Mapped property with a static getter but instance setter: read method is
    ignored, write method is used.
    
    Mapped property with an instance getter but static setter: read method
    is used, write method is ignored.
    
    Fluent introspector with a static and an instance setter for the same
    property name: the instance setter is chosen and the property remains
    writable.
---
 .../FluentPropertyBeanIntrospectorTest.java        | 32 ++++++++++++++++
 .../commons/beanutils2/MappedPropertyTest.java     | 44 +++++++++++++++++-----
 .../commons/beanutils2/MappedPropertyTestBean.java | 22 +++++++++--
 3 files changed, 85 insertions(+), 13 deletions(-)

diff --git a/src/test/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospectorTest.java b/src/test/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospectorTest.java
index 1cc6ae13..fac98bcd 100644
--- a/src/test/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospectorTest.java
+++ b/src/test/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospectorTest.java
@@ -46,6 +46,23 @@ class FluentPropertyBeanIntrospectorTest {
         }
     }
 
+    public static final class MixedSetterBean {
+        public static void setMixed(final Integer v) {
+            // static setter should be ignored
+        }
+
+        private String value;
+
+        public String getMixed() {
+            return value;
+        }
+
+        public MixedSetterBean setMixed(final String v) {
+            this.value = v;
+            return this;
+        }
+    }
+
     public static final class StaticSetterBean {
         private static String staticValue;
 
@@ -138,6 +155,21 @@ class FluentPropertyBeanIntrospectorTest {
         assertNull(props.get("uRI"), "Should not find mis-capitalized property");
     }
 
+    /**
+     * Tests that an instance setter is used when a static setter with the same name exists.
+     */
+    @Test
+    void testIntrospectionMixedStaticInstanceSetter() throws Exception {
+        final PropertyUtilsBean pu = new PropertyUtilsBean();
+        pu.addBeanIntrospector(new FluentPropertyBeanIntrospector());
+        final Map<String, PropertyDescriptor> props = createDescriptorMap(pu.getPropertyDescriptors(MixedSetterBean.class));
+        final PropertyDescriptor pd = fetchDescriptor(props, "mixed");
+        assertNotNull(pd.getReadMethod(), "No read method for mixed");
+        assertNotNull(pd.getWriteMethod(), "Instance setter should be found");
+        assertFalse(java.lang.reflect.Modifier.isStatic(pd.getWriteMethod().getModifiers()), "Write method should not be static");
+        assertTrue(pu.isWriteable(new MixedSetterBean(), "mixed"), "mixed should be writeable");
+    }
+
     /**
      * Tests that static methods are not treated as write methods.
      */
diff --git a/src/test/java/org/apache/commons/beanutils2/MappedPropertyTest.java b/src/test/java/org/apache/commons/beanutils2/MappedPropertyTest.java
index f6d898bc..f5a561c5 100644
--- a/src/test/java/org/apache/commons/beanutils2/MappedPropertyTest.java
+++ b/src/test/java/org/apache/commons/beanutils2/MappedPropertyTest.java
@@ -111,6 +111,18 @@ class MappedPropertyTest {
         assertNotNull(desc.getMappedWriteMethod(), "Setter is missing");
     }
 
+    /**
+     * Test instance getter is used but static setter is ignored
+     */
+    @Test
+    void testInstanceGetterStaticSetter() throws Exception {
+        final String property = "instanceGetterStaticSetter";
+        final Class<?> clazz = MappedPropertyTestBean.class;
+        final MappedPropertyDescriptor desc = new MappedPropertyDescriptor(property, clazz);
+        assertNotNull(desc.getMappedReadMethod(), "Instance getter should be found");
+        assertNull(desc.getMappedWriteMethod(), "Static setter should be ignored");
+    }
+
     /**
      * Test Interface with mapped property
      */
@@ -225,16 +237,6 @@ class MappedPropertyTest {
         assertThrows(IntrospectionException.class, () -> new MappedPropertyDescriptor(property, clazz));
     }
 
-    /**
-     * Test static mapped accessors are ignored
-     */
-    @Test
-    void testStaticMapped() {
-        final String property = "staticMapped";
-        final Class<?> clazz = MappedPropertyTestBean.class;
-        assertThrows(IntrospectionException.class, () -> new MappedPropertyDescriptor(property, clazz));
-    }
-
     /**
      * Test 'protected' method in parent
      */
@@ -256,4 +258,26 @@ class MappedPropertyTest {
         assertNotNull(desc.getMappedReadMethod(), "Getter is missing");
         assertNotNull(desc.getMappedWriteMethod(), "Setter is missing");
     }
+
+    /**
+     * Test static getter is ignored but instance setter is used
+     */
+    @Test
+    void testStaticGetterInstanceSetter() throws Exception {
+        final String property = "staticGetterInstanceSetter";
+        final Class<?> clazz = MappedPropertyTestBean.class;
+        final MappedPropertyDescriptor desc = new MappedPropertyDescriptor(property, clazz);
+        assertNull(desc.getMappedReadMethod(), "Static getter should be ignored");
+        assertNotNull(desc.getMappedWriteMethod(), "Instance setter should be found");
+    }
+
+    /**
+     * Test static mapped accessors are ignored
+     */
+    @Test
+    void testStaticMapped() {
+        final String property = "staticMapped";
+        final Class<?> clazz = MappedPropertyTestBean.class;
+        assertThrows(IntrospectionException.class, () -> new MappedPropertyDescriptor(property, clazz));
+    }
 }
diff --git a/src/test/java/org/apache/commons/beanutils2/MappedPropertyTestBean.java b/src/test/java/org/apache/commons/beanutils2/MappedPropertyTestBean.java
index 69d7f56c..a28cf111 100644
--- a/src/test/java/org/apache/commons/beanutils2/MappedPropertyTestBean.java
+++ b/src/test/java/org/apache/commons/beanutils2/MappedPropertyTestBean.java
@@ -26,21 +26,33 @@ import java.util.Map;
 
 public class MappedPropertyTestBean {
 
-    private final Map<Object, Object> map = new HashMap<>();
-    private final Map<Object, Object> myMap = new HashMap<>();
-
+    public static String getStaticGetterInstanceSetter(final String key) {
+        return null;
+    }
     public static String getStaticMapped(final String key) {
         return "static-" + key;
     }
 
+    public static void setInstanceGetterStaticSetter(final String key, final String value) {
+        // static setter should be ignored
+    }
+
     public static void setStaticMapped(final String key, final String value) {
         // empty
     }
 
+    private final Map<Object, Object> map = new HashMap<>();
+
+    private final Map<Object, Object> myMap = new HashMap<>();
+
     public Long getDifferentTypes(final String key) {
         return Long.valueOf(((Number) map.get(key)).longValue());
     }
 
+    public String getInstanceGetterStaticSetter(final String key) {
+        return (String) map.get(key);
+    }
+
     public String getInvalidGetter(final String key, final String other) {
         return (String) map.get(key);
     }
@@ -104,4 +116,8 @@ public class MappedPropertyTestBean {
         map.put(key, value);
     }
 
+    public void setStaticGetterInstanceSetter(final String key, final String value) {
+        map.put(key, value);
+    }
+
 }