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

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


The following commit(s) were added to refs/heads/1.X by this push:
     new ade6518c Fix first-element type initialization in LazyDynaList
ade6518c is described below

commit ade6518c75c03bcb9944579cccb41d2199fab807
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Oct 30 08:04:57 2026 -0400

    Fix first-element type initialization in LazyDynaList
    
    Backport 0a253c99 (#434) to record element metadata when an untyped
    list receives its first element. This fixes array conversion and
    ensures subsequent elements have a consistent type.
    
    Enable testCollection, adapt six upstream regression tests for
    JUnit 3, and update the release notes.
---
 src/changes/changes.xml                            |   1 +
 .../org/apache/commons/beanutils/LazyDynaList.java |  13 ++-
 .../apache/commons/beanutils/LazyDynaListTest.java | 130 ++++++++++++++++++++-
 3 files changed, 142 insertions(+), 2 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 5d33361b..afb751e0 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -42,6 +42,7 @@
       <action dev="ggregory" type="fix" due-to="Naveed Khan, Gary 
Gregory">Make WrapDynaClass instance cache thread-safe (1.X) (#419).</action>
       <action dev="ggregory" type="fix" due-to="Naveed Khan, Gary Gregory">Fix 
failed conversion in DoubleLocaleConverter when input is non-Double Number 
(1.X) (#424).</action>
       <action dev="ggregory" type="fix" due-to="Naveed Khan, Gary 
Gregory">Resolve column name in ResultSetIterator.set (1.X) (#428).</action>
+      <action dev="ggregory" type="fix" due-to="Gary Gregory">Initialize 
LazyDynaList element metadata from the first element to fix array conversion 
and enforce consistent element types (backport of #434).</action>
       <!-- ADD -->
       <!-- UPDATE -->
       <action dev="ggregory" type="update" due-to="Gary Gregory">Bump 
org.apache.commons:commons-parent from 84 to 106.</action>
diff --git a/src/main/java/org/apache/commons/beanutils/LazyDynaList.java 
b/src/main/java/org/apache/commons/beanutils/LazyDynaList.java
index 8739567d..e50e71fe 100644
--- a/src/main/java/org/apache/commons/beanutils/LazyDynaList.java
+++ b/src/main/java/org/apache/commons/beanutils/LazyDynaList.java
@@ -694,7 +694,18 @@ public class LazyDynaList extends ArrayList<Object> {
 
         // Check the new element type, matches all the
         // other elements in the List
-        if (elementType != null && !newElementType.equals(elementType)) {
+        if (elementType == null) {
+            // The first element populated defines the element type (see class
+            // Javadoc). Record it so later elements are type-checked and
+            // toArray()/toDynaBeanArray() know the element type.
+            this.elementType = newElementType;
+            this.elementDynaBeanType = newDynaBeanType;
+            if (WrapDynaBean.class.isAssignableFrom(newDynaBeanType)) {
+                this.wrapDynaClass = (WrapDynaClass) dynaBean.getDynaClass();
+            } else {
+                this.elementDynaClass = dynaBean.getDynaClass();
+            }
+        } else if (!newElementType.equals(elementType)) {
             throw new IllegalArgumentException("Element Type "  + 
newElementType
                        + " doesn't match other elements " + elementType);
         }
diff --git a/src/test/java/org/apache/commons/beanutils/LazyDynaListTest.java 
b/src/test/java/org/apache/commons/beanutils/LazyDynaListTest.java
index 35dc5907..ab867c23 100644
--- a/src/test/java/org/apache/commons/beanutils/LazyDynaListTest.java
+++ b/src/test/java/org/apache/commons/beanutils/LazyDynaListTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.beanutils;
 
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
@@ -304,7 +306,7 @@ public class LazyDynaListTest extends TestCase {
     /**
      * Test Collection
      */
-    public void testCollection(final LazyDynaList list, final Class<?> 
testClass, final DynaClass testDynaClass, final Object wrongBean) {
+    public void testCollection() {
 
         final int size = 5;
         final List<Object> testList = new ArrayList<>(size);
@@ -407,6 +409,132 @@ public class LazyDynaListTest extends TestCase {
         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.
+     */
+    public void testFirstElementSetsElementType() {
+        final LazyDynaList lazyList = new LazyDynaList();
+        lazyList.add(new HashMap<>());
+        assertThrows(IllegalArgumentException.class, () -> lazyList.add("a 
POJO String"),
+                "Element of a different type must be rejected once the type is 
set");
+
+        final TreeMap<String, Object> first = new TreeMap<>();
+        first.put("key0", "val0");
+        final TreeMap<String, Object> second = new TreeMap<>();
+        second.put("key1", "val1");
+        final TreeMap<?, ?>[] source = {first, second};
+        final LazyDynaList mapList = new LazyDynaList(source);
+        final TreeMap<?, ?>[] array = (TreeMap[]) mapList.toArray();
+        assertEquals(2, array.length);
+        assertEquals("val0", array[0].get("key0"));
+        assertEquals("val1", array[1].get("key1"));
+    }
+
+    /**
+     * 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[].
+     */
+    public void testUntypedListPojoFirstElement() {
+        final LazyDynaList lazyList = new LazyDynaList();
+        final TestBean bean = new TestBean();
+        lazyList.add(bean);
+
+        final Object[] array = lazyList.toArray();
+        assertEquals("Not TestBean[]", TestBean.class, 
array.getClass().getComponentType());
+        assertSame("Wrong element", bean, array[0]);
+
+        final DynaBean[] dynaArray = lazyList.toDynaBeanArray();
+        assertEquals("Not WrapDynaBean[]", WrapDynaBean.class, 
dynaArray.getClass().getComponentType());
+        assertSame("Wrong wrapped instance", bean, ((WrapDynaBean) 
dynaArray[0]).getInstance());
+    }
+
+    /**
+     * Test that a DynaBean first element on an untyped List sets both the 
element type and the
+     * DynaBean type to the same DynaBean subclass.
+     */
+    public 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("Not BasicDynaBean[]", BasicDynaBean.class, 
array.getClass().getComponentType());
+        assertSame("Wrong element", bean, array[0]);
+
+        // elementDynaBeanType: toDynaBeanArray() returns the same subclass
+        final DynaBean[] dynaArray = lazyList.toDynaBeanArray();
+        assertEquals("Not BasicDynaBean[]", BasicDynaBean.class, 
dynaArray.getClass().getComponentType());
+        assertSame("Wrong element", bean, dynaArray[0]);
+    }
+
+    /**
+     * 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.
+     */
+    public 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("1. check size", 2, lazyList.size());
+        TreeMap<?, ?>[] mapArray = (TreeMap[]) lazyList.toArray();
+        assertEquals("2. Map error", "val0", mapArray[0].get("prop0"));
+        assertEquals("3. Map error", "val1", mapArray[1].get("prop1"));
+        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("5. check size", 4, indexedList.size());
+        mapArray = (TreeMap[]) indexedList.toArray();
+        assertEquals("6. check size", 4, mapArray.length);
+        assertEquals("7. Map error", "val0", mapArray[2].get("prop0"));
+        assertEquals("8. Map error", "val1", mapArray[3].get("prop1"));
+        assertThrows(IllegalArgumentException.class, () -> indexedList.add(new 
TestBean()), "9. wrong type accepted");
+    }
+
+    /**
+     * Test that get(index) grows an untyped List with the element type fixed 
by the first
+     * population.
+     */
+    public 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("DynaBean Not Created", grown);
+        assertEquals("Not LazyDynaMap", LazyDynaMap.class, grown.getClass());
+        assertEquals("Wrong Map", TreeMap.class, ((LazyDynaMap) 
grown).getMap().getClass());
+        assertEquals("check size", 3, lazyList.size());
+
+        final TreeMap<?, ?>[] mapArray = (TreeMap[]) lazyList.toArray();
+        assertEquals("check array size", 3, mapArray.length);
+        assertEquals("Map error", "val", mapArray[0].get("prop"));
+    }
+
+    /**
+     * Test toDynaBeanArray() type correctness for the untyped Map case.
+     */
+    public void testUntypedListToDynaBeanArray() {
+        final LazyDynaList lazyList = new LazyDynaList();
+        lazyList.add(new HashMap<>());
+
+        final DynaBean[] dynaArray = lazyList.toDynaBeanArray();
+        assertEquals("Not LazyDynaMap[]", LazyDynaMap.class, 
dynaArray.getClass().getComponentType());
+        assertEquals("check size", 1, dynaArray.length);
+        assertEquals("Wrong Map", HashMap.class, ((LazyDynaMap) 
dynaArray[0]).getMap().getClass());
+    }
+
     /**
      * Test Pojo Create
      */

Reply via email to