diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 5dfd2b5b6f..b57cada675 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -512,11 +512,15 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra private Object convertForProperty(String propertyName, Object oldValue, Object newValue, PropertyDescriptor pd) throws TypeMismatchException { - GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd; - Class beanClass = gpd.getBeanClass(); + return convertIfNecessary(propertyName, oldValue, newValue, pd.getPropertyType(), new TypeDescriptor(property(pd))); } + private Property property(PropertyDescriptor pd) { + GenericTypeAwarePropertyDescriptor typeAware = (GenericTypeAwarePropertyDescriptor) pd; + return new Property(typeAware.getBeanClass(), typeAware.getReadMethod(), typeAware.getWriteMethod()); + } + //--------------------------------------------------------------------- // Implementation methods @@ -970,7 +974,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) { oldValue = Array.get(propValue, arrayIndex); } - Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length)); + Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), + requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length)); Array.set(propValue, arrayIndex, convertedValue); } catch (IndexOutOfBoundsException ex) { @@ -984,12 +989,13 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra pd.getReadMethod(), tokens.keys.length); List list = (List) propValue; int index = Integer.parseInt(key); - int size = list.size(); Object oldValue = null; - if (isExtractOldValueForEditor() && index < size) { + if (isExtractOldValueForEditor() && index < list.size()) { oldValue = list.get(index); } - Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length)); + Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), + requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length)); + int size = list.size(); if (index >= size && index < this.autoGrowCollectionLimit) { for (int i = size; i < index; i++) { try { @@ -1023,7 +1029,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra Map map = (Map) propValue; // 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 = mapKeyType != null ? TypeDescriptor.valueOf(mapKeyType) : TypeDescriptor.valueOf(Object.class); + TypeDescriptor typeDescriptor = (mapKeyType != null ? + TypeDescriptor.valueOf(mapKeyType) : TypeDescriptor.valueOf(Object.class)); Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor); Object oldValue = null; if (isExtractOldValueForEditor()) { @@ -1031,8 +1038,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } // Pass full property name and old value in here, since we want full // conversion ability for map values. - Object convertedMapValue = convertIfNecessary( - propertyName, oldValue, pv.getValue(), mapValueType, TypeDescriptor.nested(property(pd), tokens.keys.length)); + Object convertedMapValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), + mapValueType, TypeDescriptor.nested(property(pd), tokens.keys.length)); map.put(convertedMapKey, convertedMapValue); } else { @@ -1194,10 +1201,5 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra public String[] keys; } - - private Property property(PropertyDescriptor pd) { - GenericTypeAwarePropertyDescriptor typeAware = (GenericTypeAwarePropertyDescriptor) pd; - return new Property(typeAware.getBeanClass(), typeAware.getReadMethod(), typeAware.getWriteMethod()); - } } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java b/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java index 0355cf0ba6..a2d5ee7af3 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java @@ -93,7 +93,7 @@ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { } public Class getBeanClass() { - return beanClass; + return this.beanClass; } @Override diff --git a/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java b/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java index c02fc3ce2f..455cb8842b 100644 --- a/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java @@ -22,7 +22,13 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -45,6 +51,7 @@ import org.springframework.beans.propertyeditors.CustomNumberEditor; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.context.support.StaticMessageSource; +import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.format.number.NumberFormatter; import org.springframework.format.support.FormattingConversionService; @@ -1552,6 +1559,20 @@ public class DataBinderTests extends TestCase { } } + public void testNestedGrowingList() { + Form form = new Form(); + DataBinder binder = new DataBinder(form, "form"); + MutablePropertyValues mpv = new MutablePropertyValues(); + mpv.add("f[list][0]", "firstValue"); + mpv.add("f[list][1]", "secondValue"); + binder.bind(mpv); + assertFalse(binder.getBindingResult().hasErrors()); + List list = (List) form.getF().get("list"); + assertEquals("firstValue", list.get(0)); + assertEquals("secondValue", list.get(1)); + assertEquals(2, list.size()); + } + private static class BeanWithIntegerList { @@ -1646,4 +1667,94 @@ public class DataBinderTests extends TestCase { } } + + private static class GrowingList extends AbstractList { + + private List list; + + public GrowingList() { + this.list = new ArrayList(); + } + + public List getWrappedList() { + return list; + } + + public E get(int index) { + if (index >= list.size()) { + for (int i = list.size(); i < index; i++) { + list.add(null); + } + list.add(null); + return null; + } + else { + return list.get(index); + } + } + + public int size() { + return list.size(); + } + + public boolean add(E o) { + return list.add(o); + } + + public void add(int index, E element) { + list.add(index, element); + } + + public boolean addAll(int index, Collection c) { + return list.addAll(index, c); + } + + public void clear() { + list.clear(); + } + + public int indexOf(Object o) { + return list.indexOf(o); + } + + public Iterator iterator() { + return list.iterator(); + } + + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + public ListIterator listIterator() { + return list.listIterator(); + } + + public ListIterator listIterator(int index) { + return list.listIterator(index); + } + + public E remove(int index) { + return list.remove(index); + } + + public E set(int index, E element) { + return list.set(index, element); + } + } + + + private static class Form { + + private final Map f; + + public Form() { + f = new HashMap(); + f.put("list", new GrowingList()); + } + + public Map getF() { + return f; + } + } + } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java index 023e8ca6d1..99e7494dba 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java @@ -20,6 +20,8 @@ import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Map; +import org.springframework.util.Assert; + /** * @author Keith Donald * @since 3.1 @@ -28,15 +30,15 @@ abstract class AbstractDescriptor { private final Class type; + protected AbstractDescriptor(Class type) { - if (type == null) { - throw new IllegalArgumentException("type cannot be null"); - } + Assert.notNull(type, "Type must not be null"); this.type = type; } + public Class getType() { - return type; + return this.type; } public TypeDescriptor getElementTypeDescriptor() { @@ -73,27 +75,33 @@ abstract class AbstractDescriptor { } } - public abstract Annotation[] getAnnotations(); - public AbstractDescriptor nested() { if (isCollection()) { Class elementType = resolveCollectionElementType(); - return elementType != null ? nested(elementType, 0) : null; + return (elementType != null ? nested(elementType, 0) : null); } else if (isArray()) { return nested(getType().getComponentType(), 0); } else if (isMap()) { Class mapValueType = resolveMapValueType(); - return mapValueType != null ? nested(mapValueType, 1) : null; + return (mapValueType != null ? nested(mapValueType, 1) : null); + } + else if (Object.class.equals(getType())) { + // could be a collection type but we don't know about its element type, + // so let's just assume there is an element type of type Object + return this; } else { throw new IllegalStateException("Not a collection, array, or map: cannot resolve nested value types"); } } + // subclassing hooks + public abstract Annotation[] getAnnotations(); + protected abstract Class resolveCollectionElementType(); protected abstract Class resolveMapKeyType(); @@ -102,6 +110,7 @@ abstract class AbstractDescriptor { protected abstract AbstractDescriptor nested(Class type, int typeIndex); + // internal helpers private boolean isCollection() { diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java index b7358d07a9..de2ff2fd92 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java @@ -20,6 +20,10 @@ import java.lang.annotation.Annotation; import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.MethodParameter; +/** + * @author Keith Donald + * @since 3.1 + */ class BeanPropertyDescriptor extends AbstractDescriptor { private final Property property; @@ -28,6 +32,7 @@ class BeanPropertyDescriptor extends AbstractDescriptor { private final Annotation[] annotations; + public BeanPropertyDescriptor(Property property) { super(property.getType()); this.property = property; @@ -35,24 +40,25 @@ class BeanPropertyDescriptor extends AbstractDescriptor { this.annotations = property.getAnnotations(); } + @Override public Annotation[] getAnnotations() { - return annotations; + return this.annotations; } @Override protected Class resolveCollectionElementType() { - return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter); + return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter); } @Override protected Class resolveMapKeyType() { - return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter); + return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter); } @Override protected Class resolveMapValueType() { - return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter); + return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter); } @Override @@ -60,9 +66,10 @@ class BeanPropertyDescriptor extends AbstractDescriptor { MethodParameter methodParameter = new MethodParameter(this.methodParameter); methodParameter.increaseNestingLevel(); methodParameter.setTypeIndexForCurrentLevel(typeIndex); - return new BeanPropertyDescriptor(type, property, methodParameter, annotations); + return new BeanPropertyDescriptor(type, this.property, methodParameter, this.annotations); } + // internal private BeanPropertyDescriptor(Class type, Property propertyDescriptor, MethodParameter methodParameter, Annotation[] annotations) { @@ -72,4 +79,4 @@ class BeanPropertyDescriptor extends AbstractDescriptor { this.annotations = annotations; } -} \ No newline at end of file +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java index 653e494fd8..75c5ce82a4 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java @@ -53,7 +53,7 @@ class FieldDescriptor extends AbstractDescriptor { @Override public Annotation[] getAnnotations() { - return TypeDescriptor.nullSafeAnnotations(field.getAnnotations()); + return TypeDescriptor.nullSafeAnnotations(this.field.getAnnotations()); } @Override diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/Property.java b/org.springframework.core/src/main/java/org/springframework/core/convert/Property.java index a9f6089950..7cdf0bff80 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/Property.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/Property.java @@ -27,11 +27,16 @@ import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** - * A description of a JavaBeans Property that allows us to avoid a dependency on java.beans.PropertyDescriptor. - * java.beans is not available in a number of environments (e.g. Android, Java ME), so this is desirable. - * Used to build a TypeDescriptor from a property location. + * A description of a JavaBeans Property that allows us to avoid a dependency on + * java.beans.PropertyDescriptor. The java.beans package + * is not available in a number of environments (e.g. Android, Java ME), so this is + * desirable for portability of Spring's core conversion facility. + * + *

Used to build a TypeDescriptor from a property location. * The built TypeDescriptor can then be used to convert from/to the property type. + * * @author Keith Donald + * @since 3.1 * @see TypeDescriptor#TypeDescriptor(Property) * @see TypeDescriptor#nested(Property, int) */ @@ -49,6 +54,7 @@ public final class Property { private final Annotation[] annotations; + public Property(Class objectType, Method readMethod, Method writeMethod) { this.objectType = objectType; this.readMethod = readMethod; @@ -58,72 +64,77 @@ public final class Property { this.annotations = resolveAnnotations(); } + /** * The object declaring this property, either directly or in a superclass the object extends. */ public Class getObjectType() { - return objectType; + return this.objectType; } /** - * The name of the property e.g. 'foo'. + * The name of the property: e.g. 'foo' */ public String getName() { - return name; + return this.name; } /** - * The property type e.g. java.lang.String. + * The property type: e.g. java.lang.String */ public Class getType() { - return methodParameter.getParameterType(); + return this.methodParameter.getParameterType(); } /** - * The property getter method e.g. getFoo() + * The property getter method: e.g. getFoo() */ public Method getReadMethod() { - return readMethod; + return this.readMethod; } /** - * The property setter method e.g. setFoo(String). + * The property setter method: e.g. setFoo(String) */ public Method getWriteMethod() { - return writeMethod; + return this.writeMethod; } + // package private MethodParameter getMethodParameter() { - return methodParameter; + return this.methodParameter; } Annotation[] getAnnotations() { - return annotations; + return this.annotations; } + // internal helpers private String resolveName() { - if (readMethod != null) { - int index = readMethod.getName().indexOf("get"); + if (this.readMethod != null) { + int index = this.readMethod.getName().indexOf("get"); if (index != -1) { index += 3; - } else { - index = readMethod.getName().indexOf("is"); + } + else { + index = this.readMethod.getName().indexOf("is"); if (index == -1) { throw new IllegalArgumentException("Not a getter method"); } index += 2; } - return StringUtils.uncapitalize(readMethod.getName().substring(index)); - } else { - int index = writeMethod.getName().indexOf("set") + 3; + return StringUtils.uncapitalize(this.readMethod.getName().substring(index)); + } + else { + int index = this.writeMethod.getName().indexOf("set") + 3; if (index == -1) { throw new IllegalArgumentException("Not a setter method"); } - return StringUtils.uncapitalize(writeMethod.getName().substring(index)); + return StringUtils.uncapitalize(this.writeMethod.getName().substring(index)); } } @@ -204,7 +215,8 @@ public final class Property { private Class declaringClass() { if (getReadMethod() != null) { return getReadMethod().getDeclaringClass(); - } else { + } + else { return getWriteMethod().getDeclaringClass(); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index b8dd4b9695..ff277afa30 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -164,12 +164,12 @@ public class TypeDescriptor { /** * Creates a type descriptor for a nested type declared within the field. - * For example, if the field is a List<String> and the nestingLevel is 1, the nested type descriptor will be String.class. - * If the field is a List> and the nestingLevel is 2, the nested type descriptor will also be a String.class. - * If the field is a Map and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value. - * If the field is a List> and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value. - * Returns null if a nested type cannot be obtained because it was not declared. - * For example, if the field is a List<?>, the nested type descriptor returned will be null. + *

For example, if the field is a List<String> and the nestingLevel is 1, the nested type descriptor will be String.class. + * If the field is a List<List<String>> and the nestingLevel is 2, the nested type descriptor will also be a String.class. + * If the field is a Map<Integer, String> and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value. + * If the field is a List<Map<Integer, String>> and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value. + * Returns null if a nested type cannot be obtained because it was not declared. + * For example, if the field is a List<?>, the nested type descriptor returned will be null. * @param field the field * @param nestingLevel the nesting level of the collection/array element or map key/value declaration within the field. * @return the nested type descriptor at the specified nestingLevel, or null if it could not be obtained @@ -181,15 +181,15 @@ public class TypeDescriptor { /** * Creates a type descriptor for a nested type declared within the property. - * For example, if the property is a List<String> and the nestingLevel is 1, the nested type descriptor will be String.class. - * If the property is a List> and the nestingLevel is 2, the nested type descriptor will also be a String.class. - * If the field is a Map and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value. - * If the property is a List> and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value. - * Returns null if a nested type cannot be obtained because it was not declared. - * For example, if the property is a List<?>, the nested type descriptor returned will be null. + *

For example, if the property is a List<String> and the nestingLevel is 1, the nested type descriptor will be String.class. + * If the property is a List<List<String>> and the nestingLevel is 2, the nested type descriptor will also be a String.class. + * If the property is a Map<Integer, String> and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value. + * If the property is a List<Map<Integer, String>> and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value. + * Returns null if a nested type cannot be obtained because it was not declared. + * For example, if the property is a List<?>, the nested type descriptor returned will be null. * @param property the property * @param nestingLevel the nesting level of the collection/array element or map key/value declaration within the property. - * @return the nested type descriptor at the specified nestingLevel, or null if it could not be obtained + * @return the nested type descriptor at the specified nestingLevel, or null if it could not be obtained * @throws IllegalArgumentException if the types up to the specified nesting level are not of collection, array, or map types. */ public static TypeDescriptor nested(Property property, int nestingLevel) {