Support for MultiValueMap elements in bean property paths
Closes gh-26297
This commit is contained in:
parent
490ff0af5e
commit
c1932dd307
|
|
@ -319,14 +319,14 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
|||
}
|
||||
|
||||
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);
|
||||
Object oldValue = null;
|
||||
if (isExtractOldValueForEditor() && index < list.size()) {
|
||||
oldValue = list.get(index);
|
||||
}
|
||||
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
|
||||
requiredType, ph.nested(tokens.keys.length));
|
||||
requiredType.getResolvableType().resolve(), requiredType);
|
||||
int size = list.size();
|
||||
if (index >= size && index < this.autoGrowCollectionLimit) {
|
||||
for (int i = size; i < index; i++) {
|
||||
|
|
@ -354,12 +354,12 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
|||
}
|
||||
|
||||
else if (propValue instanceof Map map) {
|
||||
Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length);
|
||||
Class<?> mapValueType = ph.getMapValueType(tokens.keys.length);
|
||||
TypeDescriptor mapKeyType = ph.getMapKeyType(tokens.keys.length);
|
||||
TypeDescriptor mapValueType = ph.getMapValueType(tokens.keys.length);
|
||||
// IMPORTANT: Do not pass full property name in here - property editors
|
||||
// must not kick in for map keys but rather only for map values.
|
||||
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
|
||||
Object convertedMapKey = convertIfNecessary(null, null, lastKey, mapKeyType, typeDescriptor);
|
||||
Object convertedMapKey = convertIfNecessary(null, null, lastKey,
|
||||
mapKeyType.getResolvableType().resolve(), mapKeyType);
|
||||
Object oldValue = null;
|
||||
if (isExtractOldValueForEditor()) {
|
||||
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
|
||||
// conversion ability for map values.
|
||||
Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
|
||||
mapValueType, ph.nested(tokens.keys.length));
|
||||
mapValueType.getResolvableType().resolve(), mapValueType);
|
||||
map.put(convertedMapKey, convertedMapValue);
|
||||
}
|
||||
|
||||
|
|
@ -1041,19 +1041,16 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
|||
|
||||
public abstract ResolvableType getResolvableType();
|
||||
|
||||
@Nullable
|
||||
public Class<?> getMapKeyType(int nestingLevel) {
|
||||
return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(0);
|
||||
public TypeDescriptor getMapKeyType(int nestingLevel) {
|
||||
return TypeDescriptor.valueOf(getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(0));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Class<?> getMapValueType(int nestingLevel) {
|
||||
return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(1);
|
||||
public TypeDescriptor getMapValueType(int nestingLevel) {
|
||||
return TypeDescriptor.valueOf(getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(1));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Class<?> getCollectionType(int nestingLevel) {
|
||||
return getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric();
|
||||
public TypeDescriptor getCollectionType(int nestingLevel) {
|
||||
return TypeDescriptor.valueOf(getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
* 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 TypeDescriptor typeDescriptor;
|
||||
|
||||
public BeanPropertyHandler(PropertyDescriptor pd) {
|
||||
super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
|
||||
this.pd = pd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolvableType getResolvableType() {
|
||||
return ResolvableType.forMethodReturnType(this.pd.getReadMethod());
|
||||
this.typeDescriptor = new TypeDescriptor(property(pd));
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
* 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 ResolvableType resolvableType;
|
||||
|
||||
public FieldPropertyHandler(Field field) {
|
||||
super(field.getType(), true, true);
|
||||
this.field = field;
|
||||
this.resolvableType = ResolvableType.forField(this.field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDescriptor toTypeDescriptor() {
|
||||
return new TypeDescriptor(this.field);
|
||||
return new TypeDescriptor(this.resolvableType, this.field.getType(), this.field.getAnnotations());
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ import org.springframework.beans.testfixture.beans.GenericIntegerBean;
|
|||
import org.springframework.beans.testfixture.beans.GenericSetOfIntegerBean;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
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.assertThatExceptionOfType;
|
||||
|
|
@ -429,6 +431,18 @@ class BeanWrapperGenericsTests {
|
|||
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
|
||||
void testComplexDerivedIndexedMapEntry() {
|
||||
List<String> inputValue = new ArrayList<>();
|
||||
|
|
@ -455,6 +469,56 @@ class BeanWrapperGenericsTests {
|
|||
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
|
||||
void testGenericallyTypedIntegerBean() {
|
||||
GenericIntegerBean gb = new GenericIntegerBean();
|
||||
|
|
@ -585,6 +649,8 @@ class BeanWrapperGenericsTests {
|
|||
|
||||
private DerivedMap derivedIndexedMap = new DerivedMap();
|
||||
|
||||
private MultiValueMap<Integer, Long> multiValueMap = new LinkedMultiValueMap<>();
|
||||
|
||||
public void setGenericMap(Map<List<Integer>, List<Long>> genericMap) {
|
||||
this.genericMap = genericMap;
|
||||
}
|
||||
|
|
@ -608,6 +674,14 @@ class BeanWrapperGenericsTests {
|
|||
public DerivedMap getDerivedIndexedMap() {
|
||||
return derivedIndexedMap;
|
||||
}
|
||||
|
||||
public void setMultiValueMap(MultiValueMap<Integer, Long> multiValueMap) {
|
||||
this.multiValueMap = multiValueMap;
|
||||
}
|
||||
|
||||
public MultiValueMap<Integer, Long> getMultiValueMap() {
|
||||
return multiValueMap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue