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:
parent
9420bdc246
commit
01c98c3bfb
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
Loading…
Reference in New Issue