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 9620b75e03..a8cedc6b27 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
@@ -10,7 +10,7 @@ import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
/**
- * Metadata about a retrieved value or value type.
+ * Type metadata about a bindable value.
*
* @author Keith Donald
*/
@@ -24,13 +24,18 @@ public class TypeDescriptor {
private Class> type;
+ /**
+ * Creates a new descriptor for the given type.
+ * Use this constructor when a bound value comes from a source such as a Map or collection, where no additional binding metadata is available.
+ * @param type the actual type
+ */
public TypeDescriptor(Class> type) {
this.type = type;
}
/**
* Create a new descriptor for a method or constructor parameter.
- *
+ * Use this constructor when a bound value originates from a method parameter, such as a setter method argument.
* @param methodParameter the MethodParameter to wrap
*/
public TypeDescriptor(MethodParameter methodParameter) {
@@ -39,8 +44,8 @@ public class TypeDescriptor {
}
/**
- * Create a new descriptor for a field. Considers the dependency as 'eager'.
- *
+ * Create a new descriptor for a field.
+ * Use this constructor when a bound value originates from a field.
* @param field the field to wrap
*/
public TypeDescriptor(Field field) {
@@ -63,6 +68,10 @@ public class TypeDescriptor {
}
}
+ /**
+ * If the actual type is a primitive, returns its wrapper type, else just returns {@link #getType()}.
+ * @return the wrapper type if the underlying type is a primitive, else the actual type as-is
+ */
public Class> getWrapperTypeIfPrimitive() {
Class> type = getType();
if (type.isPrimitive()) {
@@ -90,42 +99,41 @@ public class TypeDescriptor {
}
}
+ /**
+ * Returns the name of this type; the fully qualified classname.
+ */
public String getName() {
return getType().getName();
}
-
+
+ /**
+ * Is this type an array type?
+ */
public boolean isArray() {
return getType().isArray();
}
-
- public Class> getElementType() {
- return isArray() ? getArrayComponentType() : getCollectionElementType();
- }
-
- public Class> getArrayComponentType() {
- return getType().getComponentType();
- }
- public boolean isInstance(Object source) {
- return getType().isInstance(source);
+ /**
+ * Is this type a {@link Collection} type?
+ */
+ public boolean isCollection() {
+ return Collection.class.isAssignableFrom(getType());
}
-
/**
- * Determine the generic element type of the wrapped Collection parameter/field, if any.
- *
- * @return the generic type, or null
if none
+ * If this type is an array type or {@link Collection} type, returns the underlying element type.
+ * Returns null if the type is neither an array or collection.
*/
- public Class> getCollectionElementType() {
- if (type != null) {
- return GenericCollectionTypeResolver.getCollectionType((Class extends Collection>) type);
- } else if (field != null) {
- return GenericCollectionTypeResolver.getCollectionFieldType(field);
+ public Class> getElementType() {
+ if (isArray()) {
+ return getArrayComponentType();
+ } else if (isCollection()) {
+ return getCollectionElementType();
} else {
- return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter);
+ return null;
}
}
-
+
/**
* Determine the generic key type of the wrapped Map parameter/field, if any.
*
@@ -182,12 +190,53 @@ public class TypeDescriptor {
return field;
}
- public boolean isCollection() {
- return Collection.class.isAssignableFrom(getType());
- }
-
+ /**
+ * Returns true if this type is an abstract class.
+ */
public boolean isAbstractClass() {
return !getType().isInterface() && Modifier.isAbstract(getType().getModifiers());
}
-}
+ /**
+ * Is the obj an instance of this type?
+ */
+ public boolean isInstance(Object obj) {
+ return getType().isInstance(obj);
+ }
+
+ /**
+ * Returns true if an object this type can be assigned to a rereference of given targetType.
+ * @param targetType the target type
+ * @return true if this type is assignable to the target
+ */
+ public boolean isAssignableTo(TypeDescriptor targetType) {
+ return targetType.getType().isAssignableFrom(getType());
+ }
+
+
+ /**
+ * Creates a new type descriptor for the given class.
+ * @param type the class
+ * @return the type descriptor
+ */
+ public static TypeDescriptor valueOf(Class extends Object> type) {
+ return new TypeDescriptor(type);
+ }
+
+ // internal helpers
+
+ private Class> getArrayComponentType() {
+ return getType().getComponentType();
+ }
+
+ private Class> getCollectionElementType() {
+ if (type != null) {
+ return GenericCollectionTypeResolver.getCollectionType((Class extends Collection>) type);
+ } else if (field != null) {
+ return GenericCollectionTypeResolver.getCollectionFieldType(field);
+ } else {
+ return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/TypedValue.java b/org.springframework.core/src/main/java/org/springframework/core/convert/TypedValue.java
index ebdbe93467..4a0ace1ba2 100644
--- a/org.springframework.core/src/main/java/org/springframework/core/convert/TypedValue.java
+++ b/org.springframework.core/src/main/java/org/springframework/core/convert/TypedValue.java
@@ -1,18 +1,40 @@
package org.springframework.core.convert;
/**
- * A value retrieved from some location such as a field access or getter invocation.
+ * A value with additional context about its type.
+ * The additional context provides information about how the value was obtained; for example from a field read or getter return value.
+ * This additional context allows access to generic information associated with a field, return value, or method argument.
+ * It also allows access to field-level or method-level annotations.
+ * All of this context can be utilized when performing a type conversion as part of a data binding routine.
*
* @author Keith Donald
*/
public class TypedValue {
+ /**
+ * The NULL TypedValue object.
+ */
+ public static final TypedValue NULL = new TypedValue(null);
+
private final Object value;
private final TypeDescriptor typeDescriptor;
/**
- * Creates a typed value.
+ * Creates a new typed value.
+ * @param value the actual value (may be null)
+ */
+ public TypedValue(Object value) {
+ this.value = value;
+ if (this.value != null) {
+ typeDescriptor = TypeDescriptor.valueOf(value.getClass());
+ } else {
+ typeDescriptor = null;
+ }
+ }
+
+ /**
+ * Creates a new typed value.
* @param value the actual value (may be null)
* @param typeDescriptor the value type descriptor (may be null)
*/
@@ -35,4 +57,11 @@ public class TypedValue {
return typeDescriptor;
}
+ /**
+ * True if both the actual value and type descriptor are null.
+ */
+ public boolean isNull() {
+ return value == null && typeDescriptor == null;
+ }
+
}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java
index 50935ff868..696b53add4 100644
--- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java
+++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java
@@ -167,11 +167,19 @@ public class GenericConversionService implements ConversionService {
public Object executeConversion(TypedValue source, TypeDescriptor targetType) throws ConversionExecutorNotFoundException,
ConversionException {
+ Assert.notNull(source, "The source to convert from is required");
+ if (source.isNull()) {
+ return null;
+ }
return getConversionExecutor(source.getTypeDescriptor(), targetType).execute(source.getValue());
}
public Object executeConversion(String converterId, TypedValue source, TypeDescriptor targetType)
throws ConversionExecutorNotFoundException, ConversionException {
+ Assert.notNull(source, "The source to convert from is required");
+ if (source.isNull()) {
+ return null;
+ }
return getConversionExecutor(converterId, source.getTypeDescriptor(), targetType).execute(source.getValue());
}
@@ -206,21 +214,20 @@ public class GenericConversionService implements ConversionService {
throw new UnsupportedOperationException("Object to collection not yet supported");
}
}
- Class> sourceClass = sourceType.getWrapperTypeIfPrimitive();
- Class> targetClass = targetType.getWrapperTypeIfPrimitive();
- Converter converter = findRegisteredConverter(sourceClass, targetClass);
+ Converter converter = findRegisteredConverter(sourceType, targetType);
if (converter != null) {
- // we found a converter
return new StaticConversionExecutor(sourceType, targetType, converter);
} else {
- SuperConverter superConverter = findRegisteredSuperConverter(sourceClass, targetClass);
+ SuperConverter superConverter = findRegisteredSuperConverter(sourceType, targetType);
if (superConverter != null) {
return new StaticSuperConversionExecutor(sourceType, targetType, superConverter);
}
if (parent != null) {
- // try the parent
return parent.getConversionExecutor(sourceType, targetType);
} else {
+ if (sourceType.isAssignableTo(targetType)) {
+ return new StaticConversionExecutor(sourceType, targetType, NoOpConverter.INSTANCE);
+ }
throw new ConversionExecutorNotFoundException(sourceType, targetType,
"No ConversionExecutor found for converting from sourceType [" + sourceType.getName()
+ "] to targetType [" + targetType.getName() + "]");
@@ -306,7 +313,9 @@ public class GenericConversionService implements ConversionService {
return sourceMap;
}
- private Converter findRegisteredConverter(Class sourceClass, Class targetClass) {
+ private Converter findRegisteredConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ Class> sourceClass = sourceType.getWrapperTypeIfPrimitive();
+ Class> targetClass = targetType.getWrapperTypeIfPrimitive();
if (sourceClass.isInterface()) {
LinkedList classQueue = new LinkedList();
classQueue.addFirst(sourceClass);
@@ -355,7 +364,9 @@ public class GenericConversionService implements ConversionService {
return (Converter) converters.get(targetClass);
}
- private SuperConverter findRegisteredSuperConverter(Class sourceClass, Class targetClass) {
+ private SuperConverter findRegisteredSuperConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ Class> sourceClass = sourceType.getWrapperTypeIfPrimitive();
+ Class> targetClass = targetType.getWrapperTypeIfPrimitive();
if (sourceClass.isInterface()) {
LinkedList classQueue = new LinkedList();
classQueue.addFirst(sourceClass);
@@ -441,7 +452,7 @@ public class GenericConversionService implements ConversionService {
}
public ConversionExecutor getElementConverter(Class> sourceElementType, Class> targetElementType) {
- return getConversionExecutor(new TypeDescriptor(sourceElementType), new TypeDescriptor(targetElementType));
+ return getConversionExecutor(TypeDescriptor.valueOf(sourceElementType), TypeDescriptor.valueOf(targetElementType));
}
}
\ No newline at end of file
diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java
index b9f8df8780..dab3ee15ef 100644
--- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java
+++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java
@@ -20,6 +20,12 @@ import org.springframework.core.convert.converter.Converter;
@SuppressWarnings("unchecked")
class NoOpConverter implements Converter {
+ public static final Converter INSTANCE = new NoOpConverter();
+
+ private NoOpConverter() {
+
+ }
+
public Object convert(Object source) throws Exception {
return source;
}
diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java
index f5c3836c72..e27c6cc6c3 100644
--- a/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java
+++ b/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java
@@ -24,6 +24,7 @@ import java.util.List;
import junit.framework.TestCase;
+import org.junit.Ignore;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionExecutionException;
import org.springframework.core.convert.ConversionExecutor;
@@ -35,25 +36,18 @@ import org.springframework.core.convert.converter.NumberToNumber;
import org.springframework.core.convert.converter.StringToEnum;
import org.springframework.core.convert.converter.StringToInteger;
-import org.junit.Ignore;
-
-@Ignore
public class GenericConversionServiceTests extends TestCase {
private GenericConversionService service = new GenericConversionService();
- private TypedValue value(Object obj) {
- return new TypedValue(obj, new TypeDescriptor(obj.getClass()));
- }
-
- private TypeDescriptor type(Class clazz) {
- return new TypeDescriptor(clazz);
- }
-
public void testExecuteConversion() {
service.addConverter(new StringToInteger());
assertEquals(new Integer(3), service.executeConversion(value("3"), type(Integer.class)));
}
+
+ public void testExecuteConversionNullSource() {
+ assertEquals(null, service.executeConversion(TypedValue.NULL, type(Integer.class)));
+ }
public void testConverterConversionForwardIndex() {
service.addConverter(new StringToInteger());
@@ -77,6 +71,23 @@ public class GenericConversionServiceTests extends TestCase {
}
}
+ public void testAddConverterNoSourceTargetClassInfoAvailable() {
+ try {
+ service.addConverter(new Converter() {
+ public Object convert(Object source) throws Exception {
+ return source;
+ }
+
+ public Object convertBack(Object target) throws Exception {
+ return target;
+ }
+ });
+ fail("Should have failed");
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
public void testConversionCompatibleTypes() {
String source = "foo";
assertSame(source, service.getConversionExecutor(type(String.class), type(String.class)).execute(source));
@@ -165,8 +176,11 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals("3", result.get(2));
}
- public void testConversionArrayToListGenericTypeConversion() {
- ConversionExecutor executor = service.getConversionExecutor(type(String[].class), type(List.class));
+ public List genericList = new ArrayList();
+
+ public void testConversionArrayToListGenericTypeConversion() throws Exception {
+ service.addConverter(new StringToInteger());
+ ConversionExecutor executor = service.getConversionExecutor(type(String[].class), new TypeDescriptor(getClass().getDeclaredField("genericList")));
List result = (List) executor.execute(new String[] { "1", "2", "3" });
assertEquals(new Integer("1"), result.get(0));
assertEquals(new Integer("2"), result.get(1));
@@ -214,6 +228,7 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals(new Integer(3), result[2]);
}
+ @Ignore
public void testConversionObjectToArray() {
ConversionExecutor executor = service.getConversionExecutor(type(String.class), type(String[].class));
String[] result = (String[]) executor.execute("1,2,3");
@@ -221,6 +236,7 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals("1,2,3", result[0]);
}
+ @Ignore
public void testConversionObjectToArrayWithElementConversion() {
service.addConverter(new StringToInteger());
ConversionExecutor executor = service.getConversionExecutor(type(String.class), type(Integer[].class));
@@ -288,29 +304,14 @@ public class GenericConversionServiceTests extends TestCase {
}
}
- public void testAddConverterNoSourceTargetClassInfoAvailable() {
- try {
- service.addConverter(new Converter() {
- public Object convert(Object source) throws Exception {
- return source;
- }
-
- public Object convertBack(Object target) throws Exception {
- return target;
- }
- });
- fail("Should have failed");
- } catch (IllegalArgumentException e) {
-
- }
- }
-
+ @Ignore
public void testCustomConverterConversionForwardIndex() {
service.addConverter("princy", new CustomTwoWayConverter());
ConversionExecutor executor = service.getConversionExecutor("princy", type(String.class), type(Principal.class));
assertEquals("keith", ((Principal) executor.execute("keith")).getName());
}
+ @Ignore
public void testCustomConverterConversionReverseIndex() {
service.addConverter("princy", new CustomTwoWayConverter());
ConversionExecutor executor = service.getConversionExecutor("princy", type(Principal.class), type(String.class));
@@ -321,12 +322,14 @@ public class GenericConversionServiceTests extends TestCase {
}));
}
+ @Ignore
public void testCustomConverterConversionForSameType() {
service.addConverter("trimmer", new Trimmer());
ConversionExecutor executor = service.getConversionExecutor("trimmer", type(String.class), type(String.class));
assertEquals("a string", executor.execute("a string "));
}
+ @Ignore
public void testCustomConverterLookupNotCompatibleSource() {
service.addConverter("trimmer", new Trimmer());
try {
@@ -337,6 +340,7 @@ public class GenericConversionServiceTests extends TestCase {
}
}
+ @Ignore
public void testCustomConverterLookupNotCompatibleTarget() {
service.addConverter("trimmer", new Trimmer());
try {
@@ -346,6 +350,7 @@ public class GenericConversionServiceTests extends TestCase {
}
}
+ @Ignore
public void testCustomConverterLookupNotCompatibleTargetReverse() {
service.addConverter("princy", new CustomTwoWayConverter());
try {
@@ -355,6 +360,7 @@ public class GenericConversionServiceTests extends TestCase {
}
}
+ @Ignore
public void testCustomConverterConversionArrayToArray() {
service.addConverter("princy", new CustomTwoWayConverter());
ConversionExecutor executor = service.getConversionExecutor("princy", type(String[].class), type(Principal[].class));
@@ -363,6 +369,7 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals("princy2", p[1].getName());
}
+ @Ignore
public void testCustomConverterConversionArrayToArrayReverse() {
service.addConverter("princy", new CustomTwoWayConverter());
ConversionExecutor executor = service.getConversionExecutor("princy", type(Principal[].class), type(String[].class));
@@ -381,6 +388,7 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals("princy2", p[1]);
}
+ @Ignore
public void testCustomConverterLookupArrayToArrayBogusSource() {
service.addConverter("princy", new CustomTwoWayConverter());
try {
@@ -390,6 +398,7 @@ public class GenericConversionServiceTests extends TestCase {
}
}
+ @Ignore
public void testCustomConverterLookupArrayToArrayBogusTarget() {
service.addConverter("princy", new CustomTwoWayConverter());
try {
@@ -399,6 +408,7 @@ public class GenericConversionServiceTests extends TestCase {
}
}
+ @Ignore
public void testCustomConverterConversionArrayToCollection() {
service.addConverter("princy", new CustomTwoWayConverter());
ConversionExecutor executor = service.getConversionExecutor("princy", type(String[].class), type(List.class));
@@ -407,6 +417,7 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals("princy2", ((Principal) list.get(1)).getName());
}
+ @Ignore
public void testCustomConverterConversionArrayToCollectionReverse() {
service.addConverter("princy", new CustomTwoWayConverter());
ConversionExecutor executor = service.getConversionExecutor("princy", type(Principal[].class), type(List.class));
@@ -425,6 +436,7 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals("princy2", p.get(1));
}
+ @Ignore
public void testCustomConverterLookupArrayToCollectionBogusSource() {
service.addConverter("princy", new CustomTwoWayConverter());
try {
@@ -435,6 +447,7 @@ public class GenericConversionServiceTests extends TestCase {
}
}
+ @Ignore
public void testCustomConverterLookupCollectionToArray() {
service.addConverter("princy", new CustomTwoWayConverter());
ConversionExecutor executor = service.getConversionExecutor("princy", type(List.class), type(Principal[].class));
@@ -446,6 +459,7 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals("princy2", p[1].getName());
}
+ @Ignore
public void testCustomConverterLookupCollectionToArrayReverse() {
service.addConverter("princy", new CustomTwoWayConverter());
ConversionExecutor executor = service.getConversionExecutor("princy", type(List.class), type(String[].class));
@@ -467,7 +481,8 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals("princy2", p[1]);
}
- public void testtestCustomConverterLookupCollectionToArrayBogusTarget() {
+ @Ignore
+ public void testCustomConverterLookupCollectionToArrayBogusTarget() {
service.addConverter("princy", new CustomTwoWayConverter());
try {
service.getConversionExecutor("princy", type(List.class), type(Integer[].class));
@@ -477,6 +492,7 @@ public class GenericConversionServiceTests extends TestCase {
}
}
+ @Ignore
public void testCustomConverterConversionObjectToArray() {
service.addConverter("princy", new CustomTwoWayConverter());
ConversionExecutor executor = service.getConversionExecutor("princy", type(String.class), type(Principal[].class));
@@ -484,6 +500,7 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals("princy1", p[0].getName());
}
+ @Ignore
public void testCustomConverterConversionObjectToArrayReverse() {
service.addConverter("princy", new CustomTwoWayConverter());
ConversionExecutor executor = service.getConversionExecutor("princy", type(Principal.class), type(String[].class));
@@ -496,6 +513,7 @@ public class GenericConversionServiceTests extends TestCase {
assertEquals("princy1", p[0]);
}
+ @Ignore
public void testCustomConverterLookupObjectToArrayBogusSource() {
service.addConverter("princy", new CustomTwoWayConverter());
try {
@@ -540,4 +558,12 @@ public class GenericConversionServiceTests extends TestCase {
//assertEquals(FooEnum.BAR, service.executeConversion("BAR", FooEnum.class));
}
+ private TypedValue value(Object obj) {
+ return new TypedValue(obj);
+ }
+
+ private TypeDescriptor type(Class> clazz) {
+ return TypeDescriptor.valueOf(clazz);
+ }
+
}
\ No newline at end of file