From 0a759a00ee8a2aeca09c39753fe778235508f5f3 Mon Sep 17 00:00:00 2001 From: Naveed Khan Date: Tue, 11 Aug 2026 11:42:44 +0530 Subject: [PATCH 1/2] record element type of first element in LazyDynaList.transform --- .../commons/beanutils2/LazyDynaList.java | 13 ++++++++++- .../commons/beanutils2/LazyDynaListTest.java | 23 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java b/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java index 28eb53dba..1f2967816 100644 --- a/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java +++ b/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java @@ -659,7 +659,18 @@ private DynaBean transform(final Object element) { // 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/beanutils2/LazyDynaListTest.java b/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java index 8ed668379..7d974a1a0 100644 --- a/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java +++ b/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java @@ -374,6 +374,29 @@ void testNullType() { 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. + */ + @Test + 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[] source = new TreeMap[2]; + source[0] = new TreeMap<>(); + source[0].put("key0", "val0"); + source[1] = new TreeMap<>(); + source[1].put("key1", "val1"); + 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 Pojo Create */ From 4b1c34056d58633fac586af7ae64fa7ad1c41915 Mon Sep 17 00:00:00 2001 From: Naveed Khan Date: Mon, 17 Aug 2026 02:19:45 +0530 Subject: [PATCH 2/2] add tests for untyped list first-element paths --- .../commons/beanutils2/LazyDynaListTest.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java b/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java index 7d974a1a0..52f1f67fc 100644 --- a/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java +++ b/src/test/java/org/apache/commons/beanutils2/LazyDynaListTest.java @@ -397,6 +397,115 @@ void testFirstElementSetsElementType() { 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[]. + */ + @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 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 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 collection = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + final TreeMap 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 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 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 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"); + } + /** * Test Pojo Create */