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