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
*/