diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java index 51d2a5f73989..ff9908684fbd 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -319,14 +319,14 @@ private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) } 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 @@ else if (propValue instanceof List list) { } 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 @@ else if (propValue instanceof Map map) { // 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 boolean isWritable() { 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 diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 4f610998ae03..f7744476a7cc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -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 @@ private class BeanPropertyHandler extends PropertyHandler { private final PropertyDescriptor pd; + private final TypeDescriptor typeDescriptor; + public BeanPropertyHandler(PropertyDescriptor pd) { super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null); this.pd = pd; + this.typeDescriptor = new TypeDescriptor(property(pd)); + } + + @Override + public TypeDescriptor toTypeDescriptor() { + return this.typeDescriptor; } @Override public ResolvableType getResolvableType() { - return ResolvableType.forMethodReturnType(this.pd.getReadMethod()); + return this.typeDescriptor.getResolvableType(); } @Override - public TypeDescriptor toTypeDescriptor() { - return new TypeDescriptor(property(this.pd)); + 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 diff --git a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java index a98c6eb41b03..3a97b43f77da 100644 --- a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java @@ -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 @@ private class FieldPropertyHandler extends PropertyHandler { 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 diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java index 6012ecfaa9cd..99f5d02bc896 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java @@ -36,6 +36,8 @@ 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 @@ void testComplexGenericIndexedMapEntryWithCollectionConversion() { 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 inputValue = new ArrayList<>(); @@ -455,6 +469,56 @@ void testComplexDerivedIndexedMapEntryWithCollectionConversion() { 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 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 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 @@ private static class ComplexMapHolder { private DerivedMap derivedIndexedMap = new DerivedMap(); + private MultiValueMap multiValueMap = new LinkedMultiValueMap<>(); + public void setGenericMap(Map, List> genericMap) { this.genericMap = genericMap; } @@ -608,6 +674,14 @@ public void setDerivedIndexedMap(DerivedMap derivedIndexedMap) { public DerivedMap getDerivedIndexedMap() { return derivedIndexedMap; } + + public void setMultiValueMap(MultiValueMap multiValueMap) { + this.multiValueMap = multiValueMap; + } + + public MultiValueMap getMultiValueMap() { + return multiValueMap; + } }