Skip to content

Commit 03984ba

Browse files
committed
Merge branch '6.2.x'
2 parents 49bd833 + 462c2bd commit 03984ba

File tree

6 files changed

+133
-205
lines changed

6 files changed

+133
-205
lines changed

Diff for: spring-context/src/main/java/org/springframework/validation/DataBinder.java

+59-19
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ else if (Map.class.isAssignableFrom(paramType)) {
937937
value = createMap(paramPath, paramType, resolvableType, valueResolver);
938938
}
939939
else if (paramType.isArray()) {
940-
value = createArray(paramPath, resolvableType, valueResolver);
940+
value = createArray(paramPath, paramType, resolvableType, valueResolver);
941941
}
942942
}
943943

@@ -954,11 +954,9 @@ else if (paramType.isArray()) {
954954
}
955955
}
956956
catch (TypeMismatchException ex) {
957-
ex.initPropertyName(paramPath);
958957
args[i] = null;
959958
failedParamNames.add(paramPath);
960-
getBindingResult().recordFieldValue(paramPath, paramType, value);
961-
getBindingErrorProcessor().processPropertyAccessException(ex, getBindingResult());
959+
handleTypeMismatchException(ex, paramPath, paramType, value);
962960
}
963961
}
964962
}
@@ -1021,27 +1019,29 @@ private boolean hasValuesFor(String paramPath, ValueResolver resolver) {
10211019
return false;
10221020
}
10231021

1024-
@SuppressWarnings("unchecked")
1025-
private <V> @Nullable List<V> createList(
1022+
private @Nullable List<?> createList(
10261023
String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
10271024

10281025
ResolvableType elementType = type.getNested(2);
10291026
SortedSet<Integer> indexes = getIndexes(paramPath, valueResolver);
10301027
if (indexes == null) {
10311028
return null;
10321029
}
1030+
10331031
int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1 : 0);
1034-
List<V> list = (List<V>) CollectionFactory.createCollection(paramType, size);
1032+
List<?> list = (List<?>) CollectionFactory.createCollection(paramType, size);
10351033
for (int i = 0; i < size; i++) {
10361034
list.add(null);
10371035
}
1036+
10381037
for (int index : indexes) {
1039-
list.set(index, (V) createObject(elementType, paramPath + "[" + index + "].", valueResolver));
1038+
String indexedPath = paramPath + "[" + index + "]";
1039+
list.set(index, createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver));
10401040
}
1041+
10411042
return list;
10421043
}
10431044

1044-
@SuppressWarnings("unchecked")
10451045
private <V> @Nullable Map<String, V> createMap(
10461046
String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
10471047

@@ -1051,50 +1051,90 @@ private boolean hasValuesFor(String paramPath, ValueResolver resolver) {
10511051
if (!name.startsWith(paramPath + "[")) {
10521052
continue;
10531053
}
1054+
10541055
int startIdx = paramPath.length() + 1;
10551056
int endIdx = name.indexOf(']', startIdx);
1056-
String nestedPath = ((name.length() > endIdx + 1) ? name.substring(0, endIdx + 2) : "");
10571057
boolean quoted = (endIdx - startIdx > 2 && name.charAt(startIdx) == '\'' && name.charAt(endIdx - 1) == '\'');
10581058
String key = (quoted ? name.substring(startIdx + 1, endIdx - 1) : name.substring(startIdx, endIdx));
1059+
10591060
if (map == null) {
10601061
map = CollectionFactory.createMap(paramType, 16);
10611062
}
1062-
if (!map.containsKey(key)) {
1063-
map.put(key, (V) createObject(elementType, nestedPath, valueResolver));
1064-
}
1063+
1064+
String indexedPath = name.substring(0, endIdx + 1);
1065+
map.put(key, createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver));
10651066
}
1067+
10661068
return map;
10671069
}
10681070

10691071
@SuppressWarnings("unchecked")
1070-
private <V> @Nullable V @Nullable [] createArray(String paramPath, ResolvableType type, ValueResolver valueResolver) {
1072+
private <V> @Nullable V @Nullable [] createArray(
1073+
String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
1074+
10711075
ResolvableType elementType = type.getNested(2);
10721076
SortedSet<Integer> indexes = getIndexes(paramPath, valueResolver);
10731077
if (indexes == null) {
10741078
return null;
10751079
}
1080+
10761081
int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1: 0);
10771082
@Nullable V[] array = (V[]) Array.newInstance(elementType.resolve(), size);
1083+
10781084
for (int index : indexes) {
1079-
array[index] = (V) createObject(elementType, paramPath + "[" + index + "].", valueResolver);
1085+
String indexedPath = paramPath + "[" + index + "]";
1086+
array[index] = createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver);
10801087
}
1088+
10811089
return array;
10821090
}
10831091

10841092
private static @Nullable SortedSet<Integer> getIndexes(String paramPath, ValueResolver valueResolver) {
10851093
SortedSet<Integer> indexes = null;
10861094
for (String name : valueResolver.getNames()) {
10871095
if (name.startsWith(paramPath + "[")) {
1088-
int endIndex = name.indexOf(']', paramPath.length() + 1);
1096+
int endIndex = name.indexOf(']', paramPath.length() + 2);
10891097
String rawIndex = name.substring(paramPath.length() + 1, endIndex);
1090-
int index = Integer.parseInt(rawIndex);
1091-
indexes = (indexes != null ? indexes : new TreeSet<>());
1092-
indexes.add(index);
1098+
if (StringUtils.hasLength(rawIndex)) {
1099+
int index = Integer.parseInt(rawIndex);
1100+
indexes = (indexes != null ? indexes : new TreeSet<>());
1101+
indexes.add(index);
1102+
}
10931103
}
10941104
}
10951105
return indexes;
10961106
}
10971107

1108+
@SuppressWarnings("unchecked")
1109+
private <V> @Nullable V createIndexedValue(
1110+
String paramPath, Class<?> paramType, ResolvableType elementType,
1111+
String indexedPath, ValueResolver valueResolver) {
1112+
1113+
Object value = null;
1114+
Class<?> elementClass = elementType.resolve(Object.class);
1115+
Object rawValue = valueResolver.resolveValue(indexedPath, elementClass);
1116+
if (rawValue != null) {
1117+
try {
1118+
value = convertIfNecessary(rawValue, elementClass);
1119+
}
1120+
catch (TypeMismatchException ex) {
1121+
handleTypeMismatchException(ex, paramPath, paramType, rawValue);
1122+
}
1123+
}
1124+
else {
1125+
value = createObject(elementType, indexedPath + ".", valueResolver);
1126+
}
1127+
return (V) value;
1128+
}
1129+
1130+
private void handleTypeMismatchException(
1131+
TypeMismatchException ex, String paramPath, Class<?> paramType, @Nullable Object value) {
1132+
1133+
ex.initPropertyName(paramPath);
1134+
getBindingResult().recordFieldValue(paramPath, paramType, value);
1135+
getBindingErrorProcessor().processPropertyAccessException(ex, getBindingResult());
1136+
}
1137+
10981138
private void validateConstructorArgument(
10991139
Class<?> constructorClass, String nestedPath, @Nullable String name, @Nullable Object value) {
11001140

Diff for: spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java

+65-19
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,17 @@ void dataClassBindingWithConversionError() {
103103
}
104104

105105
@Test
106-
void listBinding() {
106+
void dataClassWithListBinding() {
107107
MapValueResolver valueResolver = new MapValueResolver(Map.of(
108108
"dataClassList[0].param1", "value1", "dataClassList[0].param2", "true",
109109
"dataClassList[1].param1", "value2", "dataClassList[1].param2", "true",
110110
"dataClassList[2].param1", "value3", "dataClassList[2].param2", "true"));
111111

112-
DataBinder binder = initDataBinder(ListDataClass.class);
112+
DataBinder binder = initDataBinder(DataClassListRecord.class);
113113
binder.construct(valueResolver);
114114

115-
ListDataClass dataClass = getTarget(binder);
116-
List<DataClass> list = dataClass.dataClassList();
115+
DataClassListRecord target = getTarget(binder);
116+
List<DataClass> list = target.dataClassList();
117117

118118
assertThat(list).hasSize(3);
119119
assertThat(list.get(0).param1()).isEqualTo("value1");
@@ -122,35 +122,35 @@ void listBinding() {
122122
}
123123

124124
@Test // gh-34145
125-
void listBindingWithNonconsecutiveIndices() {
125+
void dataClassWithListBindingWithNonconsecutiveIndices() {
126126
MapValueResolver valueResolver = new MapValueResolver(Map.of(
127127
"dataClassList[0].param1", "value1", "dataClassList[0].param2", "true",
128128
"dataClassList[1].param1", "value2", "dataClassList[1].param2", "true",
129129
"dataClassList[3].param1", "value3", "dataClassList[3].param2", "true"));
130130

131-
DataBinder binder = initDataBinder(ListDataClass.class);
131+
DataBinder binder = initDataBinder(DataClassListRecord.class);
132132
binder.construct(valueResolver);
133133

134-
ListDataClass dataClass = getTarget(binder);
135-
List<DataClass> list = dataClass.dataClassList();
134+
DataClassListRecord target = getTarget(binder);
135+
List<DataClass> list = target.dataClassList();
136136

137137
assertThat(list.get(0).param1()).isEqualTo("value1");
138138
assertThat(list.get(1).param1()).isEqualTo("value2");
139139
assertThat(list.get(3).param1()).isEqualTo("value3");
140140
}
141141

142142
@Test
143-
void mapBinding() {
143+
void dataClassWithMapBinding() {
144144
MapValueResolver valueResolver = new MapValueResolver(Map.of(
145145
"dataClassMap[a].param1", "value1", "dataClassMap[a].param2", "true",
146146
"dataClassMap[b].param1", "value2", "dataClassMap[b].param2", "true",
147147
"dataClassMap['c'].param1", "value3", "dataClassMap['c'].param2", "true"));
148148

149-
DataBinder binder = initDataBinder(MapDataClass.class);
149+
DataBinder binder = initDataBinder(DataClassMapRecord.class);
150150
binder.construct(valueResolver);
151151

152-
MapDataClass dataClass = getTarget(binder);
153-
Map<String, DataClass> map = dataClass.dataClassMap();
152+
DataClassMapRecord target = getTarget(binder);
153+
Map<String, DataClass> map = target.dataClassMap();
154154

155155
assertThat(map).hasSize(3);
156156
assertThat(map.get("a").param1()).isEqualTo("value1");
@@ -159,24 +159,58 @@ void mapBinding() {
159159
}
160160

161161
@Test
162-
void arrayBinding() {
162+
void dataClassWithArrayBinding() {
163163
MapValueResolver valueResolver = new MapValueResolver(Map.of(
164164
"dataClassArray[0].param1", "value1", "dataClassArray[0].param2", "true",
165165
"dataClassArray[1].param1", "value2", "dataClassArray[1].param2", "true",
166166
"dataClassArray[2].param1", "value3", "dataClassArray[2].param2", "true"));
167167

168-
DataBinder binder = initDataBinder(ArrayDataClass.class);
168+
DataBinder binder = initDataBinder(DataClassArrayRecord.class);
169169
binder.construct(valueResolver);
170170

171-
ArrayDataClass dataClass = getTarget(binder);
172-
DataClass[] array = dataClass.dataClassArray();
171+
DataClassArrayRecord target = getTarget(binder);
172+
DataClass[] array = target.dataClassArray();
173173

174174
assertThat(array).hasSize(3);
175175
assertThat(array[0].param1()).isEqualTo("value1");
176176
assertThat(array[1].param1()).isEqualTo("value2");
177177
assertThat(array[2].param1()).isEqualTo("value3");
178178
}
179179

180+
@Test
181+
void simpleListBinding() {
182+
MapValueResolver valueResolver = new MapValueResolver(Map.of("integerList[0]", "1", "integerList[1]", "2"));
183+
184+
DataBinder binder = initDataBinder(IntegerListRecord.class);
185+
binder.construct(valueResolver);
186+
187+
IntegerListRecord target = getTarget(binder);
188+
assertThat(target.integerList()).containsExactly(1, 2);
189+
}
190+
191+
@Test
192+
void simpleMapBinding() {
193+
MapValueResolver valueResolver = new MapValueResolver(Map.of("integerMap[a]", "1", "integerMap[b]", "2"));
194+
195+
DataBinder binder = initDataBinder(IntegerMapRecord.class);
196+
binder.construct(valueResolver);
197+
198+
IntegerMapRecord target = getTarget(binder);
199+
assertThat(target.integerMap()).hasSize(2).containsEntry("a", 1).containsEntry("b", 2);
200+
}
201+
202+
@Test
203+
void simpleArrayBinding() {
204+
MapValueResolver valueResolver = new MapValueResolver(Map.of("integerArray[0]", "1", "integerArray[1]", "2"));
205+
206+
DataBinder binder = initDataBinder(IntegerArrayRecord.class);
207+
binder.construct(valueResolver);
208+
209+
IntegerArrayRecord target = getTarget(binder);
210+
assertThat(target.integerArray()).containsExactly(1, 2);
211+
}
212+
213+
180214
@SuppressWarnings("SameParameterValue")
181215
private static DataBinder initDataBinder(Class<?> targetType) {
182216
DataBinder binder = new DataBinder(null);
@@ -246,15 +280,27 @@ public String param1() {
246280
}
247281

248282

249-
private record ListDataClass(List<DataClass> dataClassList) {
283+
private record DataClassListRecord(List<DataClass> dataClassList) {
284+
}
285+
286+
287+
private record DataClassMapRecord(Map<String, DataClass> dataClassMap) {
288+
}
289+
290+
291+
private record DataClassArrayRecord(DataClass[] dataClassArray) {
292+
}
293+
294+
295+
private record IntegerListRecord(List<Integer> integerList) {
250296
}
251297

252298

253-
private record MapDataClass(Map<String, DataClass> dataClassMap) {
299+
private record IntegerMapRecord(Map<String, Integer> integerMap) {
254300
}
255301

256302

257-
private record ArrayDataClass(DataClass[] dataClassArray) {
303+
private record IntegerArrayRecord(Integer[] integerArray) {
258304
}
259305

260306

0 commit comments

Comments
 (0)