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

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@3867 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
Keith Donald 2011-01-06 05:14:49 +00:00
parent 480aa5d993
commit ed2a257e69
7 changed files with 107 additions and 64 deletions

View File

@ -367,12 +367,11 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
String actualPropertyName = PropertyAccessorUtils.getPropertyName(propertyName); String actualPropertyName = PropertyAccessorUtils.getPropertyName(propertyName);
PropertyDescriptor pd = getPropertyDescriptorInternal(actualPropertyName); PropertyDescriptor pd = getPropertyDescriptorInternal(actualPropertyName);
if (pd != null) { if (pd != null) {
Class type = getPropertyType(propertyName);
if (pd.getReadMethod() != null) { 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) { 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()) { if (isExtractOldValueForEditor()) {
oldValue = Array.get(propValue, arrayIndex); oldValue = Array.get(propValue, arrayIndex);
} }
MethodParameter methodParameter = new MethodParameter(pd.getReadMethod(), -1);
methodParameter.increaseNestingLevel();
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, 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); Array.set(propValue, arrayIndex, convertedValue);
} }
catch (IndexOutOfBoundsException ex) { catch (IndexOutOfBoundsException ex) {
@ -956,10 +953,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
if (isExtractOldValueForEditor() && index < list.size()) { if (isExtractOldValueForEditor() && index < list.size()) {
oldValue = list.get(index); oldValue = list.get(index);
} }
MethodParameter methodParameter = new MethodParameter(pd.getReadMethod(), -1);
methodParameter.increaseNestingLevel();
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, 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()) { if (index < list.size()) {
list.set(index, convertedValue); 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 // Pass full property name and old value in here, since we want full
// conversion ability for map values. // conversion ability for map values.
MethodParameter methodParameter = new MethodParameter(pd.getReadMethod(), -1);
methodParameter.increaseNestingLevel();
Object convertedMapValue = convertIfNecessary( Object convertedMapValue = convertIfNecessary(
propertyName, oldValue, pv.getValue(), mapValueType, propertyName, oldValue, pv.getValue(), mapValueType,
new PropertyTypeDescriptor(mapValueType, methodParameter, pd)); PropertyTypeDescriptor.forNestedType(mapValueType, new MethodParameter(pd.getReadMethod(), -1), pd));
map.put(convertedMapKey, convertedMapValue); map.put(convertedMapKey, convertedMapValue);
} }
else { else {

View File

@ -86,8 +86,7 @@ public class TypeDescriptor {
/** /**
* Create a new type descriptor from a method or constructor parameter. * Create a new type descriptor from a method or constructor parameter.
* <p>Use this constructor when a target conversion point originates from a method parameter, * Use this constructor when a target conversion point originates from a method parameter, such as a setter method argument.
* such as a setter method argument.
* @param methodParameter the MethodParameter to wrap * @param methodParameter the MethodParameter to wrap
*/ */
public TypeDescriptor(MethodParameter methodParameter) { public TypeDescriptor(MethodParameter methodParameter) {
@ -108,22 +107,15 @@ public class TypeDescriptor {
} }
/** /**
* Create a new type descriptor for the given class. * Create a new type descriptor for object.
* @param type the class * Use this factory method to introspect a source object's type before asking the conversion system to convert it to some another type.
* @return the type descriptor * If the object is null, returns {@link TypeDescriptor#NULL}.
*/ * If the object is not a collection, simply calls {@link #valueOf(Class)}.
public static TypeDescriptor valueOf(Class<?> type) { * If the object is a collection, this factory method will derive the element type(s) by introspecting the collection.
if (type == null) { * @param object the source object
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
* @return the type descriptor * @return the type descriptor
* @see ConversionService#convert(Object, Class)
* @see CollectionUtils#findCommonElementType(Collection)
*/ */
public static TypeDescriptor forObject(Object object) { public static TypeDescriptor forObject(Object object) {
if (object == null) { if (object == null) {
@ -140,6 +132,32 @@ public class TypeDescriptor {
} }
} }
/**
* 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. * Determine the declared (non-generic) type of the wrapped parameter/field.
* @return the declared type, or <code>null</code> if this is {@link TypeDescriptor#NULL} * @return the declared type, or <code>null</code> if this is {@link TypeDescriptor#NULL}
@ -309,17 +327,6 @@ public class TypeDescriptor {
// special case public operations // 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. * Exposes the underlying MethodParameter providing context for this TypeDescriptor.
* Used to support legacy code scenarios where callers are already using the MethodParameter API (BeanWrapper). * Used to support legacy code scenarios where callers are already using the MethodParameter API (BeanWrapper).
@ -403,6 +410,11 @@ public class TypeDescriptor {
// subclassing hooks // subclassing hooks
protected TypeDescriptor(Class<?> nestedType, MethodParameter parentMethodParameter) {
this.type = handleUnknownNestedType(nestedType);
this.methodParameter = createNestedMethodParameter(parentMethodParameter);
}
protected Annotation[] resolveAnnotations() { protected Annotation[] resolveAnnotations() {
if (this.field != null) { if (this.field != null) {
return this.field.getAnnotations(); return this.field.getAnnotations();
@ -446,13 +458,9 @@ public class TypeDescriptor {
} }
private TypeDescriptor createNestedTypeDescriptor(Class<?> nestedType) { private TypeDescriptor createNestedTypeDescriptor(Class<?> nestedType) {
if (nestedType == null) { nestedType = handleUnknownNestedType(nestedType);
nestedType = Object.class;
}
if (this.methodParameter != null) { if (this.methodParameter != null) {
MethodParameter nested = new MethodParameter(this.methodParameter); return newNestedTypeDescriptor(nestedType, createNestedMethodParameter(this.methodParameter));
nested.increaseNestingLevel();
return newNestedTypeDescriptor(nestedType, nested);
} }
else if (this.field != null) { else if (this.field != null) {
return new TypeDescriptor(nestedType, this.field, this.fieldNestingLevel + 1); return new TypeDescriptor(nestedType, this.field, this.fieldNestingLevel + 1);
@ -498,14 +506,18 @@ public class TypeDescriptor {
} }
} }
// internal constructors private Class<?> handleUnknownNestedType(Class<?> nestedType) {
return nestedType != null ? nestedType : Object.class;
private TypeDescriptor(Class<?> nestedType, Field field, int nestingLevel) {
this.type = nestedType;
this.field = field;
this.fieldNestingLevel = nestingLevel;
} }
private MethodParameter createNestedMethodParameter(MethodParameter parentMethodParameter) {
MethodParameter methodParameter = new MethodParameter(parentMethodParameter);
methodParameter.increaseNestingLevel();
return methodParameter;
}
// internal constructors
private TypeDescriptor() { private TypeDescriptor() {
} }
@ -534,4 +546,10 @@ public class TypeDescriptor {
this.mapValueType = TypeDescriptor.valueOf(valueType); this.mapValueType = TypeDescriptor.valueOf(valueType);
} }
private TypeDescriptor(Class<?> nestedType, Field field, int nestingLevel) {
this.type = nestedType;
this.field = field;
this.fieldNestingLevel = nestingLevel;
}
} }

View File

@ -49,7 +49,12 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert
} }
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { 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) { public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {

View File

@ -173,7 +173,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
Assert.isTrue(source == null || sourceType.getObjectType().isInstance(source)); Assert.isTrue(source == null || sourceType.getObjectType().isInstance(source));
GenericConverter converter = getConverter(sourceType, targetType); GenericConverter converter = getConverter(sourceType, targetType);
if (converter == null) { 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"); logger.debug("No converter found - returning assignable source object as-is");
return source; return source;
} }

View File

@ -34,6 +34,7 @@ import org.springframework.util.StringUtils;
* (getter/setter) and on the underlying field, if found. * (getter/setter) and on the underlying field, if found.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Keith Donald
* @since 3.0.2 * @since 3.0.2
*/ */
public class PropertyTypeDescriptor extends TypeDescriptor { public class PropertyTypeDescriptor extends TypeDescriptor {
@ -41,18 +42,24 @@ public class PropertyTypeDescriptor extends TypeDescriptor {
private final PropertyDescriptor propertyDescriptor; private final PropertyDescriptor propertyDescriptor;
/** /**
* Create a new BeanTypeDescriptor for the given bean property. * Create a new type descriptor for the given bean property.
* @param propertyDescriptor the corresponding JavaBean PropertyDescriptor
* @param methodParameter the target method parameter * @param methodParameter the target method parameter
* @param propertyDescriptor the corresponding JavaBean PropertyDescriptor
*/ */
public PropertyTypeDescriptor(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) { public PropertyTypeDescriptor(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
super(methodParameter); super(methodParameter);
this.propertyDescriptor = propertyDescriptor; this.propertyDescriptor = propertyDescriptor;
} }
public PropertyTypeDescriptor(Class<?> type, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) { /**
super(type, methodParameter); * Create a new type descriptor for a nested type declared on an array, collection, or map-based property.
this.propertyDescriptor = propertyDescriptor; * 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()]); 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); return new PropertyTypeDescriptor(nestedType, nested, this.propertyDescriptor);
} }
// internal constructors
private PropertyTypeDescriptor(Class<?> nestedType, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
super(nestedType, methodParameter);
this.propertyDescriptor = propertyDescriptor;
}
} }

View File

@ -382,6 +382,19 @@ public class GenericConversionServiceTests {
public static Map<String, Integer> map; public static Map<String, Integer> map;
@Test
public void emptyList() throws Exception {
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverterFactory(new StringToNumberConverterFactory());
List<String> list = new ArrayList<String>();
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<Integer> emptyListTarget;
private interface MyBaseInterface { private interface MyBaseInterface {
} }

View File

@ -236,7 +236,7 @@ public class ReflectionHelper {
TypeDescriptor targetType; TypeDescriptor targetType;
if (varargsPosition != null && argPosition >= varargsPosition) { if (varargsPosition != null && argPosition >= varargsPosition) {
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition); MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition);
targetType = new TypeDescriptor(methodParam.getParameterType().getComponentType(), methodParam); targetType = TypeDescriptor.forNestedType(methodParam.getParameterType().getComponentType(), methodParam);
} }
else { else {
targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, argPosition)); targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, argPosition));
@ -268,7 +268,7 @@ public class ReflectionHelper {
TypeDescriptor targetType; TypeDescriptor targetType;
if (varargsPosition != null && argPosition >= varargsPosition) { if (varargsPosition != null && argPosition >= varargsPosition) {
MethodParameter methodParam = new MethodParameter(method, varargsPosition); MethodParameter methodParam = new MethodParameter(method, varargsPosition);
targetType = new TypeDescriptor(methodParam.getParameterType().getComponentType(), methodParam); targetType = TypeDescriptor.forNestedType(methodParam.getParameterType().getComponentType(), methodParam);
} }
else { else {
targetType = new TypeDescriptor(new MethodParameter(method, argPosition)); targetType = new TypeDescriptor(new MethodParameter(method, argPosition));