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

This commit is contained in:
Keith Donald 2011-01-06 05:14:49 +00:00
parent 9420bdc246
commit 01c98c3bfb
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);
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 {

View File

@ -86,8 +86,7 @@ public class TypeDescriptor {
/**
* 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,
* 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;
}
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -382,6 +382,19 @@ public class GenericConversionServiceTests {
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 {
}

View File

@ -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));