From 01c98c3bfb6fc3f970dc66055b8435a47a876e31 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Thu, 6 Jan 2011 05:14:49 +0000 Subject: [PATCH] added initial support for handling unknown nested type values when converting collections; now favor factory method for constructing nested type descriptors for clarity (made constructor private); improved javadoc --- .../beans/BeanWrapperImpl.java | 17 +-- .../core/convert/TypeDescriptor.java | 100 +++++++++++------- .../CollectionToCollectionConverter.java | 7 +- .../support/GenericConversionService.java | 2 +- .../support/PropertyTypeDescriptor.java | 28 +++-- .../GenericConversionServiceTests.java | 13 +++ .../spel/support/ReflectionHelper.java | 4 +- 7 files changed, 107 insertions(+), 64 deletions(-) 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 c13f8d83ae3..afc26ea2d9f 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 @@ -367,12 +367,11 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra String actualPropertyName = PropertyAccessorUtils.getPropertyName(propertyName); PropertyDescriptor pd = getPropertyDescriptorInternal(actualPropertyName); if (pd != null) { - Class type = getPropertyType(propertyName); if (pd.getReadMethod() != null) { - return new PropertyTypeDescriptor(type, new MethodParameter(pd.getReadMethod(), -1), pd); + return new PropertyTypeDescriptor(new MethodParameter(pd.getReadMethod(), -1), pd); } else if (pd.getWriteMethod() != null) { - return new PropertyTypeDescriptor(type, BeanUtils.getWriteMethodParameter(pd), pd); + return new PropertyTypeDescriptor(BeanUtils.getWriteMethodParameter(pd), pd); } } } @@ -935,10 +934,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra if (isExtractOldValueForEditor()) { oldValue = Array.get(propValue, arrayIndex); } - MethodParameter methodParameter = new MethodParameter(pd.getReadMethod(), -1); - methodParameter.increaseNestingLevel(); Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, - new PropertyTypeDescriptor(requiredType, methodParameter, pd)); + PropertyTypeDescriptor.forNestedType(requiredType, new MethodParameter(pd.getReadMethod(), -1), pd)); Array.set(propValue, arrayIndex, convertedValue); } catch (IndexOutOfBoundsException ex) { @@ -956,10 +953,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra if (isExtractOldValueForEditor() && index < list.size()) { oldValue = list.get(index); } - MethodParameter methodParameter = new MethodParameter(pd.getReadMethod(), -1); - methodParameter.increaseNestingLevel(); Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, - new PropertyTypeDescriptor(requiredType, methodParameter, pd)); + PropertyTypeDescriptor.forNestedType(requiredType, new MethodParameter(pd.getReadMethod(), -1), pd)); if (index < list.size()) { list.set(index, convertedValue); } @@ -995,11 +990,9 @@ 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. - MethodParameter methodParameter = new MethodParameter(pd.getReadMethod(), -1); - methodParameter.increaseNestingLevel(); Object convertedMapValue = convertIfNecessary( propertyName, oldValue, pv.getValue(), mapValueType, - new PropertyTypeDescriptor(mapValueType, methodParameter, pd)); + PropertyTypeDescriptor.forNestedType(mapValueType, new MethodParameter(pd.getReadMethod(), -1), pd)); map.put(convertedMapKey, convertedMapValue); } else { 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 d789455aa28..588aa5224d4 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 @@ -86,8 +86,7 @@ public class TypeDescriptor { /** * Create a new type descriptor from a method or constructor parameter. - *

Use this constructor when a target conversion point originates from a method parameter, - * such as a setter method argument. + * Use this constructor when a target conversion point originates from a method parameter, such as a setter method argument. * @param methodParameter the MethodParameter to wrap */ public TypeDescriptor(MethodParameter methodParameter) { @@ -106,24 +105,17 @@ public class TypeDescriptor { this.type = field.getType(); this.field = field; } - - /** - * Create a new type descriptor for the given class. - * @param type the class - * @return the type descriptor - */ - public static TypeDescriptor valueOf(Class type) { - if (type == null) { - return NULL; - } - TypeDescriptor desc = typeDescriptorCache.get(type); - return (desc != null ? desc : new TypeDescriptor(type)); - } /** - * Create a new type descriptor for the class of the given object. - * @param object the object + * Create a new type descriptor for object. + * Use this factory method to introspect a source object's type before asking the conversion system to convert it to some another type. + * If the object is null, returns {@link TypeDescriptor#NULL}. + * If the object is not a collection, simply calls {@link #valueOf(Class)}. + * If the object is a collection, this factory method will derive the element type(s) by introspecting the collection. + * @param object the source object * @return the type descriptor + * @see ConversionService#convert(Object, Class) + * @see CollectionUtils#findCommonElementType(Collection) */ public static TypeDescriptor forObject(Object object) { if (object == null) { @@ -139,6 +131,32 @@ public class TypeDescriptor { return valueOf(object.getClass()); } } + + /** + * Create a new type descriptor for a nested type declared on an array, collection, or map-based method parameter. + * Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted. + * @param nestedType the nested type + * @param parentMethodParameter the parent method parameter declaring the collection or map + * @return the nested type descriptor + */ + public static TypeDescriptor forNestedType(Class nestedType, MethodParameter parentMethodParameter) { + return new TypeDescriptor(nestedType, parentMethodParameter); + } + + /** + * Create a new type descriptor for the given class. + * Use this to instruct the conversion system to convert to an object to a specific target type, when no type location such as a method parameter or field is available. + * Generally prefer use of {@link #forObject(Object)} for constructing source type descriptors for source objects. + * @param type the class + * @return the type descriptor + */ + public static TypeDescriptor valueOf(Class type) { + if (type == null) { + return NULL; + } + TypeDescriptor desc = typeDescriptorCache.get(type); + return (desc != null ? desc : new TypeDescriptor(type)); + } /** * Determine the declared (non-generic) type of the wrapped parameter/field. @@ -309,17 +327,6 @@ public class TypeDescriptor { // special case public operations - /** - * Constructs a new TypeDescriptor for a nested type declared within a method parameter, such as a collection type or map key or value type. - */ - public TypeDescriptor(Class nestedType, MethodParameter methodParameter) { - if (nestedType == null) { - nestedType = Object.class; - } - this.type = nestedType; - this.methodParameter = methodParameter; - } - /** * Exposes the underlying MethodParameter providing context for this TypeDescriptor. * Used to support legacy code scenarios where callers are already using the MethodParameter API (BeanWrapper). @@ -402,6 +409,11 @@ public class TypeDescriptor { } // subclassing hooks + + protected TypeDescriptor(Class nestedType, MethodParameter parentMethodParameter) { + this.type = handleUnknownNestedType(nestedType); + this.methodParameter = createNestedMethodParameter(parentMethodParameter); + } protected Annotation[] resolveAnnotations() { if (this.field != null) { @@ -446,13 +458,9 @@ public class TypeDescriptor { } private TypeDescriptor createNestedTypeDescriptor(Class nestedType) { - if (nestedType == null) { - nestedType = Object.class; - } + nestedType = handleUnknownNestedType(nestedType); if (this.methodParameter != null) { - MethodParameter nested = new MethodParameter(this.methodParameter); - nested.increaseNestingLevel(); - return newNestedTypeDescriptor(nestedType, nested); + return newNestedTypeDescriptor(nestedType, createNestedMethodParameter(this.methodParameter)); } else if (this.field != null) { return new TypeDescriptor(nestedType, this.field, this.fieldNestingLevel + 1); @@ -461,7 +469,7 @@ public class TypeDescriptor { return TypeDescriptor.valueOf(nestedType); } } - + private Class resolveCollectionElementType() { if (this.methodParameter != null) { return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter); @@ -498,14 +506,18 @@ public class TypeDescriptor { } } - // internal constructors - - private TypeDescriptor(Class nestedType, Field field, int nestingLevel) { - this.type = nestedType; - this.field = field; - this.fieldNestingLevel = nestingLevel; + private Class handleUnknownNestedType(Class nestedType) { + return nestedType != null ? nestedType : Object.class; } + private MethodParameter createNestedMethodParameter(MethodParameter parentMethodParameter) { + MethodParameter methodParameter = new MethodParameter(parentMethodParameter); + methodParameter.increaseNestingLevel(); + return methodParameter; + } + + // internal constructors + private TypeDescriptor() { } @@ -534,4 +546,10 @@ public class TypeDescriptor { this.mapValueType = TypeDescriptor.valueOf(valueType); } + private TypeDescriptor(Class nestedType, Field field, int nestingLevel) { + this.type = nestedType; + this.field = field; + this.fieldNestingLevel = nestingLevel; + } + } \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java index 4b573b10517..2e2fcdc7520 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java @@ -49,7 +49,12 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor()); + TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor(); + TypeDescriptor targetElementType = targetType.getElementTypeDescriptor(); + if (Object.class.equals(sourceElementType.getType()) || Object.class.equals(targetElementType.getType())) { + return true; + } + return this.conversionService.canConvert(sourceElementType, targetElementType); } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index 9804ef78a3e..e0d30af05bf 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -173,7 +173,7 @@ public class GenericConversionService implements ConversionService, ConverterReg Assert.isTrue(source == null || sourceType.getObjectType().isInstance(source)); GenericConverter converter = getConverter(sourceType, targetType); if (converter == null) { - if (source == null || targetType.getObjectType().isInstance(source)) { + if (source == null || sourceType.isAssignableTo(targetType)) { logger.debug("No converter found - returning assignable source object as-is"); return source; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java index 3eef94f6bcd..d4f3631b3a7 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java @@ -34,6 +34,7 @@ import org.springframework.util.StringUtils; * (getter/setter) and on the underlying field, if found. * * @author Juergen Hoeller + * @author Keith Donald * @since 3.0.2 */ public class PropertyTypeDescriptor extends TypeDescriptor { @@ -41,18 +42,24 @@ public class PropertyTypeDescriptor extends TypeDescriptor { private final PropertyDescriptor propertyDescriptor; /** - * Create a new BeanTypeDescriptor for the given bean property. - * @param propertyDescriptor the corresponding JavaBean PropertyDescriptor + * Create a new type descriptor for the given bean property. * @param methodParameter the target method parameter + * @param propertyDescriptor the corresponding JavaBean PropertyDescriptor */ public PropertyTypeDescriptor(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) { super(methodParameter); this.propertyDescriptor = propertyDescriptor; } - - public PropertyTypeDescriptor(Class type, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) { - super(type, methodParameter); - this.propertyDescriptor = propertyDescriptor; + + /** + * Create a new type descriptor for a nested type declared on an array, collection, or map-based property. + * Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted. + * @param nestedType the nested type + * @param parentMethodParameter the parent property's method parameter that declares the collection or map + * @return the parent property descriptor + */ + public static PropertyTypeDescriptor forNestedType(Class nestedType, MethodParameter propertyMethodParameter, PropertyDescriptor propertyDescriptor) { + return new PropertyTypeDescriptor(nestedType, propertyMethodParameter, propertyDescriptor); } /** @@ -102,8 +109,15 @@ public class PropertyTypeDescriptor extends TypeDescriptor { return annMap.values().toArray(new Annotation[annMap.size()]); } - public TypeDescriptor newNestedTypeDescriptor(Class nestedType, MethodParameter nested) { + protected TypeDescriptor newNestedTypeDescriptor(Class nestedType, MethodParameter nested) { return new PropertyTypeDescriptor(nestedType, nested, this.propertyDescriptor); } + // internal constructors + + private PropertyTypeDescriptor(Class nestedType, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) { + super(nestedType, methodParameter); + this.propertyDescriptor = propertyDescriptor; + } + } diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index 71ff37da78f..3ca6aa040b5 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java @@ -382,6 +382,19 @@ public class GenericConversionServiceTests { public static Map map; + @Test + public void emptyList() throws Exception { + conversionService.addConverter(new CollectionToCollectionConverter(conversionService)); + conversionService.addConverterFactory(new StringToNumberConverterFactory()); + List list = new ArrayList(); + TypeDescriptor sourceType = TypeDescriptor.forObject(list); + TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyListTarget")); + assertTrue(conversionService.canConvert(sourceType, targetType)); + assertEquals(list, conversionService.convert(list, sourceType, targetType)); + } + + public List emptyListTarget; + private interface MyBaseInterface { } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index 8f7debd0ac8..a9e9a08a2e3 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -236,7 +236,7 @@ public class ReflectionHelper { TypeDescriptor targetType; if (varargsPosition != null && argPosition >= varargsPosition) { MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition); - targetType = new TypeDescriptor(methodParam.getParameterType().getComponentType(), methodParam); + targetType = TypeDescriptor.forNestedType(methodParam.getParameterType().getComponentType(), methodParam); } else { targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, argPosition)); @@ -268,7 +268,7 @@ public class ReflectionHelper { TypeDescriptor targetType; if (varargsPosition != null && argPosition >= varargsPosition) { MethodParameter methodParam = new MethodParameter(method, varargsPosition); - targetType = new TypeDescriptor(methodParam.getParameterType().getComponentType(), methodParam); + targetType = TypeDescriptor.forNestedType(methodParam.getParameterType().getComponentType(), methodParam); } else { targetType = new TypeDescriptor(new MethodParameter(method, argPosition));