master
Keith Donald 16 years ago
parent eabad33aa2
commit 518f98d4c3
  1. 111
      org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
  2. 33
      org.springframework.core/src/main/java/org/springframework/core/convert/TypedValue.java
  3. 29
      org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java
  4. 6
      org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java
  5. 88
      org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.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 <code>null</code> 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);
}
}
}

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

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

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

@ -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<Integer> genericList = new ArrayList<Integer>();
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);
}
}
Loading…
Cancel
Save