This commit is contained in:
Keith Donald
2009-03-31 15:06:22 +00:00
parent eabad33aa2
commit 518f98d4c3
5 changed files with 196 additions and 75 deletions

View File

@@ -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);
}
/**
* Determine the generic element type of the wrapped Collection parameter/field, if any.
*
* @return the generic type, or <code>null</code> if none
* Is this type a {@link Collection} type?
*/
public Class<?> getCollectionElementType() {
if (type != null) {
return GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection>) type);
} else if (field != null) {
return GenericCollectionTypeResolver.getCollectionFieldType(field);
public boolean isCollection() {
return Collection.class.isAssignableFrom(getType());
}
/**
* 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<?> 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);
}
}
}

View File

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

View File

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

View File

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