Skip to content

Commit ac3c670

Browse files
committed
Fixed type resolution in case of inconsistencies between read and write method
Issue: SPR-11361
1 parent 398f91c commit ac3c670

File tree

3 files changed

+73
-54
lines changed

3 files changed

+73
-54
lines changed

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -59,7 +59,7 @@ public interface BeanWrapper extends ConfigurablePropertyAccessor {
5959
* @return the type of the wrapped bean instance,
6060
* or {@code null} if no wrapped object has been set
6161
*/
62-
Class getWrappedClass();
62+
Class<?> getWrappedClass();
6363

6464
/**
6565
* Obtain the PropertyDescriptors for the wrapped object
@@ -79,11 +79,13 @@ public interface BeanWrapper extends ConfigurablePropertyAccessor {
7979
PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
8080

8181
/**
82-
* Set whether this BeanWrapper should attempt to "auto-grow" a nested path that contains a null value.
83-
* <p>If "true", a null path location will be populated with a default object value and traversed
84-
* instead of resulting in a {@link NullValueInNestedPathException}. Turning this flag on also
85-
* enables auto-growth of collection elements when accessing an out-of-bounds index.
86-
* <p>Default is "false" on a plain BeanWrapper.
82+
* Set whether this BeanWrapper should attempt to "auto-grow" a
83+
* nested path that contains a {@code null} value.
84+
* <p>If {@code true}, a {@code null} path location will be populated
85+
* with a default object value and traversed instead of resulting in a
86+
* {@link NullValueInNestedPathException}. Turning this flag on also enables
87+
* auto-growth of collection elements when accessing an out-of-bounds index.
88+
* <p>Default is {@code false} on a plain BeanWrapper.
8789
*/
8890
void setAutoGrowNestedPaths(boolean autoGrowNestedPaths);
8991

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

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -223,7 +223,7 @@ public final Object getWrappedInstance() {
223223
return this.object;
224224
}
225225

226-
public final Class getWrappedClass() {
226+
public final Class<?> getWrappedClass() {
227227
return (this.object != null ? this.object.getClass() : null);
228228
}
229229

@@ -246,7 +246,7 @@ public final Object getRootInstance() {
246246
* Return the class of the root object at the top of the path of this BeanWrapper.
247247
* @see #getNestedPath
248248
*/
249-
public final Class getRootClass() {
249+
public final Class<?> getRootClass() {
250250
return (this.rootObject != null ? this.rootObject.getClass() : null);
251251
}
252252

@@ -304,7 +304,7 @@ public AccessControlContext getSecurityContext() {
304304
* Needs to be called when the target object changes.
305305
* @param clazz the class to introspect
306306
*/
307-
protected void setIntrospectionClass(Class clazz) {
307+
protected void setIntrospectionClass(Class<?> clazz) {
308308
if (this.cachedIntrospectionResults != null &&
309309
!clazz.equals(this.cachedIntrospectionResults.getBeanClass())) {
310310
this.cachedIntrospectionResults = null;
@@ -352,7 +352,7 @@ protected PropertyDescriptor getPropertyDescriptorInternal(String propertyName)
352352
}
353353

354354
@Override
355-
public Class getPropertyType(String propertyName) throws BeansException {
355+
public Class<?> getPropertyType(String propertyName) throws BeansException {
356356
try {
357357
PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
358358
if (pd != null) {
@@ -366,7 +366,7 @@ public Class getPropertyType(String propertyName) throws BeansException {
366366
}
367367
// Check to see if there is a custom editor,
368368
// which might give an indication on the desired target type.
369-
Class editorType = guessPropertyTypeFromEditors(propertyName);
369+
Class<?> editorType = guessPropertyTypeFromEditors(propertyName);
370370
if (editorType != null) {
371371
return editorType;
372372
}
@@ -485,13 +485,13 @@ public Object convertForProperty(Object value, String propertyName) throws TypeM
485485
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
486486
"No property '" + propertyName + "' found");
487487
}
488-
return convertForProperty(propertyName, null, value, pd);
488+
return convertForProperty(propertyName, null, value, new TypeDescriptor(property(pd)));
489489
}
490490

491-
private Object convertForProperty(String propertyName, Object oldValue, Object newValue, PropertyDescriptor pd)
491+
private Object convertForProperty(String propertyName, Object oldValue, Object newValue, TypeDescriptor td)
492492
throws TypeMismatchException {
493493

494-
return convertIfNecessary(propertyName, oldValue, newValue, pd.getPropertyType(), new TypeDescriptor(property(pd)));
494+
return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
495495
}
496496

497497
private Property property(PropertyDescriptor pd) {
@@ -699,7 +699,8 @@ public Object getPropertyValue(String propertyName) throws BeansException {
699699
return nestedBw.getPropertyValue(tokens);
700700
}
701701

702-
private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
702+
@SuppressWarnings("unchecked")
703+
private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
703704
String propertyName = tokens.canonicalName;
704705
String actualName = tokens.actualName;
705706
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
@@ -766,20 +767,20 @@ else if (value.getClass().isArray()) {
766767
}
767768
else if (value instanceof List) {
768769
int index = Integer.parseInt(key);
769-
List list = (List) value;
770+
List<Object> list = (List<Object>) value;
770771
growCollectionIfNecessary(list, index, indexedPropertyName, pd, i + 1);
771772
value = list.get(index);
772773
}
773774
else if (value instanceof Set) {
774775
// Apply index to Iterator in case of a Set.
775-
Set set = (Set) value;
776+
Set<Object> set = (Set<Object>) value;
776777
int index = Integer.parseInt(key);
777778
if (index < 0 || index >= set.size()) {
778779
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
779780
"Cannot get element with index " + index + " from Set of size " +
780781
set.size() + ", accessed using property path '" + propertyName + "'");
781782
}
782-
Iterator it = set.iterator();
783+
Iterator<Object> it = set.iterator();
783784
for (int j = 0; it.hasNext(); j++) {
784785
Object elem = it.next();
785786
if (j == index) {
@@ -789,11 +790,12 @@ else if (value instanceof Set) {
789790
}
790791
}
791792
else if (value instanceof Map) {
792-
Map map = (Map) value;
793+
Map<Object, Object> map = (Map<Object, Object>) value;
793794
Class<?> mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(pd.getReadMethod(), i + 1);
794795
// IMPORTANT: Do not pass full property name in here - property editors
795796
// must not kick in for map keys but rather only for map values.
796-
TypeDescriptor typeDescriptor = mapKeyType != null ? TypeDescriptor.valueOf(mapKeyType) : TypeDescriptor.valueOf(Object.class);
797+
TypeDescriptor typeDescriptor = (mapKeyType != null ?
798+
TypeDescriptor.valueOf(mapKeyType) : TypeDescriptor.valueOf(Object.class));
797799
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
798800
value = map.get(convertedMapKey);
799801
}
@@ -850,16 +852,15 @@ private Object growArrayIfNecessary(Object array, int index, String name) {
850852
}
851853
}
852854

853-
@SuppressWarnings("unchecked")
854-
private void growCollectionIfNecessary(
855-
Collection collection, int index, String name, PropertyDescriptor pd, int nestingLevel) {
855+
private void growCollectionIfNecessary(Collection<Object> collection, int index, String name,
856+
PropertyDescriptor pd, int nestingLevel) {
856857

857858
if (!this.autoGrowNestedPaths) {
858859
return;
859860
}
860861
int size = collection.size();
861862
if (index >= size && index < this.autoGrowCollectionLimit) {
862-
Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel);
863+
Class<?> elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel);
863864
if (elementType != null) {
864865
for (int i = collection.size(); i < index + 1; i++) {
865866
collection.add(newValue(elementType, name));
@@ -945,7 +946,7 @@ private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) thro
945946
}
946947
if (propValue.getClass().isArray()) {
947948
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
948-
Class requiredType = propValue.getClass().getComponentType();
949+
Class<?> requiredType = propValue.getClass().getComponentType();
949950
int arrayIndex = Integer.parseInt(key);
950951
Object oldValue = null;
951952
try {
@@ -963,9 +964,9 @@ private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) thro
963964
}
964965
else if (propValue instanceof List) {
965966
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
966-
Class requiredType = GenericCollectionTypeResolver.getCollectionReturnType(
967+
Class<?> requiredType = GenericCollectionTypeResolver.getCollectionReturnType(
967968
pd.getReadMethod(), tokens.keys.length);
968-
List list = (List) propValue;
969+
List<Object> list = (List<Object>) propValue;
969970
int index = Integer.parseInt(key);
970971
Object oldValue = null;
971972
if (isExtractOldValueForEditor() && index < list.size()) {
@@ -1000,11 +1001,11 @@ else if (propValue instanceof List) {
10001001
}
10011002
else if (propValue instanceof Map) {
10021003
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
1003-
Class mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(
1004+
Class<?> mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(
10041005
pd.getReadMethod(), tokens.keys.length);
1005-
Class mapValueType = GenericCollectionTypeResolver.getMapValueReturnType(
1006+
Class<?> mapValueType = GenericCollectionTypeResolver.getMapValueReturnType(
10061007
pd.getReadMethod(), tokens.keys.length);
1007-
Map map = (Map) propValue;
1008+
Map<Object, Object> map = (Map<Object, Object>) propValue;
10081009
// IMPORTANT: Do not pass full property name in here - property editors
10091010
// must not kick in for map keys but rather only for map values.
10101011
TypeDescriptor typeDescriptor = (mapKeyType != null ?
@@ -1094,7 +1095,8 @@ public Object run() throws Exception {
10941095
}
10951096
}
10961097
}
1097-
valueToApply = convertForProperty(propertyName, oldValue, originalValue, pd);
1098+
valueToApply = convertForProperty(
1099+
propertyName, oldValue, originalValue, new TypeDescriptor(property(pd)));
10981100
}
10991101
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
11001102
}

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

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -16,13 +16,6 @@
1616

1717
package org.springframework.beans;
1818

19-
import static org.junit.Assert.assertEquals;
20-
import static org.junit.Assert.assertFalse;
21-
import static org.junit.Assert.assertNotNull;
22-
import static org.junit.Assert.assertSame;
23-
import static org.junit.Assert.assertTrue;
24-
import static org.junit.Assert.fail;
25-
2619
import java.beans.PropertyEditorSupport;
2720
import java.math.BigDecimal;
2821
import java.math.BigInteger;
@@ -44,27 +37,27 @@
4437

4538
import org.apache.commons.logging.LogFactory;
4639
import org.junit.Test;
40+
4741
import org.springframework.beans.factory.annotation.Autowire;
4842
import org.springframework.beans.propertyeditors.CustomNumberEditor;
4943
import org.springframework.beans.propertyeditors.StringArrayPropertyEditor;
5044
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
5145
import org.springframework.beans.support.DerivedFromProtectedBaseBean;
46+
import org.springframework.core.convert.ConversionFailedException;
47+
import org.springframework.core.convert.TypeDescriptor;
48+
import org.springframework.core.convert.support.DefaultConversionService;
49+
import org.springframework.core.convert.support.GenericConversionService;
5250
import org.springframework.tests.Assume;
5351
import org.springframework.tests.TestGroup;
5452
import org.springframework.tests.sample.beans.BooleanTestBean;
5553
import org.springframework.tests.sample.beans.ITestBean;
5654
import org.springframework.tests.sample.beans.IndexedTestBean;
5755
import org.springframework.tests.sample.beans.NumberTestBean;
5856
import org.springframework.tests.sample.beans.TestBean;
59-
import org.springframework.core.convert.ConversionFailedException;
60-
import org.springframework.core.convert.TypeDescriptor;
61-
import org.springframework.core.convert.support.DefaultConversionService;
62-
import org.springframework.core.convert.support.GenericConversionService;
6357
import org.springframework.util.StopWatch;
6458
import org.springframework.util.StringUtils;
6559

6660
import static org.hamcrest.Matchers.*;
67-
6861
import static org.junit.Assert.*;
6962

7063

@@ -1557,17 +1550,15 @@ public void testWildcardedGenericEnum() {
15571550
@Test
15581551
public void cornerSpr10115() {
15591552
Spr10115Bean foo = new Spr10115Bean();
1560-
BeanWrapperImpl bwi = new BeanWrapperImpl();
1561-
bwi.setWrappedInstance(foo);
1553+
BeanWrapperImpl bwi = new BeanWrapperImpl(foo);
15621554
bwi.setPropertyValue("prop1", "val1");
15631555
assertEquals("val1", Spr10115Bean.prop1);
15641556
}
15651557

15661558
@Test
1567-
public void testArrayToObject() throws Exception {
1559+
public void testArrayToObject() {
15681560
ArrayToObject foo = new ArrayToObject();
1569-
BeanWrapperImpl bwi = new BeanWrapperImpl();
1570-
bwi.setWrappedInstance(foo);
1561+
BeanWrapperImpl bwi = new BeanWrapperImpl(foo);
15711562

15721563
Object[] array = new Object[] {"1","2"};
15731564
bwi.setPropertyValue("object", array );
@@ -1576,9 +1567,20 @@ public void testArrayToObject() throws Exception {
15761567
array = new Object[] {"1"};
15771568
bwi.setPropertyValue("object", array );
15781569
assertThat(foo.getObject(), equalTo((Object) array));
1579-
}
1570+
}
1571+
1572+
@Test
1573+
public void testPropertyTypeMismatch() {
1574+
PropertyTypeMismatch foo = new PropertyTypeMismatch();
1575+
BeanWrapperImpl bwi = new BeanWrapperImpl(foo);
1576+
bwi.setPropertyValue("object", "a String");
1577+
assertEquals("a String", foo.value);
1578+
assertEquals(8, bwi.getPropertyValue("object"));
1579+
}
1580+
15801581

15811582
static class Spr10115Bean {
1583+
15821584
private static String prop1;
15831585

15841586
public static void setProp1(String prop1) {
@@ -1963,18 +1965,31 @@ public enum TestEnum {
19631965
}
19641966

19651967

1966-
static class ArrayToObject {
1968+
public static class ArrayToObject {
19671969

19681970
private Object object;
19691971

1970-
19711972
public void setObject(Object object) {
19721973
this.object = object;
19731974
}
19741975

19751976
public Object getObject() {
19761977
return object;
19771978
}
1979+
}
1980+
19781981

1982+
public static class PropertyTypeMismatch {
1983+
1984+
public String value;
1985+
1986+
public void setObject(String object) {
1987+
this.value = object;
1988+
}
1989+
1990+
public Integer getObject() {
1991+
return (this.value != null ? this.value.length() : null);
1992+
}
19791993
}
1994+
19801995
}

0 commit comments

Comments
 (0)