Support list/map/array constructor data binding
See gh-32426
This commit is contained in:
@@ -18,11 +18,11 @@ package org.springframework.validation;
|
||||
|
||||
import java.beans.PropertyEditor;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -30,6 +30,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -49,6 +51,7 @@ import org.springframework.beans.PropertyValues;
|
||||
import org.springframework.beans.SimpleTypeConverter;
|
||||
import org.springframework.beans.TypeConverter;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -948,11 +951,24 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
|
||||
String paramPath = nestedPath + lookupName;
|
||||
Class<?> paramType = paramTypes[i];
|
||||
ResolvableType resolvableType = ResolvableType.forMethodParameter(param);
|
||||
|
||||
Object value = valueResolver.resolveValue(paramPath, paramType);
|
||||
|
||||
if (value == null) {
|
||||
if (List.class.isAssignableFrom(paramType)) {
|
||||
value = createList(paramPath, paramType, resolvableType, valueResolver);
|
||||
}
|
||||
else if (Map.class.isAssignableFrom(paramType)) {
|
||||
value = createMap(paramPath, paramType, resolvableType, valueResolver);
|
||||
}
|
||||
else if (paramType.isArray()) {
|
||||
value = createArray(paramPath, resolvableType, valueResolver);
|
||||
}
|
||||
}
|
||||
|
||||
if (value == null && shouldConstructArgument(param) && hasValuesFor(paramPath, valueResolver)) {
|
||||
ResolvableType type = ResolvableType.forMethodParameter(param);
|
||||
args[i] = createObject(type, paramPath + ".", valueResolver);
|
||||
args[i] = createObject(resolvableType, paramPath + ".", valueResolver);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
@@ -1019,9 +1035,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
*/
|
||||
protected boolean shouldConstructArgument(MethodParameter param) {
|
||||
Class<?> type = param.nestedIfOptional().getNestedParameterType();
|
||||
return !(BeanUtils.isSimpleValueType(type) ||
|
||||
Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type) || type.isArray() ||
|
||||
type.getPackageName().startsWith("java."));
|
||||
return !BeanUtils.isSimpleValueType(type) && !type.getPackageName().startsWith("java.");
|
||||
}
|
||||
|
||||
private boolean hasValuesFor(String paramPath, ValueResolver resolver) {
|
||||
@@ -1033,6 +1047,82 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private <V> List<V> createList(
|
||||
String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
|
||||
|
||||
ResolvableType elementType = type.getNested(2);
|
||||
SortedSet<Integer> indexes = getIndexes(paramPath, valueResolver);
|
||||
if (indexes == null) {
|
||||
return null;
|
||||
}
|
||||
int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1 : 0);
|
||||
List<V> list = (List<V>) CollectionFactory.createCollection(paramType, size);
|
||||
indexes.forEach(i -> list.add(null));
|
||||
for (int index : indexes) {
|
||||
list.set(index, (V) createObject(elementType, paramPath + "[" + index + "].", valueResolver));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private <V> Map<String, V> createMap(
|
||||
String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
|
||||
|
||||
ResolvableType elementType = type.getNested(2);
|
||||
Map<String, V> map = null;
|
||||
for (String name : valueResolver.getNames()) {
|
||||
if (!name.startsWith(paramPath + "[")) {
|
||||
continue;
|
||||
}
|
||||
int startIdx = paramPath.length() + 1;
|
||||
int endIdx = name.indexOf(']', startIdx);
|
||||
String nestedPath = name.substring(0, endIdx + 2);
|
||||
boolean quoted = (endIdx - startIdx > 2 && name.charAt(startIdx) == '\'' && name.charAt(endIdx - 1) == '\'');
|
||||
String key = (quoted ? name.substring(startIdx + 1, endIdx - 1) : name.substring(startIdx, endIdx));
|
||||
if (map == null) {
|
||||
map = CollectionFactory.createMap(paramType, 16);
|
||||
}
|
||||
if (!map.containsKey(key)) {
|
||||
map.put(key, (V) createObject(elementType, nestedPath, valueResolver));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private <V> V[] createArray(String paramPath, ResolvableType type, ValueResolver valueResolver) {
|
||||
ResolvableType elementType = type.getNested(2);
|
||||
SortedSet<Integer> indexes = getIndexes(paramPath, valueResolver);
|
||||
if (indexes == null) {
|
||||
return null;
|
||||
}
|
||||
int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1: 0);
|
||||
V[] array = (V[]) Array.newInstance(elementType.resolve(), size);
|
||||
for (int index : indexes) {
|
||||
array[index] = (V) createObject(elementType, paramPath + "[" + index + "].", valueResolver);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static SortedSet<Integer> getIndexes(String paramPath, ValueResolver valueResolver) {
|
||||
SortedSet<Integer> 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);
|
||||
int index = Integer.parseInt(rawIndex);
|
||||
indexes = (indexes != null ? indexes : new TreeSet<>());
|
||||
indexes.add(index);
|
||||
}
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
|
||||
private void validateConstructorArgument(
|
||||
Class<?> constructorClass, String nestedPath, String name, @Nullable Object value) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user