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:
parent
480aa5d993
commit
ed2a257e69
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue