Skip to content

Commit c1932dd

Browse files
committed
Support for MultiValueMap elements in bean property paths
Closes gh-26297
1 parent 490ff0a commit c1932dd

File tree

4 files changed

+126
-23
lines changed

4 files changed

+126
-23
lines changed

spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -319,14 +319,14 @@ private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv)
319319
}
320320

321321
else if (propValue instanceof List list) {
322-
Class<?> requiredType = ph.getCollectionType(tokens.keys.length);
322+
TypeDescriptor requiredType = ph.getCollectionType(tokens.keys.length);
323323
int index = Integer.parseInt(lastKey);
324324
Object oldValue = null;
325325
if (isExtractOldValueForEditor() && index < list.size()) {
326326
oldValue = list.get(index);
327327
}
328328
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
329-
requiredType, ph.nested(tokens.keys.length));
329+
requiredType.getResolvableType().resolve(), requiredType);
330330
int size = list.size();
331331
if (index >= size && index < this.autoGrowCollectionLimit) {
332332
for (int i = size; i < index; i++) {
@@ -354,20 +354,20 @@ else if (propValue instanceof List list) {
354354
}
355355

356356
else if (propValue instanceof Map map) {
357-
Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length);
358-
Class<?> mapValueType = ph.getMapValueType(tokens.keys.length);
357+
TypeDescriptor mapKeyType = ph.getMapKeyType(tokens.keys.length);
358+
TypeDescriptor mapValueType = ph.getMapValueType(tokens.keys.length);
359359
// IMPORTANT: Do not pass full property name in here - property editors
360360
// must not kick in for map keys but rather only for map values.
361-
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
362-
Object convertedMapKey = convertIfNecessary(null, null, lastKey, mapKeyType, typeDescriptor);
361+
Object convertedMapKey = convertIfNecessary(null, null, lastKey,
362+
mapKeyType.getResolvableType().resolve(), mapKeyType);
363363
Object oldValue = null;
364364
if (isExtractOldValueForEditor()) {
365365
oldValue = map.get(convertedMapKey);
366366
}
367367
// Pass full property name and old value in here, since we want full
368368
// conversion ability for map values.
369369
Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
370-
mapValueType, ph.nested(tokens.keys.length));
370+
mapValueType.getResolvableType().resolve(), mapValueType);
371371
map.put(convertedMapKey, convertedMapValue);
372372
}
373373

@@ -1041,19 +1041,16 @@ public boolean isWritable() {
10411041

10421042
public abstract ResolvableType getResolvableType();
10431043

1044-
@Nullable
1045-
public Class<?> getMapKeyType(int nestingLevel) {
1046-
return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(0);
1044+
public TypeDescriptor getMapKeyType(int nestingLevel) {
1045+
return TypeDescriptor.valueOf(getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(0));
10471046
}
10481047

1049-
@Nullable
1050-
public Class<?> getMapValueType(int nestingLevel) {
1051-
return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(1);
1048+
public TypeDescriptor getMapValueType(int nestingLevel) {
1049+
return TypeDescriptor.valueOf(getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(1));
10521050
}
10531051

1054-
@Nullable
1055-
public Class<?> getCollectionType(int nestingLevel) {
1056-
return getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric();
1052+
public TypeDescriptor getCollectionType(int nestingLevel) {
1053+
return TypeDescriptor.valueOf(getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric());
10571054
}
10581055

10591056
@Nullable

spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -236,19 +236,36 @@ private class BeanPropertyHandler extends PropertyHandler {
236236

237237
private final PropertyDescriptor pd;
238238

239+
private final TypeDescriptor typeDescriptor;
240+
239241
public BeanPropertyHandler(PropertyDescriptor pd) {
240242
super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
241243
this.pd = pd;
244+
this.typeDescriptor = new TypeDescriptor(property(pd));
245+
}
246+
247+
@Override
248+
public TypeDescriptor toTypeDescriptor() {
249+
return this.typeDescriptor;
242250
}
243251

244252
@Override
245253
public ResolvableType getResolvableType() {
246-
return ResolvableType.forMethodReturnType(this.pd.getReadMethod());
254+
return this.typeDescriptor.getResolvableType();
247255
}
248256

249257
@Override
250-
public TypeDescriptor toTypeDescriptor() {
251-
return new TypeDescriptor(property(this.pd));
258+
public TypeDescriptor getMapValueType(int nestingLevel) {
259+
return new TypeDescriptor(
260+
this.typeDescriptor.getResolvableType().getNested(nestingLevel).asMap().getGeneric(1),
261+
null, this.typeDescriptor.getAnnotations());
262+
}
263+
264+
@Override
265+
public TypeDescriptor getCollectionType(int nestingLevel) {
266+
return new TypeDescriptor(
267+
this.typeDescriptor.getResolvableType().getNested(nestingLevel).asCollection().getGeneric(),
268+
null, this.typeDescriptor.getAnnotations());
252269
}
253270

254271
@Override

spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -101,19 +101,34 @@ private class FieldPropertyHandler extends PropertyHandler {
101101

102102
private final Field field;
103103

104+
private final ResolvableType resolvableType;
105+
104106
public FieldPropertyHandler(Field field) {
105107
super(field.getType(), true, true);
106108
this.field = field;
109+
this.resolvableType = ResolvableType.forField(this.field);
107110
}
108111

109112
@Override
110113
public TypeDescriptor toTypeDescriptor() {
111-
return new TypeDescriptor(this.field);
114+
return new TypeDescriptor(this.resolvableType, this.field.getType(), this.field.getAnnotations());
112115
}
113116

114117
@Override
115118
public ResolvableType getResolvableType() {
116-
return ResolvableType.forField(this.field);
119+
return this.resolvableType;
120+
}
121+
122+
@Override
123+
public TypeDescriptor getMapValueType(int nestingLevel) {
124+
return new TypeDescriptor(this.resolvableType.getNested(nestingLevel).asMap().getGeneric(1),
125+
null, this.field.getAnnotations());
126+
}
127+
128+
@Override
129+
public TypeDescriptor getCollectionType(int nestingLevel) {
130+
return new TypeDescriptor(this.resolvableType.getNested(nestingLevel).asCollection().getGeneric(),
131+
null, this.field.getAnnotations());
117132
}
118133

119134
@Override

spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import org.springframework.beans.testfixture.beans.GenericSetOfIntegerBean;
3737
import org.springframework.beans.testfixture.beans.TestBean;
3838
import org.springframework.core.io.UrlResource;
39+
import org.springframework.util.LinkedMultiValueMap;
40+
import org.springframework.util.MultiValueMap;
3941

4042
import static org.assertj.core.api.Assertions.assertThat;
4143
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -429,6 +431,18 @@ void testComplexGenericIndexedMapEntryWithCollectionConversion() {
429431
assertThat(holder.getGenericIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
430432
}
431433

434+
@Test
435+
void testComplexGenericIndexedMapEntryWithPlainValue() {
436+
String inputValue = "10";
437+
438+
ComplexMapHolder holder = new ComplexMapHolder();
439+
BeanWrapper bw = new BeanWrapperImpl(holder);
440+
bw.setPropertyValue("genericIndexedMap[1]", inputValue);
441+
442+
assertThat(holder.getGenericIndexedMap().keySet().iterator().next()).isEqualTo(1);
443+
assertThat(holder.getGenericIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
444+
}
445+
432446
@Test
433447
void testComplexDerivedIndexedMapEntry() {
434448
List<String> inputValue = new ArrayList<>();
@@ -455,6 +469,56 @@ void testComplexDerivedIndexedMapEntryWithCollectionConversion() {
455469
assertThat(holder.getDerivedIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
456470
}
457471

472+
@Test
473+
void testComplexDerivedIndexedMapEntryWithPlainValue() {
474+
String inputValue = "10";
475+
476+
ComplexMapHolder holder = new ComplexMapHolder();
477+
BeanWrapper bw = new BeanWrapperImpl(holder);
478+
bw.setPropertyValue("derivedIndexedMap[1]", inputValue);
479+
480+
assertThat(holder.getDerivedIndexedMap().keySet().iterator().next()).isEqualTo(1);
481+
assertThat(holder.getDerivedIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
482+
}
483+
484+
@Test
485+
void testComplexMultiValueMapEntry() {
486+
List<String> inputValue = new ArrayList<>();
487+
inputValue.add("10");
488+
489+
ComplexMapHolder holder = new ComplexMapHolder();
490+
BeanWrapper bw = new BeanWrapperImpl(holder);
491+
bw.setPropertyValue("multiValueMap[1]", inputValue);
492+
493+
assertThat(holder.getMultiValueMap().keySet().iterator().next()).isEqualTo(1);
494+
assertThat(holder.getMultiValueMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
495+
}
496+
497+
@Test
498+
void testComplexMultiValueMapEntryWithCollectionConversion() {
499+
Set<String> inputValue = new HashSet<>();
500+
inputValue.add("10");
501+
502+
ComplexMapHolder holder = new ComplexMapHolder();
503+
BeanWrapper bw = new BeanWrapperImpl(holder);
504+
bw.setPropertyValue("multiValueMap[1]", inputValue);
505+
506+
assertThat(holder.getMultiValueMap().keySet().iterator().next()).isEqualTo(1);
507+
assertThat(holder.getMultiValueMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
508+
}
509+
510+
@Test
511+
void testComplexMultiValueMapEntryWithPlainValue() {
512+
String inputValue = "10";
513+
514+
ComplexMapHolder holder = new ComplexMapHolder();
515+
BeanWrapper bw = new BeanWrapperImpl(holder);
516+
bw.setPropertyValue("multiValueMap[1]", inputValue);
517+
518+
assertThat(holder.getMultiValueMap().keySet().iterator().next()).isEqualTo(1);
519+
assertThat(holder.getMultiValueMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10));
520+
}
521+
458522
@Test
459523
void testGenericallyTypedIntegerBean() {
460524
GenericIntegerBean gb = new GenericIntegerBean();
@@ -585,6 +649,8 @@ private static class ComplexMapHolder {
585649

586650
private DerivedMap derivedIndexedMap = new DerivedMap();
587651

652+
private MultiValueMap<Integer, Long> multiValueMap = new LinkedMultiValueMap<>();
653+
588654
public void setGenericMap(Map<List<Integer>, List<Long>> genericMap) {
589655
this.genericMap = genericMap;
590656
}
@@ -608,6 +674,14 @@ public void setDerivedIndexedMap(DerivedMap derivedIndexedMap) {
608674
public DerivedMap getDerivedIndexedMap() {
609675
return derivedIndexedMap;
610676
}
677+
678+
public void setMultiValueMap(MultiValueMap<Integer, Long> multiValueMap) {
679+
this.multiValueMap = multiValueMap;
680+
}
681+
682+
public MultiValueMap<Integer, Long> getMultiValueMap() {
683+
return multiValueMap;
684+
}
611685
}
612686

613687

0 commit comments

Comments
 (0)