From 4591a67641c87f8cccbce19bafac6cccf8727ce3 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 10 Feb 2025 17:41:05 +0000 Subject: [PATCH] Handle [] leniently in constructor binding See gh-34305 --- .../validation/DataBinder.java | 39 +++++++++++++------ .../validation/DataBinderConstructTests.java | 11 ++++++ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index efce2b9d5f3..2d00655f4cd 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -142,6 +142,10 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { */ protected static final Log logger = LogFactory.getLog(DataBinder.class); + /** Internal constant for constructor binding via "[]". */ + private static final int NO_INDEX = -1; + + @Nullable private Object target; @@ -1056,15 +1060,17 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { return null; } - int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1 : 0); + int lastIndex = Math.max(indexes.last(), 0); + int size = (lastIndex < this.autoGrowCollectionLimit ? lastIndex + 1 : 0); List list = (List) CollectionFactory.createCollection(paramType, size); for (int i = 0; i < size; i++) { list.add(null); } for (int index : indexes) { - String indexedPath = paramPath + "[" + index + "]"; - list.set(index, createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver)); + String indexedPath = paramPath + "[" + (index != NO_INDEX ? index : "") + "]"; + list.set(Math.max(index, 0), + createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver)); } return list; @@ -1108,12 +1114,14 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { return null; } - int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1: 0); + int lastIndex = Math.max(indexes.last(), 0); + int size = (lastIndex < this.autoGrowCollectionLimit ? lastIndex + 1: 0); V[] array = (V[]) Array.newInstance(elementType.resolve(), size); for (int index : indexes) { - String indexedPath = paramPath + "[" + index + "]"; - array[index] = createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver); + String indexedPath = paramPath + "[" + (index != NO_INDEX ? index : "") + "]"; + array[Math.max(index, 0)] = + createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver); } return array; @@ -1124,13 +1132,20 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { SortedSet indexes = null; for (String name : valueResolver.getNames()) { if (name.startsWith(paramPath + "[")) { - int endIndex = name.indexOf(']', paramPath.length() + 2); - String rawIndex = name.substring(paramPath.length() + 1, endIndex); - if (StringUtils.hasLength(rawIndex)) { - int index = Integer.parseInt(rawIndex); - indexes = (indexes != null ? indexes : new TreeSet<>()); - indexes.add(index); + int index; + if (paramPath.length() + 2 == name.length()) { + if (!name.endsWith("[]")) { + continue; + } + index = NO_INDEX; + } + else { + int endIndex = name.indexOf(']', paramPath.length() + 2); + String indexValue = name.substring(paramPath.length() + 1, endIndex); + index = Integer.parseInt(indexValue); } + indexes = (indexes != null ? indexes : new TreeSet<>()); + indexes.add(index); } } return indexes; diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java index 7b0fdb72ee5..8bae5d4c4d9 100644 --- a/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java @@ -188,6 +188,17 @@ class DataBinderConstructTests { assertThat(target.integerList()).containsExactly(1, 2); } + @Test + void simpleListBindingEmptyBrackets() { + MapValueResolver valueResolver = new MapValueResolver(Map.of("integerList[]", "1")); + + DataBinder binder = initDataBinder(IntegerListRecord.class); + binder.construct(valueResolver); + + IntegerListRecord target = getTarget(binder); + assertThat(target.integerList()).containsExactly(1); + } + @Test void simpleMapBinding() { MapValueResolver valueResolver = new MapValueResolver(Map.of("integerMap[a]", "1", "integerMap[b]", "2"));