TypeDescriptor cleanup and general polishing; fixed a number of bugs related to TypeDescriptor usage in client code across beans and spel packages
This commit is contained in:
@@ -42,9 +42,6 @@ public class TypeDescriptor {
|
||||
/** Constant defining a TypeDescriptor for a <code>null</code> value */
|
||||
public static final TypeDescriptor NULL = new TypeDescriptor();
|
||||
|
||||
/** Constant defining a TypeDescriptor for 'unknown type' */
|
||||
private static final TypeDescriptor UNKNOWN = new TypeDescriptor(Object.class);
|
||||
|
||||
private static final Map<Class<?>, TypeDescriptor> typeDescriptorCache = new HashMap<Class<?>, TypeDescriptor>();
|
||||
|
||||
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
|
||||
@@ -78,8 +75,6 @@ public class TypeDescriptor {
|
||||
|
||||
private int fieldNestingLevel = 1;
|
||||
|
||||
private Object value;
|
||||
|
||||
private TypeDescriptor elementType;
|
||||
|
||||
private TypeDescriptor mapKeyType;
|
||||
@@ -97,6 +92,7 @@ public class TypeDescriptor {
|
||||
*/
|
||||
public TypeDescriptor(MethodParameter methodParameter) {
|
||||
Assert.notNull(methodParameter, "MethodParameter must not be null");
|
||||
this.type = methodParameter.getParameterType();
|
||||
this.methodParameter = methodParameter;
|
||||
}
|
||||
|
||||
@@ -107,112 +103,49 @@ public class TypeDescriptor {
|
||||
*/
|
||||
public TypeDescriptor(Field field) {
|
||||
Assert.notNull(field, "Field must not be null");
|
||||
this.type = field.getType();
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* such as a setter method argument.
|
||||
* @param methodParameter the MethodParameter to wrap
|
||||
* @param type the specific type to expose (may be an array/collection element)
|
||||
* Create a new type descriptor for the given class.
|
||||
* @param type the class
|
||||
* @return the type descriptor
|
||||
*/
|
||||
public TypeDescriptor(MethodParameter methodParameter, Class<?> type) {
|
||||
Assert.notNull(methodParameter, "MethodParameter must not be null");
|
||||
this.methodParameter = methodParameter;
|
||||
this.type = type;
|
||||
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 a field.
|
||||
* Use this constructor when a target conversion point originates from a field.
|
||||
* @param field the field to wrap
|
||||
* @param type the specific type to expose (may be an array/collection element)
|
||||
* Create a new type descriptor for the class of the given object.
|
||||
* @param object the object
|
||||
* @return the type descriptor
|
||||
*/
|
||||
public TypeDescriptor(Field field, Class<?> type) {
|
||||
Assert.notNull(field, "Field must not be null");
|
||||
this.field = field;
|
||||
this.type = type;
|
||||
public static TypeDescriptor forObject(Object object) {
|
||||
if (object == null) {
|
||||
return NULL;
|
||||
}
|
||||
if (object instanceof Collection<?>) {
|
||||
return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType((Collection<?>) object));
|
||||
}
|
||||
else if (object instanceof Map<?, ?>) {
|
||||
return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType(((Map<?, ?>) object).keySet()), CollectionUtils.findCommonElementType(((Map<?, ?>) object).values()));
|
||||
}
|
||||
else {
|
||||
return valueOf(object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for a field.
|
||||
* Use this constructor when a target conversion point originates from a field.
|
||||
* @param field the field to wrap
|
||||
* @param type the specific type to expose (may be an array/collection element)
|
||||
*/
|
||||
private TypeDescriptor(Field field, int nestingLevel, Class<?> type) {
|
||||
Assert.notNull(field, "Field must not be null");
|
||||
this.field = field;
|
||||
this.fieldNestingLevel = nestingLevel;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal constructor for a NULL descriptor.
|
||||
*/
|
||||
private TypeDescriptor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new descriptor for the type of the given value.
|
||||
* <p>Use this constructor when a conversion point comes from a source such as a Map or
|
||||
* Collection, where no additional context is available but elements can be introspected.
|
||||
* @param value the value to determine the actual type from
|
||||
*/
|
||||
private TypeDescriptor(Object value) {
|
||||
Assert.notNull(value, "Value must not be null");
|
||||
this.value = value;
|
||||
this.type = value.getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new descriptor for the given type.
|
||||
* <p>Use this constructor when a conversion point comes from a plain source type,
|
||||
* where no additional context is available.
|
||||
* @param type the actual type to wrap
|
||||
*/
|
||||
private TypeDescriptor(Class<?> type) {
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the wrapped MethodParameter, if any.
|
||||
* <p>Note: Either MethodParameter or Field is available.
|
||||
* @return the MethodParameter, or <code>null</code> if none
|
||||
*/
|
||||
public MethodParameter getMethodParameter() {
|
||||
return this.methodParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the wrapped Field, if any.
|
||||
* <p>Note: Either MethodParameter or Field is available.
|
||||
* @return the Field, or <code>null</code> if none
|
||||
*/
|
||||
public Field getField() {
|
||||
return this.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}
|
||||
*/
|
||||
public Class<?> getType() {
|
||||
if (this.type != null) {
|
||||
return this.type;
|
||||
}
|
||||
else if (this.field != null) {
|
||||
return this.field.getType();
|
||||
}
|
||||
else if (this.methodParameter != null) {
|
||||
return this.methodParameter.getParameterType();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,140 +153,21 @@ public class TypeDescriptor {
|
||||
* Returns the Object wrapper type if the underlying type is a primitive.
|
||||
*/
|
||||
public Class<?> getObjectType() {
|
||||
Class<?> type = getType();
|
||||
return (type != null ? ClassUtils.resolvePrimitiveIfNecessary(type) : type);
|
||||
return ClassUtils.resolvePrimitiveIfNecessary(getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this type: the fully qualified class name.
|
||||
*/
|
||||
public String getName() {
|
||||
Class<?> type = getType();
|
||||
return (type != null ? ClassUtils.getQualifiedName(type) : null);
|
||||
return ClassUtils.getQualifiedName(getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this type a primitive type?
|
||||
*/
|
||||
public boolean isPrimitive() {
|
||||
Class<?> type = getType();
|
||||
return (type != null && type.isPrimitive());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this type an array type?
|
||||
*/
|
||||
public boolean isArray() {
|
||||
Class<?> type = getType();
|
||||
return (type != null && type.isArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this type a {@link Collection} type?
|
||||
*/
|
||||
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 <code>null</code> if the type is neither an array or collection.
|
||||
*/
|
||||
public Class<?> getElementType() {
|
||||
return getElementTypeDescriptor().getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the element type as a type descriptor.
|
||||
*/
|
||||
public synchronized TypeDescriptor getElementTypeDescriptor() {
|
||||
if (this.elementType == null) {
|
||||
this.elementType = forElementType(resolveElementType());
|
||||
}
|
||||
return this.elementType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the element type as a type descriptor. If the element type is null
|
||||
* (cannot be determined), the type descriptor is derived from the element argument.
|
||||
* @param element the element
|
||||
* @return the element type descriptor
|
||||
*/
|
||||
public TypeDescriptor getElementTypeDescriptor(Object element) {
|
||||
TypeDescriptor elementType = getElementTypeDescriptor();
|
||||
return (elementType != TypeDescriptor.UNKNOWN ? elementType : forObject(element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this type a {@link Map} type?
|
||||
*/
|
||||
public boolean isMap() {
|
||||
return Map.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this descriptor for a map where the key type and value type are known?
|
||||
*/
|
||||
public boolean isMapEntryTypeKnown() {
|
||||
return (isMap() && getMapKeyType() != null && getMapValueType() != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the generic key type of the wrapped Map parameter/field, if any.
|
||||
* @return the generic type, or <code>null</code> if none
|
||||
*/
|
||||
public Class<?> getMapKeyType() {
|
||||
return getMapKeyTypeDescriptor().getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns map key type as a type descriptor.
|
||||
*/
|
||||
public synchronized TypeDescriptor getMapKeyTypeDescriptor() {
|
||||
if (this.mapKeyType == null) {
|
||||
this.mapKeyType = forElementType(resolveMapKeyType());
|
||||
}
|
||||
return this.mapKeyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the map key type as a type descriptor. If the key type is null
|
||||
* (cannot be determined), the type descriptor is derived from the key argument.
|
||||
* @param key the key
|
||||
* @return the map key type descriptor
|
||||
*/
|
||||
public TypeDescriptor getMapKeyTypeDescriptor(Object key) {
|
||||
TypeDescriptor keyType = getMapKeyTypeDescriptor();
|
||||
return (keyType != TypeDescriptor.UNKNOWN ? keyType : TypeDescriptor.forObject(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the generic value type of the wrapped Map parameter/field, if any.
|
||||
* @return the generic type, or <code>null</code> if none
|
||||
*/
|
||||
public Class<?> getMapValueType() {
|
||||
return getMapValueTypeDescriptor().getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns map value type as a type descriptor.
|
||||
*/
|
||||
public synchronized TypeDescriptor getMapValueTypeDescriptor() {
|
||||
if (this.mapValueType == null) {
|
||||
this.mapValueType = forElementType(resolveMapValueType());
|
||||
}
|
||||
return this.mapValueType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the map value type as a type descriptor. If the value type is null
|
||||
* (cannot be determined), the type descriptor is derived from the value argument.
|
||||
* @param value the value
|
||||
* @return the map value type descriptor
|
||||
*/
|
||||
public TypeDescriptor getMapValueTypeDescriptor(Object value) {
|
||||
TypeDescriptor valueType = getMapValueTypeDescriptor();
|
||||
return (valueType != TypeDescriptor.UNKNOWN ? valueType : TypeDescriptor.forObject(value));
|
||||
return getType().isPrimitive();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -402,28 +216,130 @@ public class TypeDescriptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of this type descriptor, preserving the context information
|
||||
* but exposing the specified element type (e.g. an array/collection/map element).
|
||||
* @param elementType the desired type to expose
|
||||
* @return the type descriptor
|
||||
* A textual representation of the type descriptor (eg. Map<String,Foo>) for use in messages.
|
||||
*/
|
||||
public TypeDescriptor forElementType(Class<?> elementType) {
|
||||
if (elementType == null) {
|
||||
return TypeDescriptor.UNKNOWN;
|
||||
}
|
||||
else if (this.methodParameter != null) {
|
||||
MethodParameter nested = new MethodParameter(this.methodParameter);
|
||||
nested.increaseNestingLevel();
|
||||
return new TypeDescriptor(nested, elementType);
|
||||
}
|
||||
else if (this.field != null) {
|
||||
return new TypeDescriptor(this.field, this.fieldNestingLevel + 1, elementType);
|
||||
}
|
||||
else {
|
||||
return TypeDescriptor.valueOf(elementType);
|
||||
}
|
||||
public String asString() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
// indexable type descriptor operations
|
||||
|
||||
/**
|
||||
* Is this type an array type?
|
||||
*/
|
||||
public boolean isArray() {
|
||||
return getType().isArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this type a {@link Collection} type?
|
||||
*/
|
||||
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 <code>null</code> if the type is neither an array or collection.
|
||||
*/
|
||||
public Class<?> getElementType() {
|
||||
return getElementTypeDescriptor().getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the element type as a type descriptor.
|
||||
*/
|
||||
public synchronized TypeDescriptor getElementTypeDescriptor() {
|
||||
if (!isCollection() && !isArray()) {
|
||||
throw new IllegalStateException("Not a collection or array type");
|
||||
}
|
||||
if (this.elementType == null) {
|
||||
this.elementType = resolveElementTypeDescriptor();
|
||||
}
|
||||
return this.elementType;
|
||||
}
|
||||
|
||||
// map type descriptor operations
|
||||
|
||||
/**
|
||||
* Is this type a {@link Map} type?
|
||||
*/
|
||||
public boolean isMap() {
|
||||
return Map.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the generic key type of the wrapped Map parameter/field, if any.
|
||||
* @return the generic type, or <code>null</code> if none
|
||||
*/
|
||||
public Class<?> getMapKeyType() {
|
||||
return getMapKeyTypeDescriptor().getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns map key type as a type descriptor.
|
||||
*/
|
||||
public synchronized TypeDescriptor getMapKeyTypeDescriptor() {
|
||||
if (!isMap()) {
|
||||
throw new IllegalStateException("Not a Map type");
|
||||
}
|
||||
if (this.mapKeyType == null) {
|
||||
this.mapKeyType = resolveMapKeyTypeDescriptor();
|
||||
}
|
||||
return this.mapKeyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the generic value type of the wrapped Map parameter/field, if any.
|
||||
* @return the generic type, or <code>null</code> if none
|
||||
*/
|
||||
public Class<?> getMapValueType() {
|
||||
return getMapValueTypeDescriptor().getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns map value type as a type descriptor.
|
||||
*/
|
||||
public synchronized TypeDescriptor getMapValueTypeDescriptor() {
|
||||
if (this.mapValueType == null) {
|
||||
this.mapValueType = resolveMapValueTypeDescriptor();
|
||||
}
|
||||
return this.mapValueType;
|
||||
}
|
||||
|
||||
// special case public operations
|
||||
|
||||
public TypeDescriptor(Class<?> componentType, MethodParameter methodParameter) {
|
||||
if (componentType == null) {
|
||||
componentType = Object.class;
|
||||
}
|
||||
this.type = componentType;
|
||||
this.methodParameter = methodParameter;
|
||||
}
|
||||
|
||||
public MethodParameter getMethodParameter() {
|
||||
return methodParameter;
|
||||
}
|
||||
|
||||
public TypeDescriptor applyType(Object object) {
|
||||
if (object == null) {
|
||||
return this;
|
||||
}
|
||||
// TODO preserve binding context with returned copy
|
||||
// TODO fall back to generic info if collection is empty
|
||||
if (object instanceof Collection<?>) {
|
||||
return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType((Collection<?>) object));
|
||||
}
|
||||
else if (object instanceof Map<?, ?>) {
|
||||
return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType(((Map<?, ?>) object).keySet()), CollectionUtils.findCommonElementType(((Map<?, ?>) object).values()));
|
||||
}
|
||||
else {
|
||||
return valueOf(object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
// extending Object
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
@@ -432,8 +348,7 @@ public class TypeDescriptor {
|
||||
return false;
|
||||
}
|
||||
TypeDescriptor other = (TypeDescriptor) obj;
|
||||
boolean annotatedTypeEquals =
|
||||
getType().equals(other.getType()) && ObjectUtils.nullSafeEquals(getAnnotations(), other.getAnnotations());
|
||||
boolean annotatedTypeEquals = getType().equals(other.getType()) && ObjectUtils.nullSafeEquals(getAnnotations(), other.getAnnotations());
|
||||
if (isCollection()) {
|
||||
return annotatedTypeEquals && ObjectUtils.nullSafeEquals(getElementType(), other.getElementType());
|
||||
}
|
||||
@@ -450,13 +365,6 @@ public class TypeDescriptor {
|
||||
return (this == TypeDescriptor.NULL ? 0 : getType().hashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* A textual representation of the type descriptor (eg. Map<String,Foo>) for use in messages.
|
||||
*/
|
||||
public String asString() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (this == TypeDescriptor.NULL) {
|
||||
return "null";
|
||||
@@ -479,82 +387,9 @@ public class TypeDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// internal helpers
|
||||
|
||||
private Class<?> resolveElementType() {
|
||||
if (isArray()) {
|
||||
return getType().getComponentType();
|
||||
}
|
||||
else if (isCollection()) {
|
||||
return resolveCollectionElementType();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<?> resolveCollectionElementType() {
|
||||
if (this.field != null) {
|
||||
return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.fieldNestingLevel);
|
||||
}
|
||||
else if (this.methodParameter != null) {
|
||||
return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter);
|
||||
}
|
||||
else if (this.value instanceof Collection) {
|
||||
Class<?> elementType = CollectionUtils.findCommonElementType((Collection) this.value);
|
||||
if (elementType != null) {
|
||||
return elementType;
|
||||
}
|
||||
}
|
||||
else if (this.type != null) {
|
||||
return GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection>) this.type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<?> resolveMapKeyType() {
|
||||
if (this.field != null) {
|
||||
return GenericCollectionTypeResolver.getMapKeyFieldType(this.field);
|
||||
}
|
||||
else if (this.methodParameter != null) {
|
||||
return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter);
|
||||
}
|
||||
else if (this.value instanceof Map<?, ?>) {
|
||||
Class<?> keyType = CollectionUtils.findCommonElementType(((Map<?, ?>) this.value).keySet());
|
||||
if (keyType != null) {
|
||||
return keyType;
|
||||
}
|
||||
}
|
||||
else if (this.type != null && isMap()) {
|
||||
return GenericCollectionTypeResolver.getMapKeyType((Class<? extends Map>) this.type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<?> resolveMapValueType() {
|
||||
if (this.field != null) {
|
||||
return GenericCollectionTypeResolver.getMapValueFieldType(this.field);
|
||||
}
|
||||
else if (this.methodParameter != null) {
|
||||
return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter);
|
||||
}
|
||||
else if (this.value instanceof Map<?, ?>) {
|
||||
Class<?> valueType = CollectionUtils.findCommonElementType(((Map<?, ?>) this.value).values());
|
||||
if (valueType != null) {
|
||||
return valueType;
|
||||
}
|
||||
}
|
||||
else if (this.type != null && isMap()) {
|
||||
return GenericCollectionTypeResolver.getMapValueType((Class<? extends Map>) this.type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Annotation[] resolveAnnotations() {
|
||||
// subclassing hooks
|
||||
|
||||
protected Annotation[] resolveAnnotations() {
|
||||
if (this.field != null) {
|
||||
return this.field.getAnnotations();
|
||||
}
|
||||
@@ -571,37 +406,118 @@ public class TypeDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
protected TypeDescriptor newComponentTypeDescriptor(Class<?> componentType, MethodParameter nested) {
|
||||
return new TypeDescriptor(componentType, nested);
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
// static factory methods
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for the class of the given object.
|
||||
* @param object the object
|
||||
* @return the type descriptor
|
||||
*/
|
||||
public static TypeDescriptor forObject(Object object) {
|
||||
if (object == null) {
|
||||
return NULL;
|
||||
}
|
||||
else if (object instanceof Collection<?> || object instanceof Map<?, ?>) {
|
||||
return new TypeDescriptor(object);
|
||||
private TypeDescriptor resolveElementTypeDescriptor() {
|
||||
if (isCollection()) {
|
||||
return createComponentTypeDescriptor(resolveCollectionElementType());
|
||||
}
|
||||
else {
|
||||
return valueOf(object.getClass());
|
||||
// TODO: GenericCollectionTypeResolver is not capable of applying nesting levels to array fields;
|
||||
// this means generic info of nested lists or maps stored inside array method parameters or fields is not obtainable
|
||||
return createComponentTypeDescriptor(getType().getComponentType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 TypeDescriptor.NULL;
|
||||
}
|
||||
TypeDescriptor desc = typeDescriptorCache.get(type);
|
||||
return (desc != null ? desc : new TypeDescriptor(type));
|
||||
private TypeDescriptor resolveMapKeyTypeDescriptor() {
|
||||
return createComponentTypeDescriptor(resolveMapKeyType());
|
||||
}
|
||||
|
||||
}
|
||||
private TypeDescriptor resolveMapValueTypeDescriptor() {
|
||||
return createComponentTypeDescriptor(resolveMapValueType());
|
||||
}
|
||||
|
||||
private TypeDescriptor createComponentTypeDescriptor(Class<?> componentType) {
|
||||
if (componentType == null) {
|
||||
componentType = Object.class;
|
||||
}
|
||||
if (this.methodParameter != null) {
|
||||
MethodParameter nested = new MethodParameter(this.methodParameter);
|
||||
nested.increaseNestingLevel();
|
||||
return newComponentTypeDescriptor(componentType, nested);
|
||||
}
|
||||
else if (this.field != null) {
|
||||
return new TypeDescriptor(componentType, this.field, this.fieldNestingLevel + 1);
|
||||
}
|
||||
else {
|
||||
return TypeDescriptor.valueOf(componentType);
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> resolveCollectionElementType() {
|
||||
if (this.methodParameter != null) {
|
||||
return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter);
|
||||
}
|
||||
else if (this.field != null) {
|
||||
return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.fieldNestingLevel);
|
||||
}
|
||||
else {
|
||||
return GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection>) this.type);
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> resolveMapKeyType() {
|
||||
if (this.methodParameter != null) {
|
||||
return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter);
|
||||
}
|
||||
else if (this.field != null) {
|
||||
return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.fieldNestingLevel);
|
||||
}
|
||||
else {
|
||||
return GenericCollectionTypeResolver.getMapKeyType((Class<? extends Map>) this.type);
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> resolveMapValueType() {
|
||||
if (this.methodParameter != null) {
|
||||
return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter);
|
||||
}
|
||||
else if (this.field != null) {
|
||||
return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.fieldNestingLevel);
|
||||
}
|
||||
else {
|
||||
return GenericCollectionTypeResolver.getMapValueType((Class<? extends Map>) this.type);
|
||||
}
|
||||
}
|
||||
|
||||
// internal constructors
|
||||
|
||||
private TypeDescriptor(Class<?> componentType, Field field, int nestingLevel) {
|
||||
this.type = componentType;
|
||||
this.field = field;
|
||||
this.fieldNestingLevel = nestingLevel;
|
||||
}
|
||||
|
||||
private TypeDescriptor() {
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> type) {
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> collectionType, Class<?> elementType) {
|
||||
this.type = collectionType;
|
||||
if (elementType == null) {
|
||||
elementType = Object.class;
|
||||
}
|
||||
this.elementType = TypeDescriptor.valueOf(elementType);
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> mapType, Class<?> keyType, Class<?> valueType) {
|
||||
this.type = mapType;
|
||||
if (keyType == null) {
|
||||
keyType = Object.class;
|
||||
}
|
||||
if (valueType == null) {
|
||||
valueType = Object.class;
|
||||
}
|
||||
this.mapKeyType = TypeDescriptor.valueOf(keyType);
|
||||
this.mapValueType = TypeDescriptor.valueOf(valueType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,7 +52,6 @@ final class ArrayToCollectionConverter implements ConditionalGenericConverter {
|
||||
return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
@@ -61,7 +60,7 @@ final class ArrayToCollectionConverter implements ConditionalGenericConverter {
|
||||
Collection target = CollectionFactory.createCollection(targetType.getType(), length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
Object sourceElement = Array.get(source, i);
|
||||
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(sourceElement));
|
||||
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor());
|
||||
target.add(targetElement);
|
||||
}
|
||||
return target;
|
||||
|
||||
@@ -60,7 +60,7 @@ final class CollectionToArrayConverter implements ConditionalGenericConverter {
|
||||
Object array = Array.newInstance(targetType.getElementType(), sourceCollection.size());
|
||||
int i = 0;
|
||||
for (Object sourceElement : sourceCollection) {
|
||||
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(sourceElement), targetType.getElementTypeDescriptor());
|
||||
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor());
|
||||
Array.set(array, i++, targetElement);
|
||||
}
|
||||
return array;
|
||||
|
||||
@@ -52,7 +52,6 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert
|
||||
return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
@@ -63,9 +62,7 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert
|
||||
}
|
||||
Collection target = CollectionFactory.createCollection(targetType.getType(), sourceCollection.size());
|
||||
for (Object sourceElement : sourceCollection) {
|
||||
Object targetElement = this.conversionService.convert(sourceElement,
|
||||
sourceType.getElementTypeDescriptor(sourceElement),
|
||||
targetType.getElementTypeDescriptor(sourceElement));
|
||||
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor());
|
||||
target.add(targetElement);
|
||||
}
|
||||
return target;
|
||||
|
||||
@@ -55,7 +55,7 @@ final class CollectionToObjectConverter implements ConditionalGenericConverter {
|
||||
return null;
|
||||
}
|
||||
Object firstElement = sourceCollection.iterator().next();
|
||||
return this.conversionService.convert(firstElement, sourceType.getElementTypeDescriptor(firstElement), targetType);
|
||||
return this.conversionService.convert(firstElement, sourceType.getElementTypeDescriptor(), targetType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -62,8 +62,7 @@ final class CollectionToStringConverter implements ConditionalGenericConverter {
|
||||
if (i > 0) {
|
||||
string.append(DELIMITER);
|
||||
}
|
||||
Object targetElement = this.conversionService.convert(
|
||||
sourceElement, sourceType.getElementTypeDescriptor(sourceElement), targetType);
|
||||
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType);
|
||||
string.append(targetElement);
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -67,12 +67,8 @@ final class MapToMapConverter implements ConditionalGenericConverter {
|
||||
Map.Entry sourceMapEntry = (Map.Entry) entry;
|
||||
Object sourceKey = sourceMapEntry.getKey();
|
||||
Object sourceValue = sourceMapEntry.getValue();
|
||||
Object targetKey = this.conversionService.convert(sourceKey,
|
||||
sourceType.getMapKeyTypeDescriptor(sourceKey),
|
||||
targetType.getMapKeyTypeDescriptor(sourceKey));
|
||||
Object targetValue = this.conversionService.convert(sourceValue,
|
||||
sourceType.getMapValueTypeDescriptor(sourceValue),
|
||||
targetType.getMapValueTypeDescriptor(sourceValue));
|
||||
Object targetKey = this.conversionService.convert(sourceKey, sourceType.getMapKeyTypeDescriptor(), targetType.getMapKeyTypeDescriptor());
|
||||
Object targetValue = this.conversionService.convert(sourceValue, sourceType.getMapValueTypeDescriptor(), targetType.getMapValueTypeDescriptor());
|
||||
targetMap.put(targetKey, targetValue);
|
||||
}
|
||||
return targetMap;
|
||||
|
||||
@@ -49,13 +49,12 @@ final class ObjectToCollectionConverter implements ConditionalGenericConverter {
|
||||
return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
Collection target = CollectionFactory.createCollection(targetType.getType(), 1);
|
||||
TypeDescriptor elementType = targetType.getElementTypeDescriptor(source);
|
||||
TypeDescriptor elementType = targetType.getElementTypeDescriptor();
|
||||
// Avoid potential recursion...
|
||||
if (!Collection.class.isAssignableFrom(elementType.getType())) {
|
||||
target.add(this.conversionService.convert(source, sourceType, elementType));
|
||||
|
||||
@@ -40,31 +40,21 @@ public class PropertyTypeDescriptor extends TypeDescriptor {
|
||||
|
||||
private final PropertyDescriptor propertyDescriptor;
|
||||
|
||||
private Annotation[] cachedAnnotations;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new BeanTypeDescriptor for the given bean property.
|
||||
* @param propertyDescriptor the corresponding JavaBean PropertyDescriptor
|
||||
* @param methodParameter the target method parameter
|
||||
*/
|
||||
public PropertyTypeDescriptor(PropertyDescriptor propertyDescriptor, MethodParameter methodParameter) {
|
||||
public PropertyTypeDescriptor(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
|
||||
super(methodParameter);
|
||||
this.propertyDescriptor = propertyDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new BeanTypeDescriptor for the given bean property.
|
||||
* @param propertyDescriptor the corresponding JavaBean PropertyDescriptor
|
||||
* @param methodParameter the target method parameter
|
||||
* @param type the specific type to expose (may be an array/collection element)
|
||||
*/
|
||||
public PropertyTypeDescriptor(PropertyDescriptor propertyDescriptor, MethodParameter methodParameter, Class<?> type) {
|
||||
super(methodParameter, type);
|
||||
|
||||
public PropertyTypeDescriptor(Class<?> componentType, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
|
||||
super(componentType, methodParameter);
|
||||
this.propertyDescriptor = propertyDescriptor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the underlying PropertyDescriptor.
|
||||
*/
|
||||
@@ -72,58 +62,48 @@ public class PropertyTypeDescriptor extends TypeDescriptor {
|
||||
return this.propertyDescriptor;
|
||||
}
|
||||
|
||||
public Annotation[] getAnnotations() {
|
||||
Annotation[] anns = this.cachedAnnotations;
|
||||
if (anns == null) {
|
||||
Map<Class<?>, Annotation> annMap = new LinkedHashMap<Class<?>, Annotation>();
|
||||
String name = this.propertyDescriptor.getName();
|
||||
if (StringUtils.hasLength(name)) {
|
||||
Class<?> clazz = getMethodParameter().getMethod().getDeclaringClass();
|
||||
Field field = ReflectionUtils.findField(clazz, name);
|
||||
protected Annotation[] resolveAnnotations() {
|
||||
Map<Class<?>, Annotation> annMap = new LinkedHashMap<Class<?>, Annotation>();
|
||||
String name = this.propertyDescriptor.getName();
|
||||
if (StringUtils.hasLength(name)) {
|
||||
Class<?> clazz = getMethodParameter().getMethod().getDeclaringClass();
|
||||
Field field = ReflectionUtils.findField(clazz, name);
|
||||
if (field == null) {
|
||||
// Same lenient fallback checking as in CachedIntrospectionResults...
|
||||
field = ReflectionUtils.findField(clazz, name.substring(0, 1).toLowerCase() + name.substring(1));
|
||||
if (field == null) {
|
||||
// Same lenient fallback checking as in CachedIntrospectionResults...
|
||||
field = ReflectionUtils.findField(clazz, name.substring(0, 1).toLowerCase() + name.substring(1));
|
||||
if (field == null) {
|
||||
field = ReflectionUtils.findField(clazz, name.substring(0, 1).toUpperCase() + name.substring(1));
|
||||
}
|
||||
}
|
||||
if (field != null) {
|
||||
for (Annotation ann : field.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
field = ReflectionUtils.findField(clazz, name.substring(0, 1).toUpperCase() + name.substring(1));
|
||||
}
|
||||
}
|
||||
Method writeMethod = this.propertyDescriptor.getWriteMethod();
|
||||
Method readMethod = this.propertyDescriptor.getReadMethod();
|
||||
if (writeMethod != null && writeMethod != getMethodParameter().getMethod()) {
|
||||
for (Annotation ann : writeMethod.getAnnotations()) {
|
||||
if (field != null) {
|
||||
for (Annotation ann : field.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
}
|
||||
if (readMethod != null && readMethod != getMethodParameter().getMethod()) {
|
||||
for (Annotation ann : readMethod.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
}
|
||||
for (Annotation ann : getMethodParameter().getMethodAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
for (Annotation ann : getMethodParameter().getParameterAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
anns = annMap.values().toArray(new Annotation[annMap.size()]);
|
||||
this.cachedAnnotations = anns;
|
||||
}
|
||||
return anns;
|
||||
Method writeMethod = this.propertyDescriptor.getWriteMethod();
|
||||
Method readMethod = this.propertyDescriptor.getReadMethod();
|
||||
if (writeMethod != null && writeMethod != getMethodParameter().getMethod()) {
|
||||
for (Annotation ann : writeMethod.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
}
|
||||
if (readMethod != null && readMethod != getMethodParameter().getMethod()) {
|
||||
for (Annotation ann : readMethod.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
}
|
||||
for (Annotation ann : getMethodParameter().getMethodAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
for (Annotation ann : getMethodParameter().getParameterAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
return annMap.values().toArray(new Annotation[annMap.size()]);
|
||||
}
|
||||
|
||||
public TypeDescriptor forElementType(Class<?> elementType) {
|
||||
if (elementType != null) {
|
||||
return new PropertyTypeDescriptor(this.propertyDescriptor, getMethodParameter(), elementType);
|
||||
}
|
||||
else {
|
||||
return super.forElementType(null);
|
||||
}
|
||||
public TypeDescriptor newComponentTypeDescriptor(Class<?> componentType, MethodParameter nested) {
|
||||
return new PropertyTypeDescriptor(componentType, nested, this.propertyDescriptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ final class StringToCollectionConverter implements ConditionalGenericConverter {
|
||||
return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
@@ -57,8 +56,7 @@ final class StringToCollectionConverter implements ConditionalGenericConverter {
|
||||
String[] fields = StringUtils.commaDelimitedListToStringArray(string);
|
||||
Collection target = CollectionFactory.createCollection(targetType.getType(), fields.length);
|
||||
for (String sourceElement : fields) {
|
||||
Object targetElement = this.conversionService.convert(sourceElement.trim(),
|
||||
sourceType, targetType.getElementTypeDescriptor(sourceElement));
|
||||
Object targetElement = this.conversionService.convert(sourceElement.trim(), sourceType, targetType.getElementTypeDescriptor());
|
||||
target.add(targetElement);
|
||||
}
|
||||
return target;
|
||||
|
||||
@@ -16,16 +16,17 @@
|
||||
|
||||
package org.springframework.core.convert;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
@@ -47,7 +48,8 @@ public class TypeDescriptorTests {
|
||||
|
||||
public Map<String, Integer> mapField = new HashMap<String, Integer>();
|
||||
|
||||
|
||||
public Map<String, List<Integer>> nestedMapField = new HashMap<String, List<Integer>>();
|
||||
|
||||
@Test
|
||||
public void listDescriptor() throws Exception {
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfString"));
|
||||
@@ -94,14 +96,27 @@ public class TypeDescriptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void complexTypeDescriptor() throws Exception {
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("arrayOfListOfString"));
|
||||
assertTrue(typeDescriptor.isArray());
|
||||
assertEquals(List.class,typeDescriptor.getElementType());
|
||||
assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType());
|
||||
|
||||
// TODO asc notice that the type of the list elements is lost: typeDescriptor.getElementType() should return a TypeDescriptor
|
||||
assertEquals("java.util.List[]",typeDescriptor.asString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void complexTypeDescriptor2() throws Exception {
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("nestedMapField"));
|
||||
assertTrue(typeDescriptor.isMap());
|
||||
assertEquals(String.class,typeDescriptor.getMapKeyType());
|
||||
assertEquals(List.class, typeDescriptor.getMapValueType());
|
||||
assertEquals(Integer.class, typeDescriptor.getMapValueTypeDescriptor().getElementType());
|
||||
assertEquals("java.util.Map<java.lang.String, java.util.List<java.lang.Integer>>", typeDescriptor.asString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() throws Exception {
|
||||
TypeDescriptor t1 = TypeDescriptor.valueOf(String.class);
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
|
||||
package org.springframework.core.convert.support;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.AbstractList;
|
||||
@@ -33,13 +40,14 @@ import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
|
||||
/**
|
||||
* @author Keith Donald
|
||||
@@ -286,6 +294,24 @@ public class DefaultConversionTests {
|
||||
assertEquals(new Integer("3"), result.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpr7766() throws Exception {
|
||||
ConverterRegistry registry = ((ConverterRegistry) conversionService);
|
||||
registry.addConverter(new ColorConverter());
|
||||
List<Color> colors = (List<Color>) conversionService.convert(new String[] { "ffffff", "#000000" }, TypeDescriptor.valueOf(String[].class), new TypeDescriptor(new MethodParameter(getClass().getMethod("handlerMethod", List.class), 0)));
|
||||
assertEquals(2, colors.size());
|
||||
assertEquals(Color.WHITE, colors.get(0));
|
||||
assertEquals(Color.BLACK, colors.get(1));
|
||||
}
|
||||
|
||||
public class ColorConverter implements Converter<String, Color> {
|
||||
public Color convert(String source) { if (!source.startsWith("#")) source = "#" + source; return Color.decode(source); }
|
||||
}
|
||||
|
||||
public void handlerMethod(List<Color> color) {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertArrayToCollectionImpl() {
|
||||
LinkedList<?> result = conversionService.convert(new String[] { "1", "2", "3" }, LinkedList.class);
|
||||
|
||||
Reference in New Issue
Block a user