Support for MultiValueMap elements in bean property paths

Closes gh-26297
This commit is contained in:
Juergen Hoeller
2023-07-12 16:23:39 +02:00
parent 490ff0af5e
commit c1932dd307
4 changed files with 129 additions and 26 deletions

View File

@@ -319,14 +319,14 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
} }
else if (propValue instanceof List list) { else if (propValue instanceof List list) {
Class<?> requiredType = ph.getCollectionType(tokens.keys.length); TypeDescriptor requiredType = ph.getCollectionType(tokens.keys.length);
int index = Integer.parseInt(lastKey); int index = Integer.parseInt(lastKey);
Object oldValue = null; Object oldValue = null;
if (isExtractOldValueForEditor() && index < list.size()) { if (isExtractOldValueForEditor() && index < list.size()) {
oldValue = list.get(index); oldValue = list.get(index);
} }
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(), Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
requiredType, ph.nested(tokens.keys.length)); requiredType.getResolvableType().resolve(), requiredType);
int size = list.size(); int size = list.size();
if (index >= size && index < this.autoGrowCollectionLimit) { if (index >= size && index < this.autoGrowCollectionLimit) {
for (int i = size; i < index; i++) { for (int i = size; i < index; i++) {
@@ -354,12 +354,12 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
} }
else if (propValue instanceof Map map) { else if (propValue instanceof Map map) {
Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length); TypeDescriptor mapKeyType = ph.getMapKeyType(tokens.keys.length);
Class<?> mapValueType = ph.getMapValueType(tokens.keys.length); TypeDescriptor mapValueType = ph.getMapValueType(tokens.keys.length);
// IMPORTANT: Do not pass full property name in here - property editors // IMPORTANT: Do not pass full property name in here - property editors
// must not kick in for map keys but rather only for map values. // must not kick in for map keys but rather only for map values.
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType); Object convertedMapKey = convertIfNecessary(null, null, lastKey,
Object convertedMapKey = convertIfNecessary(null, null, lastKey, mapKeyType, typeDescriptor); mapKeyType.getResolvableType().resolve(), mapKeyType);
Object oldValue = null; Object oldValue = null;
if (isExtractOldValueForEditor()) { if (isExtractOldValueForEditor()) {
oldValue = map.get(convertedMapKey); oldValue = map.get(convertedMapKey);
@@ -367,7 +367,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
// Pass full property name and old value in here, since we want full // Pass full property name and old value in here, since we want full
// conversion ability for map values. // conversion ability for map values.
Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(), Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
mapValueType, ph.nested(tokens.keys.length)); mapValueType.getResolvableType().resolve(), mapValueType);
map.put(convertedMapKey, convertedMapValue); map.put(convertedMapKey, convertedMapValue);
} }
@@ -1041,19 +1041,16 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
public abstract ResolvableType getResolvableType(); public abstract ResolvableType getResolvableType();
@Nullable public TypeDescriptor getMapKeyType(int nestingLevel) {
public Class<?> getMapKeyType(int nestingLevel) { return TypeDescriptor.valueOf(getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(0));
return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(0);
} }
@Nullable public TypeDescriptor getMapValueType(int nestingLevel) {
public Class<?> getMapValueType(int nestingLevel) { return TypeDescriptor.valueOf(getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(1));
return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(1);
} }
@Nullable public TypeDescriptor getCollectionType(int nestingLevel) {
public Class<?> getCollectionType(int nestingLevel) { return TypeDescriptor.valueOf(getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric());
return getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric();
} }
@Nullable @Nullable

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -236,19 +236,36 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements
private final PropertyDescriptor pd; private final PropertyDescriptor pd;
private final TypeDescriptor typeDescriptor;
public BeanPropertyHandler(PropertyDescriptor pd) { public BeanPropertyHandler(PropertyDescriptor pd) {
super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null); super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
this.pd = pd; this.pd = pd;
} this.typeDescriptor = new TypeDescriptor(property(pd));
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forMethodReturnType(this.pd.getReadMethod());
} }
@Override @Override
public TypeDescriptor toTypeDescriptor() { public TypeDescriptor toTypeDescriptor() {
return new TypeDescriptor(property(this.pd)); return this.typeDescriptor;
}
@Override
public ResolvableType getResolvableType() {
return this.typeDescriptor.getResolvableType();
}
@Override
public TypeDescriptor getMapValueType(int nestingLevel) {
return new TypeDescriptor(
this.typeDescriptor.getResolvableType().getNested(nestingLevel).asMap().getGeneric(1),
null, this.typeDescriptor.getAnnotations());
}
@Override
public TypeDescriptor getCollectionType(int nestingLevel) {
return new TypeDescriptor(
this.typeDescriptor.getResolvableType().getNested(nestingLevel).asCollection().getGeneric(),
null, this.typeDescriptor.getAnnotations());
} }
@Override @Override

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -101,19 +101,34 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
private final Field field; private final Field field;
private final ResolvableType resolvableType;
public FieldPropertyHandler(Field field) { public FieldPropertyHandler(Field field) {
super(field.getType(), true, true); super(field.getType(), true, true);
this.field = field; this.field = field;
this.resolvableType = ResolvableType.forField(this.field);
} }
@Override @Override
public TypeDescriptor toTypeDescriptor() { public TypeDescriptor toTypeDescriptor() {
return new TypeDescriptor(this.field); return new TypeDescriptor(this.resolvableType, this.field.getType(), this.field.getAnnotations());
} }
@Override @Override
public ResolvableType getResolvableType() { public ResolvableType getResolvableType() {
return ResolvableType.forField(this.field); return this.resolvableType;
}
@Override
public TypeDescriptor getMapValueType(int nestingLevel) {
return new TypeDescriptor(this.resolvableType.getNested(nestingLevel).asMap().getGeneric(1),
null, this.field.getAnnotations());
}
@Override
public TypeDescriptor getCollectionType(int nestingLevel) {
return new TypeDescriptor(this.resolvableType.getNested(nestingLevel).asCollection().getGeneric(),
null, this.field.getAnnotations());
} }
@Override @Override

View File

@@ -36,6 +36,8 @@ import org.springframework.beans.testfixture.beans.GenericIntegerBean;
import org.springframework.beans.testfixture.beans.GenericSetOfIntegerBean; import org.springframework.beans.testfixture.beans.GenericSetOfIntegerBean;
import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.io.UrlResource; import org.springframework.core.io.UrlResource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -429,6 +431,18 @@ class BeanWrapperGenericsTests {
assertThat(holder.getGenericIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10)); assertThat(holder.getGenericIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
} }
@Test
void testComplexGenericIndexedMapEntryWithPlainValue() {
String inputValue = "10";
ComplexMapHolder holder = new ComplexMapHolder();
BeanWrapper bw = new BeanWrapperImpl(holder);
bw.setPropertyValue("genericIndexedMap[1]", inputValue);
assertThat(holder.getGenericIndexedMap().keySet().iterator().next()).isEqualTo(1);
assertThat(holder.getGenericIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
}
@Test @Test
void testComplexDerivedIndexedMapEntry() { void testComplexDerivedIndexedMapEntry() {
List<String> inputValue = new ArrayList<>(); List<String> inputValue = new ArrayList<>();
@@ -455,6 +469,56 @@ class BeanWrapperGenericsTests {
assertThat(holder.getDerivedIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10)); assertThat(holder.getDerivedIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
} }
@Test
void testComplexDerivedIndexedMapEntryWithPlainValue() {
String inputValue = "10";
ComplexMapHolder holder = new ComplexMapHolder();
BeanWrapper bw = new BeanWrapperImpl(holder);
bw.setPropertyValue("derivedIndexedMap[1]", inputValue);
assertThat(holder.getDerivedIndexedMap().keySet().iterator().next()).isEqualTo(1);
assertThat(holder.getDerivedIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
}
@Test
void testComplexMultiValueMapEntry() {
List<String> inputValue = new ArrayList<>();
inputValue.add("10");
ComplexMapHolder holder = new ComplexMapHolder();
BeanWrapper bw = new BeanWrapperImpl(holder);
bw.setPropertyValue("multiValueMap[1]", inputValue);
assertThat(holder.getMultiValueMap().keySet().iterator().next()).isEqualTo(1);
assertThat(holder.getMultiValueMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
}
@Test
void testComplexMultiValueMapEntryWithCollectionConversion() {
Set<String> inputValue = new HashSet<>();
inputValue.add("10");
ComplexMapHolder holder = new ComplexMapHolder();
BeanWrapper bw = new BeanWrapperImpl(holder);
bw.setPropertyValue("multiValueMap[1]", inputValue);
assertThat(holder.getMultiValueMap().keySet().iterator().next()).isEqualTo(1);
assertThat(holder.getMultiValueMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
}
@Test
void testComplexMultiValueMapEntryWithPlainValue() {
String inputValue = "10";
ComplexMapHolder holder = new ComplexMapHolder();
BeanWrapper bw = new BeanWrapperImpl(holder);
bw.setPropertyValue("multiValueMap[1]", inputValue);
assertThat(holder.getMultiValueMap().keySet().iterator().next()).isEqualTo(1);
assertThat(holder.getMultiValueMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
}
@Test @Test
void testGenericallyTypedIntegerBean() { void testGenericallyTypedIntegerBean() {
GenericIntegerBean gb = new GenericIntegerBean(); GenericIntegerBean gb = new GenericIntegerBean();
@@ -585,6 +649,8 @@ class BeanWrapperGenericsTests {
private DerivedMap derivedIndexedMap = new DerivedMap(); private DerivedMap derivedIndexedMap = new DerivedMap();
private MultiValueMap<Integer, Long> multiValueMap = new LinkedMultiValueMap<>();
public void setGenericMap(Map<List<Integer>, List<Long>> genericMap) { public void setGenericMap(Map<List<Integer>, List<Long>> genericMap) {
this.genericMap = genericMap; this.genericMap = genericMap;
} }
@@ -608,6 +674,14 @@ class BeanWrapperGenericsTests {
public DerivedMap getDerivedIndexedMap() { public DerivedMap getDerivedIndexedMap() {
return derivedIndexedMap; return derivedIndexedMap;
} }
public void setMultiValueMap(MultiValueMap<Integer, Long> multiValueMap) {
this.multiValueMap = multiValueMap;
}
public MultiValueMap<Integer, Long> getMultiValueMap() {
return multiValueMap;
}
} }