提交 c84cccf0 编写于 作者: K Keith Donald

revised TypeDescriptor NULL and element/mapKey/mapValue type semantics

上级 a2a4929c
......@@ -141,8 +141,8 @@ class TypeConverterDelegate {
// Value not of required type?
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) {
Class elemType = typeDescriptor.getElementType();
if (elemType != null && Enum.class.isAssignableFrom(elemType)) {
TypeDescriptor elementType = typeDescriptor.getElementType();
if (elementType != null && Enum.class.isAssignableFrom(elementType.getType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
......@@ -459,8 +459,8 @@ class TypeConverterDelegate {
return original;
}
Class elementType = typeDescriptor.getElementType();
if (elementType == Object.class && originalAllowed &&
TypeDescriptor elementType = typeDescriptor.getElementType();
if (elementType == null && originalAllowed &&
!this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
return original;
}
......@@ -506,7 +506,7 @@ class TypeConverterDelegate {
Object element = it.next();
String indexedPropertyName = buildIndexedPropertyName(propertyName, i);
Object convertedElement = convertIfNecessary(
indexedPropertyName, null, element, elementType, typeDescriptor.getElementTypeDescriptor());
indexedPropertyName, null, element, elementType != null ? elementType.getType() : null , typeDescriptor.getElementType());
try {
convertedCopy.add(convertedElement);
}
......@@ -531,9 +531,9 @@ class TypeConverterDelegate {
return original;
}
Class keyType = typeDescriptor.getMapKeyType();
Class valueType = typeDescriptor.getMapValueType();
if (keyType == Object.class && valueType == Object.class && originalAllowed &&
TypeDescriptor keyType = typeDescriptor.getMapKeyType();
TypeDescriptor valueType = typeDescriptor.getMapValueType();
if (keyType == null && valueType == null && originalAllowed &&
!this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
return original;
}
......@@ -579,8 +579,8 @@ class TypeConverterDelegate {
Object key = entry.getKey();
Object value = entry.getValue();
String keyedPropertyName = buildKeyedPropertyName(propertyName, key);
Object convertedKey = convertIfNecessary(keyedPropertyName, null, key, keyType, typeDescriptor.getMapKeyTypeDescriptor());
Object convertedValue = convertIfNecessary(keyedPropertyName, null, value, valueType, typeDescriptor.getMapValueTypeDescriptor());
Object convertedKey = convertIfNecessary(keyedPropertyName, null, key, keyType != null ? keyType.getType() : null, typeDescriptor.getMapKeyType());
Object convertedValue = convertIfNecessary(keyedPropertyName, null, value, valueType!= null ? valueType.getType() : null, typeDescriptor.getMapValueType());
try {
convertedCopy.put(convertedKey, convertedValue);
}
......
......@@ -24,6 +24,9 @@ abstract class AbstractDescriptor {
private final Class<?> type;
public AbstractDescriptor(Class<?> type) {
//if (type == null) {
// throw new IllegalArgumentException("type cannot be null");
//}
this.type = type;
}
......@@ -33,31 +36,31 @@ abstract class AbstractDescriptor {
public TypeDescriptor getElementType() {
if (isCollection()) {
Class<?> elementType = wildcard(getCollectionElementClass());
return new TypeDescriptor(nested(elementType, 0));
Class<?> elementType = resolveCollectionElementType();
return elementType != null ? new TypeDescriptor(nested(elementType, 0)) : null;
} else if (isArray()) {
Class<?> elementType = getType().getComponentType();
return new TypeDescriptor(nested(elementType, 0));
} else {
return TypeDescriptor.NULL;
return null;
}
}
public TypeDescriptor getMapKeyType() {
if (isMap()) {
Class<?> keyType = wildcard(getMapKeyClass());
return new TypeDescriptor(nested(keyType, 0));
Class<?> keyType = resolveMapKeyType();
return keyType != null ? new TypeDescriptor(nested(keyType, 0)) : null;
} else {
return TypeDescriptor.NULL;
return null;
}
}
public TypeDescriptor getMapValueType() {
if (isMap()) {
Class<?> valueType = wildcard(getMapValueClass());
return new TypeDescriptor(nested(valueType, 1));
Class<?> valueType = resolveMapValueType();
return valueType != null ? new TypeDescriptor(nested(valueType, 1)) : null;
} else {
return TypeDescriptor.NULL;
return null;
}
}
......@@ -65,11 +68,11 @@ abstract class AbstractDescriptor {
public AbstractDescriptor nested() {
if (isCollection()) {
return nested(wildcard(getCollectionElementClass()), 0);
return nested(resolveCollectionElementType(), 0);
} else if (isArray()) {
return nested(getType().getComponentType(), 0);
} else if (isMap()) {
return nested(wildcard(getMapValueClass()), 1);
return nested(resolveMapValueType(), 1);
} else {
throw new IllegalStateException("Not a collection, array, or map: cannot resolve nested value types");
}
......@@ -77,30 +80,26 @@ abstract class AbstractDescriptor {
// subclassing hooks
protected abstract Class<?> getCollectionElementClass();
protected abstract Class<?> resolveCollectionElementType();
protected abstract Class<?> getMapKeyClass();
protected abstract Class<?> resolveMapKeyType();
protected abstract Class<?> getMapValueClass();
protected abstract Class<?> resolveMapValueType();
protected abstract AbstractDescriptor nested(Class<?> type, int typeIndex);
// internal helpers
private boolean isCollection() {
return Collection.class.isAssignableFrom(getType());
return getType() != null && Collection.class.isAssignableFrom(getType());
}
private boolean isArray() {
return getType().isArray();
return getType() != null && getType().isArray();
}
private boolean isMap() {
return Map.class.isAssignableFrom(getType());
}
private Class<?> wildcard(Class<?> type) {
return type != null ? type : Object.class;
return getType() != null && Map.class.isAssignableFrom(getType());
}
}
\ No newline at end of file
......@@ -52,17 +52,17 @@ class BeanPropertyDescriptor extends AbstractDescriptor {
}
@Override
protected Class<?> getCollectionElementClass() {
protected Class<?> resolveCollectionElementType() {
return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter);
}
@Override
protected Class<?> getMapKeyClass() {
protected Class<?> resolveMapKeyType() {
return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter);
}
@Override
protected Class<?> getMapValueClass() {
protected Class<?> resolveMapValueType() {
return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter);
}
......
......@@ -29,18 +29,18 @@ class ClassDescriptor extends AbstractDescriptor {
}
@Override
protected Class<?> getCollectionElementClass() {
return Object.class;
protected Class<?> resolveCollectionElementType() {
return null;
}
@Override
protected Class<?> getMapKeyClass() {
return Object.class;
protected Class<?> resolveMapKeyType() {
return null;
}
@Override
protected Class<?> getMapValueClass() {
return Object.class;
protected Class<?> resolveMapValueType() {
return null;
}
@Override
......
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
class CommonElement {
private final Class<?> type;
private final Object value;
public CommonElement(Class<?> type, Object value) {
this.type = type;
this.value = value;
}
public Class<?> getType() {
return type;
}
public Object getValue() {
return value;
}
public TypeDescriptor toTypeDescriptor() {
if (type == null) {
return TypeDescriptor.NULL;
} else if (value instanceof Collection<?>) {
Collection<?> collection = (Collection<?>) value;
return new TypeDescriptor(type, typeDescriptor(collection));
}
else if (value instanceof Map<?, ?>) {
Map<?, ?> map = (Map<?, ?>) value;
return new TypeDescriptor(type, typeDescriptor(map.keySet()), typeDescriptor(map.values()));
}
else {
return TypeDescriptor.valueOf(type);
}
}
public static TypeDescriptor typeDescriptor(Collection<?> collection) {
return findCommonElement(collection).toTypeDescriptor();
}
// internal helpers
private static CommonElement findCommonElement(Collection<?> values) {
Class<?> commonType = null;
Object candidate = null;
for (Object value : values) {
if (value != null) {
if (candidate == null) {
commonType = value.getClass();
candidate = value;
} else {
commonType = commonType(commonType, value.getClass());
if (commonType == Object.class) {
return new CommonElement(Object.class, null);
}
}
}
}
return new CommonElement(commonType, candidate);
}
private static Class<?> commonType(Class<?> commonType, Class<?> valueClass) {
Set<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(commonType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
if (currentClass.isAssignableFrom(valueClass)) {
return currentClass;
}
Class<?> superClass = currentClass.getSuperclass();
if (superClass != null && superClass != Object.class) {
classQueue.addFirst(currentClass.getSuperclass());
}
for (Class<?> interfaceType : currentClass.getInterfaces()) {
addInterfaceHierarchy(interfaceType, interfaces);
}
}
for (Class<?> interfaceType : interfaces) {
if (interfaceType.isAssignableFrom(valueClass)) {
return interfaceType;
}
}
return Object.class;
}
private static void addInterfaceHierarchy(Class<?> interfaceType, Set<Class<?>> interfaces) {
interfaces.add(interfaceType);
for (Class<?> inheritedInterface : interfaceType.getInterfaces()) {
addInterfaceHierarchy(inheritedInterface, interfaces);
}
}
}
\ No newline at end of file
......@@ -41,7 +41,7 @@ public interface ConversionService {
* @throws ConversionException if an exception occurred
*/
<T> T convert(Object source, Class<T> targetType);
/**
* Returns true if objects of sourceType can be converted to the targetType.
* The TypeDescriptors provide additional context about the field locations where conversion would occur, often object property locations.
......
......@@ -36,17 +36,17 @@ class FieldDescriptor extends AbstractDescriptor {
}
@Override
protected Class<?> getCollectionElementClass() {
protected Class<?> resolveCollectionElementType() {
return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel);
}
@Override
protected Class<?> getMapKeyClass() {
protected Class<?> resolveMapKeyType() {
return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel);
}
@Override
protected Class<?> getMapValueClass() {
protected Class<?> resolveMapValueType() {
return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel);
}
......
......@@ -43,17 +43,17 @@ class ParameterDescriptor extends AbstractDescriptor {
}
@Override
protected Class<?> getCollectionElementClass() {
protected Class<?> resolveCollectionElementType() {
return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter);
}
@Override
protected Class<?> getMapKeyClass() {
protected Class<?> resolveMapKeyType() {
return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter);
}
@Override
protected Class<?> getMapValueClass() {
protected Class<?> resolveMapValueType() {
return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter);
}
......
......@@ -38,9 +38,6 @@ public class TypeDescriptor {
static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
/** Constant defining a TypeDescriptor for a <code>null</code> value */
public static final TypeDescriptor NULL = new TypeDescriptor();
private static final Map<Class<?>, TypeDescriptor> typeDescriptorCache = new HashMap<Class<?>, TypeDescriptor>();
static {
......@@ -63,7 +60,6 @@ public class TypeDescriptor {
typeDescriptorCache.put(String.class, new TypeDescriptor(String.class));
}
private final Class<?> type;
private final TypeDescriptor elementType;
......@@ -110,9 +106,6 @@ public class TypeDescriptor {
* @return the type descriptor
*/
public static TypeDescriptor valueOf(Class<?> type) {
if (type == null) {
return NULL;
}
TypeDescriptor desc = typeDescriptorCache.get(type);
return (desc != null ? desc : new TypeDescriptor(type));
}
......@@ -148,37 +141,6 @@ public class TypeDescriptor {
return new TypeDescriptor(mapType, keyType, valueType);
}
/**
* Create a new type descriptor for an object.
* Use this factory method to introspect a source object's type before asking the conversion system to convert it to some another type.
* Populates nested type descriptors for collection and map objects through object introspection.
* If the provided object is null, returns {@link TypeDescriptor#NULL}.
* If the object is not a collection or map, simply calls {@link #valueOf(Class)}.
* If the object is a collection or map, this factory method will derive nested element or key/value types by introspecting the collection or map.
* The introspection algorithm derives nested element or key/value types by resolving the "common element type" across the collection or map.
* For example, if a Collection contained all java.lang.Integer elements, its element type would be java.lang.Integer.
* If a Collection contained several distinct number types all extending from java.lang.Number, its element type would be java.lang.Number.
* If a Collection contained a String and a java.util.Map element, its element type would be java.io.Serializable.
* @param object the source object
* @return the type descriptor
* @see ConversionService#convert(Object, Class)
*/
public static TypeDescriptor forObject(Object object) {
if (object == null) {
return NULL;
}
if (object instanceof Collection<?>) {
return new TypeDescriptor(object.getClass(), CommonElement.typeDescriptor((Collection<?>) object));
}
else if (object instanceof Map<?, ?>) {
Map<?, ?> map = (Map<?, ?>) object;
return new TypeDescriptor(map.getClass(), CommonElement.typeDescriptor(map.keySet()), CommonElement.typeDescriptor(map.values()));
}
else {
return valueOf(object.getClass());
}
}
/**
* Creates a type descriptor for a nested type declared within the method parameter.
* For example, if the methodParameter is a List&lt;String&gt; and the nestingLevel is 1, the nested type descriptor will be String.class.
......@@ -224,6 +186,10 @@ public class TypeDescriptor {
return nested(new BeanPropertyDescriptor(beanClass, property), nestingLevel);
}
public static TypeDescriptor forObject(Object source) {
return source != null ? valueOf(source.getClass()) : null;
}
/**
* 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}
......@@ -237,21 +203,27 @@ public class TypeDescriptor {
* Returns the Object wrapper type if the underlying type is a primitive.
*/
public Class<?> getObjectType() {
return getType() != null ? ClassUtils.resolvePrimitiveIfNecessary(getType()) : null;
return ClassUtils.resolvePrimitiveIfNecessary(getType());
}
public TypeDescriptor narrowType(Object value) {
if (value == null) {
return this;
}
return new TypeDescriptor(value.getClass(), elementType, mapKeyType, mapValueType, annotations);
}
/**
* Returns the name of this type: the fully qualified class name.
*/
public String getName() {
return getType() != null ? ClassUtils.getQualifiedName(getType()) : null;
return ClassUtils.getQualifiedName(getType());
}
/**
* Is this type a primitive type?
*/
public boolean isPrimitive() {
return getType() != null && getType().isPrimitive();
return getType().isPrimitive();
}
/**
......@@ -281,21 +253,19 @@ public class TypeDescriptor {
* @return true if this type is assignable to the target
*/
public boolean isAssignableTo(TypeDescriptor targetType) {
if (this == TypeDescriptor.NULL || targetType == TypeDescriptor.NULL) {
return true;
}
if (isCollection() && targetType.isCollection() || isArray() && targetType.isArray()) {
return targetType.getType().isAssignableFrom(getType()) &&
getElementTypeDescriptor().isAssignableTo(targetType.getElementTypeDescriptor());
boolean typesAssignable = targetType.getObjectType().isAssignableFrom(getObjectType());
if (!typesAssignable) {
return false;
}
else if (isMap() && targetType.isMap()) {
return targetType.getType().isAssignableFrom(getType()) &&
getMapKeyTypeDescriptor().isAssignableTo(targetType.getMapKeyTypeDescriptor()) &&
getMapValueTypeDescriptor().isAssignableTo(targetType.getMapValueTypeDescriptor());
if (isArray() && targetType.isArray()) {
return getElementType().isAssignableTo(targetType.getElementType());
}
else {
return targetType.getObjectType().isAssignableFrom(getObjectType());
if (isCollection() && targetType.isCollection()) {
return collectionElementsAssignable(targetType.getElementType());
} else if (isMap() && targetType.isMap()) {
return mapKeysAssignable(targetType.getMapKeyType()) && mapValuesAssignable(targetType.getMapValueType());
}
return true;
}
// indexable type descriptor operations
......@@ -304,53 +274,36 @@ public class TypeDescriptor {
* Is this type a {@link Collection} type?
*/
public boolean isCollection() {
return getType() != null && Collection.class.isAssignableFrom(getType());
return Collection.class.isAssignableFrom(getType());
}
/**
* Is this type an array type?
*/
public boolean isArray() {
return getType() != null && getType().isArray();
}
/**
* If this type is a {@link Collection} or array, returns the underlying element type.
* Returns Object.class if this type is a collection and the element type was not explicitly declared.
* @return the map element type, or <code>null</code> if not a collection or array.
* @throws IllegalStateException if this descriptor is not for a java.util.Collection or Array
*/
public Class<?> getElementType() {
return getElementTypeDescriptor().getType();
return getType().isArray();
}
/**
* The collection or array element type as a type descriptor.
* Returns TypeDescriptor.valueOf(Object.class) if this type is a collection and the element type is not explicitly declared.
* @throws IllegalStateException if this descriptor is not for a java.util.Collection or Array
* If this type is an array, returns the array's component type.
* If this type is a {@link Collection} and it is parameterized, returns the Collection's element type.
* If the Collection is not parameterized, returns null indicating the element type is not declared.
* @return the array component type or Collection element type, or <code>null</code> if this type is a Collection but its element type is not parameterized.
* @throws IllegalStateException if this type is not a java.util.Collection or Array type
*/
public TypeDescriptor getElementTypeDescriptor() {
public TypeDescriptor getElementType() {
if (!isCollection() && !isArray()) {
throw new IllegalStateException("Not a java.util.Collection or Array");
}
return this.elementType;
}
/**
* Returns a copy of this type descriptor that has its elementType populated from the specified Collection.
* This property will be set by calculating the "common element type" of the specified Collection.
* For example, if the collection contains String elements, the returned TypeDescriptor will have its elementType set to String.
* This method is designed to be used when converting values read from Collection fields or method return values that are not parameterized e.g. Collection vs. Collection<String>
* In this scenario the elementType will be Object.class before invoking this method.
* @param colection the collection to derive the elementType from
* @return a new TypeDescriptor with the resolved elementType property
* @throws IllegalArgumentException if this is not a type descriptor for a java.util.Collection.
*/
public TypeDescriptor resolveCollectionElementType(Collection<?> collection) {
if (!isCollection()) {
throw new IllegalStateException("Not a java.util.Collection");
}
return new TypeDescriptor(type, CommonElement.typeDescriptor(collection), mapKeyType, mapValueType, annotations);
public TypeDescriptor elementType(Object element) {
if (elementType != null) {
return elementType.narrowType(element);
} else {
return element != null ? new TypeDescriptor(element.getClass(), null, null, null, annotations) : null;
}
}
// map type descriptor operations
......@@ -359,71 +312,51 @@ public class TypeDescriptor {
* Is this type a {@link Map} type?
*/
public boolean isMap() {
return getType() != null && Map.class.isAssignableFrom(getType());
return Map.class.isAssignableFrom(getType());
}
/**
* If this type is a {@link Map}, returns the underlying key type.
* Returns Object.class if this type is a map and its key type was not explicitly declared.
* @return the map key type, or <code>null</code> if not a map.
* @throws IllegalStateException if this descriptor is not for a java.util.Map
* If this type is a {@link Map} and its key type is parameterized, returns the map's key type.
* If the Map's key type is not parameterized, returns null indicating the key type is not declared.
* @return the Map key type, or <code>null</code> if this type is a Map but its key type is not parameterized.
* @throws IllegalStateException if this type is not a java.util.Map.
*/
public Class<?> getMapKeyType() {
return getMapKeyTypeDescriptor().getType();
}
/**
* The map key type as a type descriptor.
* Returns TypeDescriptor.valueOf(Object.class) if this type is a map and the key type is not explicitly declared.
* @throws IllegalStateException if this descriptor is not for a java.util.Map
*/
public TypeDescriptor getMapKeyTypeDescriptor() {
public TypeDescriptor getMapKeyType() {
if (!isMap()) {
throw new IllegalStateException("Not a map");
}
return this.mapKeyType;
}
/**
* If this type is a {@link Map}, returns the underlying value type.
* Returns <code>null</code> if this type is not map.
* Returns Object.class if this type is a map and its value type was not explicitly declared.
* @return the map value type, or <code>null</code> if not a map.
* @throws IllegalStateException if this descriptor is not for a java.util.Map
*/
public Class<?> getMapValueType() {
return getMapValueTypeDescriptor().getType();
public TypeDescriptor mapKeyType(Object mapKey) {
if (mapKeyType != null) {
return mapKeyType.narrowType(mapKey);
} else {
return mapKey != null ? new TypeDescriptor(mapKey.getClass(), null, null, null, annotations) : null;
}
}
/**
* The map value type as a type descriptor.
* Returns TypeDescriptor.valueOf(Object.class) if this type is a map and the value type is not explicitly declared.
* @throws IllegalStateException if this descriptor is not for a java.util.Map
* If this type is a {@link Map} and its value type is parameterized, returns the map's value type.
* If the Map's value type is not parameterized, returns null indicating the value type is not declared.
* @return the Map value type, or <code>null</code> if this type is a Map but its value type is not parameterized.
* @throws IllegalStateException if this type is not a java.util.Map.
*/
public TypeDescriptor getMapValueTypeDescriptor() {
public TypeDescriptor getMapValueType() {
if (!isMap()) {
throw new IllegalStateException("Not a map");
}
return this.mapValueType;
}
/**
* Returns a copy of this type descriptor that has its mapKeyType and mapValueType properties populated from the specified Map.
* These properties will be set by calculating the "common element type" of the specified Map's keySet and values collection.
* For example, if the Map contains String keys and Integer values, the returned TypeDescriptor will have its mapKeyType set to String and its mapValueType to Integer.
* This method is designed to be used when converting values read from Map fields or method return values that are not parameterized e.g. Map vs. Map<String, Integer>.
* In this scenario the key and value types will be Object.class before invoking this method.
* @param map the map to derive key and value types from
* @return a new TypeDescriptor with the resolved mapKeyType and mapValueType properties
* @throws IllegalArgumentException if this is not a type descriptor for a java.util.Map.
*/
public TypeDescriptor resolveMapKeyValueTypes(Map<?, ?> map) {
if (!isMap()) {
throw new IllegalStateException("Not a java.util.Map");
public TypeDescriptor mapValueType(Object mapValue) {
if (mapValueType != null) {
return mapValueType.narrowType(mapValue);
} else {
return mapValue != null ? new TypeDescriptor(mapValue.getClass(), null, null, null, annotations) : null;
}
return new TypeDescriptor(type, elementType, CommonElement.typeDescriptor(map.keySet()), CommonElement.typeDescriptor(map.values()), annotations);
}
// extending Object
public boolean equals(Object obj) {
......@@ -450,29 +383,24 @@ public class TypeDescriptor {
}
public int hashCode() {
return (this == TypeDescriptor.NULL ? 0 : getType().hashCode());
return getType().hashCode();
}
public String toString() {
if (this == TypeDescriptor.NULL) {
return "null";
StringBuilder builder = new StringBuilder();
Annotation[] anns = getAnnotations();
for (Annotation ann : anns) {
builder.append("@").append(ann.annotationType().getName()).append(' ');
}
else {
StringBuilder builder = new StringBuilder();
Annotation[] anns = getAnnotations();
for (Annotation ann : anns) {
builder.append("@").append(ann.annotationType().getName()).append(' ');
}
builder.append(ClassUtils.getQualifiedName(getType()));
if (isMap()) {
builder.append("<").append(getMapKeyTypeDescriptor());
builder.append(", ").append(getMapValueTypeDescriptor()).append(">");
}
else if (isCollection()) {
builder.append("<").append(getElementTypeDescriptor()).append(">");
}
return builder.toString();
builder.append(ClassUtils.getQualifiedName(getType()));
if (isMap()) {
builder.append("<").append(wildcard(getMapKeyType()));
builder.append(", ").append(wildcard(getMapValueType())).append(">");
}
else if (isCollection()) {
builder.append("<").append(wildcard(getElementType())).append(">");
}
return builder.toString();
}
// package private
......@@ -485,14 +413,6 @@ public class TypeDescriptor {
this.annotations = descriptor.getAnnotations();
}
TypeDescriptor(Class<?> collectionType, TypeDescriptor elementType) {
this(collectionType, elementType, TypeDescriptor.NULL, TypeDescriptor.NULL, EMPTY_ANNOTATION_ARRAY);
}
TypeDescriptor(Class<?> mapType, TypeDescriptor keyType, TypeDescriptor valueType) {
this(mapType, TypeDescriptor.NULL, keyType, valueType, EMPTY_ANNOTATION_ARRAY);
}
static Annotation[] nullSafeAnnotations(Annotation[] annotations) {
return annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY;
}
......@@ -503,8 +423,12 @@ public class TypeDescriptor {
this(new ClassDescriptor(type));
}
private TypeDescriptor() {
this(null, TypeDescriptor.NULL, TypeDescriptor.NULL, TypeDescriptor.NULL, EMPTY_ANNOTATION_ARRAY);
private TypeDescriptor(Class<?> collectionType, TypeDescriptor elementType) {
this(collectionType, elementType, null, null, EMPTY_ANNOTATION_ARRAY);
}
private TypeDescriptor(Class<?> mapType, TypeDescriptor keyType, TypeDescriptor valueType) {
this(mapType, null, keyType, valueType, EMPTY_ANNOTATION_ARRAY);
}
private TypeDescriptor(Class<?> type, TypeDescriptor elementType, TypeDescriptor mapKeyType, TypeDescriptor mapValueType, Annotation[] annotations) {
......@@ -515,8 +439,6 @@ public class TypeDescriptor {
this.annotations = annotations;
}
// internal helpers
private static TypeDescriptor nested(AbstractDescriptor descriptor, int nestingLevel) {
for (int i = 0; i < nestingLevel; i++) {
descriptor = descriptor.nested();
......@@ -524,4 +446,43 @@ public class TypeDescriptor {
return new TypeDescriptor(descriptor);
}
// internal helpers
private boolean mapKeysAssignable(TypeDescriptor targetKeyType) {
TypeDescriptor keyType = getMapKeyType();
if (targetKeyType == null) {
return true;
}
if (keyType == null) {
return false;
}
return keyType.isAssignableTo(targetKeyType);
}
private boolean collectionElementsAssignable(TypeDescriptor targetElementType) {
TypeDescriptor elementType = getElementType();
if (targetElementType == null) {
return true;
}
if (elementType == null) {
return false;
}
return elementType.isAssignableTo(targetElementType);
}
private boolean mapValuesAssignable(TypeDescriptor targetValueType) {
TypeDescriptor valueType = getMapValueType();
if (targetValueType == null) {
return true;
}
if (valueType == null) {
return false;
}
return valueType.isAssignableTo(targetValueType);
}
private String wildcard(TypeDescriptor nestedType) {
return nestedType != null ? nestedType.toString() : "?";
}
}
\ No newline at end of file
......@@ -44,10 +44,6 @@ final class ArrayToArrayConverter implements GenericConverter {
return Collections.singleton(new ConvertiblePair(Object[].class, Object[].class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.helperConverter.matches(sourceType, targetType);
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType);
}
......
......@@ -24,7 +24,7 @@ import java.util.Set;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* Converts an Array to a Collection.
......@@ -36,7 +36,7 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
* @author Keith Donald
* @since 3.0
*/
final class ArrayToCollectionConverter implements ConditionalGenericConverter {
final class ArrayToCollectionConverter implements GenericConverter {
private final ConversionService conversionService;
......@@ -48,10 +48,6 @@ final class ArrayToCollectionConverter implements ConditionalGenericConverter {
return Collections.singleton(new ConvertiblePair(Object[].class, Collection.class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor());
}
@SuppressWarnings("unchecked")
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
......@@ -59,9 +55,7 @@ final class ArrayToCollectionConverter implements ConditionalGenericConverter {
}
int length = Array.getLength(source);
Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), length);
TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor();
TypeDescriptor targetElementType = targetType.getElementTypeDescriptor();
if (Object.class.equals(targetElementType.getType())) {
if (targetType.getElementType() == null) {
for (int i = 0; i < length; i++) {
Object sourceElement = Array.get(source, i);
target.add(sourceElement);
......@@ -69,7 +63,7 @@ final class ArrayToCollectionConverter implements ConditionalGenericConverter {
} else {
for (int i = 0; i < length; i++) {
Object sourceElement = Array.get(source, i);
Object targetElement = this.conversionService.convert(sourceElement, sourceElementType, targetElementType);
Object targetElement = this.conversionService.convert(sourceElement, sourceType.elementType(sourceElement), targetType.getElementType());
target.add(targetElement);
}
}
......
......@@ -22,7 +22,7 @@ import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.util.ObjectUtils;
/**
......@@ -32,7 +32,7 @@ import org.springframework.util.ObjectUtils;
* @author Keith Donald
* @since 3.0
*/
final class ArrayToObjectConverter implements ConditionalGenericConverter {
final class ArrayToObjectConverter implements GenericConverter {
private final CollectionToObjectConverter helperConverter;
......@@ -44,10 +44,6 @@ final class ArrayToObjectConverter implements ConditionalGenericConverter {
return Collections.singleton(new ConvertiblePair(Object[].class, Object.class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.helperConverter.matches(sourceType, targetType);
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType);
}
......
......@@ -22,7 +22,7 @@ import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.util.ObjectUtils;
/**
......@@ -32,7 +32,7 @@ import org.springframework.util.ObjectUtils;
* @author Keith Donald
* @since 3.0
*/
final class ArrayToStringConverter implements ConditionalGenericConverter {
final class ArrayToStringConverter implements GenericConverter {
private final CollectionToStringConverter helperConverter;
......@@ -44,10 +44,6 @@ final class ArrayToStringConverter implements ConditionalGenericConverter {
return Collections.singleton(new ConvertiblePair(Object[].class, String.class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.helperConverter.matches(sourceType, targetType);
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType);
}
......
......@@ -23,7 +23,7 @@ import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* Converts a Collection to an array.
......@@ -36,7 +36,7 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
* @author Keith Donald
* @since 3.0
*/
final class CollectionToArrayConverter implements ConditionalGenericConverter {
final class CollectionToArrayConverter implements GenericConverter {
private final ConversionService conversionService;
......@@ -48,20 +48,15 @@ final class CollectionToArrayConverter implements ConditionalGenericConverter {
return Collections.singleton(new ConvertiblePair(Collection.class, Object[].class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor());
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
Collection<?> sourceCollection = (Collection<?>) source;
TypeDescriptor targetElementType = targetType.getElementTypeDescriptor();
Object array = Array.newInstance(targetElementType.getType(), sourceCollection.size());
Object array = Array.newInstance(targetType.getElementType().getType(), sourceCollection.size());
int i = 0;
for (Object sourceElement : sourceCollection) {
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetElementType);
Object targetElement = this.conversionService.convert(sourceElement, sourceType.elementType(sourceElement), targetType.getElementType());
Array.set(array, i++, targetElement);
}
return array;
......
......@@ -23,7 +23,7 @@ import java.util.Set;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* Converts from a Collection to another Collection.
......@@ -36,7 +36,7 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
* @author Keith Donald
* @since 3.0
*/
final class CollectionToCollectionConverter implements ConditionalGenericConverter {
final class CollectionToCollectionConverter implements GenericConverter {
private final ConversionService conversionService;
......@@ -48,12 +48,6 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert
return Collections.singleton(new ConvertiblePair(Collection.class, Collection.class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor();
TypeDescriptor targetElementType = targetType.getElementTypeDescriptor();
return this.conversionService.canConvert(sourceElementType, targetElementType);
}
@SuppressWarnings("unchecked")
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
......@@ -61,17 +55,15 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert
}
Collection<?> sourceCollection = (Collection<?>) source;
Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), sourceCollection.size());
TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor();
TypeDescriptor targetElementType = targetType.getElementTypeDescriptor();
if (Object.class.equals(targetElementType.getType())) {
for (Object sourceElement : sourceCollection) {
target.add(sourceElement);
}
if (targetType.getElementType() == null) {
for (Object element : sourceCollection) {
target.add(element);
}
} else {
for (Object sourceElement : sourceCollection) {
Object targetElement = this.conversionService.convert(sourceElement, sourceElementType, targetElementType);
Object targetElement = this.conversionService.convert(sourceElement, sourceType.elementType(sourceElement), targetType.getElementType());
target.add(targetElement);
}
}
}
return target;
}
......
......@@ -22,7 +22,7 @@ import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* Converts a Collection to an Object by returning the first collection element after converting it to the desired targetType.
......@@ -30,7 +30,7 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
* @author Keith Donald
* @since 3.0
*/
final class CollectionToObjectConverter implements ConditionalGenericConverter {
final class CollectionToObjectConverter implements GenericConverter {
private final ConversionService conversionService;
......@@ -42,10 +42,6 @@ final class CollectionToObjectConverter implements ConditionalGenericConverter {
return Collections.singleton(new ConvertiblePair(Collection.class, Object.class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType);
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
......@@ -55,7 +51,7 @@ final class CollectionToObjectConverter implements ConditionalGenericConverter {
return null;
}
Object firstElement = sourceCollection.iterator().next();
return this.conversionService.convert(firstElement, sourceType.getElementTypeDescriptor(), targetType);
return this.conversionService.convert(firstElement, sourceType.elementType(firstElement), targetType);
}
}
\ No newline at end of file
......@@ -22,7 +22,7 @@ import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* Converts a Collection to a comma-delimited String.
......@@ -30,7 +30,7 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
* @author Keith Donald
* @since 3.0
*/
final class CollectionToStringConverter implements ConditionalGenericConverter {
final class CollectionToStringConverter implements GenericConverter {
private static final String DELIMITER = ",";
......@@ -44,10 +44,6 @@ final class CollectionToStringConverter implements ConditionalGenericConverter {
return Collections.singleton(new ConvertiblePair(Collection.class, String.class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType);
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
......@@ -56,17 +52,17 @@ final class CollectionToStringConverter implements ConditionalGenericConverter {
if (sourceCollection.size() == 0) {
return "";
}
StringBuilder string = new StringBuilder();
StringBuilder sb = new StringBuilder();
int i = 0;
for (Object sourceElement : sourceCollection) {
if (i > 0) {
string.append(DELIMITER);
sb.append(DELIMITER);
}
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType);
string.append(targetElement);
Object targetElement = this.conversionService.convert(sourceElement, sourceType.elementType(sourceElement), targetType);
sb.append(targetElement);
i++;
}
return string.toString();
return sb.toString();
}
}
......@@ -138,18 +138,16 @@ public class GenericConversionService implements ConfigurableConversionService {
@SuppressWarnings("unchecked")
public <T> T convert(Object source, Class<T> targetType) {
if (targetType == null) {
throw new IllegalArgumentException("The targetType to convert to cannot be null");
}
return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
}
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
assertNotNull(sourceType, targetType);
if (logger.isTraceEnabled()) {
logger.trace("Checking if I can convert " + sourceType + " to " + targetType);
}
if (sourceType == TypeDescriptor.NULL || targetType == TypeDescriptor.NULL) {
logger.trace("Yes, I can convert");
return true;
}
GenericConverter converter = getConverter(sourceType, targetType);
if (converter != null) {
logger.trace("Yes, I can convert");
......@@ -162,21 +160,19 @@ public class GenericConversionService implements ConfigurableConversionService {
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
assertNotNull(sourceType, targetType);
if (logger.isDebugEnabled()) {
logger.debug("Converting value " + StylerUtils.style(source) + " of " + sourceType + " to " + targetType);
if (targetType == null) {
throw new IllegalArgumentException("The targetType to convert to cannot be null");
}
if (sourceType == TypeDescriptor.NULL) {
if (sourceType == null) {
Assert.isTrue(source == null, "The source must be [null] if sourceType == [null]");
return handleResult(sourceType, targetType, convertNullSource(sourceType, targetType));
}
if (targetType == TypeDescriptor.NULL) {
logger.debug("Converted to null");
return null;
}
if (source != null && !sourceType.getObjectType().isInstance(source)) {
throw new IllegalArgumentException("The source to convert from must be an instance of " + sourceType + "; instead it was a " + source.getClass().getName());
}
if (logger.isDebugEnabled()) {
logger.debug("Converting value " + StylerUtils.style(source) + " of " + sourceType + " to " + targetType);
}
GenericConverter converter = getConverter(sourceType, targetType);
if (converter != null) {
return handleResult(sourceType, targetType, ConversionUtils.invokeConverter(converter, source, sourceType, targetType));
......@@ -236,30 +232,26 @@ public class GenericConversionService implements ConfigurableConversionService {
if (logger.isTraceEnabled()) {
logger.trace("Matched cached converter " + converter);
}
return (converter != NO_MATCH ? converter : null);
return converter != NO_MATCH ? converter : null;
}
else {
converter = findConverterForClassPair(sourceType, targetType);
if (converter == null) {
converter = getDefaultConverter(sourceType, targetType);
}
if (converter != null) {
if (logger.isTraceEnabled()) {
logger.trace("Caching under " + key);
logger.trace("Caching matched Converter under key " + key);
}
this.converterCache.put(key, converter);
return converter;
}
converter = getDefaultConverter(sourceType, targetType);
if (converter != null) {
} else {
if (logger.isTraceEnabled()) {
logger.trace("Caching under " + key);
logger.trace("Caching Converter [NO_MATCH] result under key " + key);
}
this.converterCache.put(key, converter);
return converter;
this.converterCache.put(key, NO_MATCH);
return null;
}
if (logger.isTraceEnabled()) {
logger.trace("Caching NO_MATCH under " + key);
}
this.converterCache.put(key, NO_MATCH);
return null;
}
}
......@@ -312,11 +304,6 @@ public class GenericConversionService implements ConfigurableConversionService {
return sourceMap;
}
private void assertNotNull(TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(sourceType, "The sourceType to convert from is required");
Assert.notNull(targetType, "The targetType to convert to is required");
}
private GenericConverter findConverterForClassPair(TypeDescriptor sourceType, TypeDescriptor targetType) {
Class<?> sourceObjectType = sourceType.getObjectType();
if (sourceObjectType.isInterface()) {
......
......@@ -23,7 +23,7 @@ import java.util.Set;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* Converts a Map to another Map.
......@@ -36,7 +36,7 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
* @author Keith Donald
* @since 3.0
*/
final class MapToMapConverter implements ConditionalGenericConverter {
final class MapToMapConverter implements GenericConverter {
private final ConversionService conversionService;
......@@ -48,11 +48,6 @@ final class MapToMapConverter implements ConditionalGenericConverter {
return Collections.singleton(new ConvertiblePair(Map.class, Map.class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType.getMapKeyTypeDescriptor(), targetType.getMapKeyTypeDescriptor()) &&
this.conversionService.canConvert(sourceType.getMapValueTypeDescriptor(), targetType.getMapValueTypeDescriptor());
}
@SuppressWarnings("unchecked")
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
......@@ -60,24 +55,30 @@ final class MapToMapConverter implements ConditionalGenericConverter {
}
Map<Object, Object> sourceMap = (Map<Object, Object>) source;
Map<Object, Object> targetMap = CollectionFactory.createMap(targetType.getType(), sourceMap.size());
TypeDescriptor sourceKeyType = sourceType.getMapKeyTypeDescriptor();
TypeDescriptor targetKeyType = targetType.getMapKeyTypeDescriptor();
TypeDescriptor sourceValueType = sourceType.getMapValueTypeDescriptor();
TypeDescriptor targetValueType = targetType.getMapValueTypeDescriptor();
if (Object.class.equals(targetKeyType.getType()) && Object.class.equals(targetValueType.getType())) {
for (Map.Entry<Object, Object> entry : sourceMap.entrySet()) {
targetMap.put(entry.getKey(), entry.getValue());
}
} else {
for (Map.Entry<Object, Object> entry : sourceMap.entrySet()) {
Object sourceKey = entry.getKey();
Object sourceValue = entry.getValue();
Object targetKey = this.conversionService.convert(sourceKey, sourceKeyType, targetKeyType);
Object targetValue = this.conversionService.convert(sourceValue, sourceValueType, targetValueType);
targetMap.put(targetKey, targetValue);
}
for (Map.Entry<Object, Object> entry : sourceMap.entrySet()) {
Object sourceKey = entry.getKey();
Object sourceValue = entry.getValue();
Object targetKey = convertKey(sourceKey, sourceType, targetType.getMapKeyType());
Object targetValue = convertValue(sourceValue, sourceType, targetType.getMapValueType());
targetMap.put(targetKey, targetValue);
}
return targetMap;
}
// internal helpers
private Object convertKey(Object sourceKey, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (targetType == null) {
return sourceKey;
}
return this.conversionService.convert(sourceKey, sourceType.mapKeyType(sourceKey), targetType);
}
private Object convertValue(Object sourceValue, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (targetType == null) {
return sourceValue;
}
return this.conversionService.convert(sourceValue, sourceType.mapValueType(sourceValue), targetType);
}
}
\ No newline at end of file
......@@ -22,7 +22,7 @@ import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* Converts an Object to a single-element Array containing the Object.
......@@ -31,7 +31,7 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
* @author Keith Donald
* @since 3.0
*/
final class ObjectToArrayConverter implements ConditionalGenericConverter {
final class ObjectToArrayConverter implements GenericConverter {
private final ConversionService conversionService;
......@@ -43,16 +43,12 @@ final class ObjectToArrayConverter implements ConditionalGenericConverter {
return Collections.singleton(new ConvertiblePair(Object.class, Object[].class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
Object target = Array.newInstance(targetType.getElementType(), 1);
Object targetElement = this.conversionService.convert(source, sourceType, targetType.getElementTypeDescriptor());
Object target = Array.newInstance(targetType.getElementType().getType(), 1);
Object targetElement = this.conversionService.convert(source, sourceType, targetType.getElementType());
Array.set(target, 0, targetElement);
return target;
}
......
......@@ -23,7 +23,7 @@ import java.util.Set;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* Converts an Object to a single-element Collection containing the Object.
......@@ -33,7 +33,7 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
* @author Juergen Hoeller
* @since 3.0
*/
final class ObjectToCollectionConverter implements ConditionalGenericConverter {
final class ObjectToCollectionConverter implements GenericConverter {
private final ConversionService conversionService;
......@@ -45,22 +45,17 @@ final class ObjectToCollectionConverter implements ConditionalGenericConverter {
return Collections.singleton(new ConvertiblePair(Object.class, Collection.class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());
}
@SuppressWarnings("unchecked")
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), 1);
TypeDescriptor targetElementType = targetType.getElementTypeDescriptor();
// Avoid potential recursion....
if (targetElementType.isCollection()) {
if (targetType.getElementType() == null || targetType.getElementType().isCollection()) {
target.add(source);
} else {
target.add(this.conversionService.convert(source, sourceType, targetElementType));
Object singleElement = this.conversionService.convert(source, sourceType, targetType.getElementType());
target.add(singleElement);
}
return target;
}
......
......@@ -48,9 +48,7 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
Class<?> source = sourceType.getType();
Class<?> target = targetType.getType();
return !source.equals(target) && hasValueOfMethodOrConstructor(target, source);
return !sourceType.equals(targetType) && hasValueOfMethodOrConstructor(targetType.getType(), sourceType.getType());
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
......@@ -79,16 +77,16 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
") method or Constructor(" + sourceClass.getName() + ") exists on " + targetClass.getName());
}
public static boolean hasValueOfMethodOrConstructor(Class<?> targetClass, Class<?> sourceClass) {
return getValueOfMethodOn(targetClass, sourceClass) != null || getConstructor(targetClass, sourceClass) != null;
static boolean hasValueOfMethodOrConstructor(Class<?> clazz, Class<?> sourceParameterType) {
return getValueOfMethodOn(clazz, sourceParameterType) != null || getConstructor(clazz, sourceParameterType) != null;
}
private static Method getValueOfMethodOn(Class<?> targetClass, Class<?> sourceClass) {
return ClassUtils.getStaticMethod(targetClass, "valueOf", sourceClass);
private static Method getValueOfMethodOn(Class<?> clazz, Class<?> sourceParameterType) {
return ClassUtils.getStaticMethod(clazz, "valueOf", sourceParameterType);
}
private static Constructor<?> getConstructor(Class<?> targetClass, Class<?> sourceClass) {
return ClassUtils.getConstructorIfAvailable(targetClass, sourceClass);
private static Constructor<?> getConstructor(Class<?> clazz, Class<?> sourceParameterType) {
return ClassUtils.getConstructorIfAvailable(clazz, sourceParameterType);
}
}
......@@ -22,7 +22,7 @@ import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.util.StringUtils;
/**
......@@ -31,7 +31,7 @@ import org.springframework.util.StringUtils;
* @author Keith Donald
* @since 3.0
*/
final class StringToArrayConverter implements ConditionalGenericConverter {
final class StringToArrayConverter implements GenericConverter {
private final ConversionService conversionService;
......@@ -43,20 +43,16 @@ final class StringToArrayConverter implements ConditionalGenericConverter {
return Collections.singleton(new ConvertiblePair(String.class, Object[].class));
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
String string = (String) source;
String[] fields = StringUtils.commaDelimitedListToStringArray(string);
Object target = Array.newInstance(targetType.getElementType(), fields.length);
Object target = Array.newInstance(targetType.getElementType().getType(), fields.length);
for (int i = 0; i < fields.length; i++) {
String sourceElement = fields[i];
Object targetElement = this.conversionService.convert(sourceElement.trim(), sourceType, targetType.getElementTypeDescriptor());
Object targetElement = this.conversionService.convert(sourceElement.trim(), sourceType, targetType.getElementType());
Array.set(target, i, targetElement);
}
return target;
......
......@@ -45,7 +45,10 @@ final class StringToCollectionConverter implements ConditionalGenericConverter {
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());
if (targetType.getElementType() == null) {
return true;
}
return this.conversionService.canConvert(sourceType, targetType.getElementType());
}
@SuppressWarnings("unchecked")
......@@ -56,11 +59,17 @@ final class StringToCollectionConverter implements ConditionalGenericConverter {
String string = (String) source;
String[] fields = StringUtils.commaDelimitedListToStringArray(string);
Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), fields.length);
for (String sourceElement : fields) {
Object targetElement = this.conversionService.convert(sourceElement.trim(), sourceType, targetType.getElementTypeDescriptor());
target.add(targetElement);
if (targetType.getElementType() == null) {
for (String field : fields) {
target.add(field.trim());
}
} else {
for (String field : fields) {
Object targetElement = this.conversionService.convert(field.trim(), sourceType, targetType.getElementType());
target.add(targetElement);
}
}
return target;
}
}
}
\ No newline at end of file
......@@ -28,7 +28,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
......@@ -37,7 +36,6 @@ import java.util.Map;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter;
/**
......@@ -62,18 +60,6 @@ public class TypeDescriptorTests {
public Map<String, List<Integer>> nestedMapField = new HashMap<String, List<Integer>>();
@Test
public void nullTypeDescriptor() {
TypeDescriptor desc = TypeDescriptor.NULL;
assertEquals(false, desc.isMap());
assertEquals(false, desc.isCollection());
assertEquals(false, desc.isArray());
assertEquals(null, desc.getType());
assertEquals(null, desc.getObjectType());
assertEquals(null, desc.getName());
assertEquals(0, desc.getAnnotations().length);
}
@Test
public void parameterPrimitive() throws Exception {
TypeDescriptor desc = new TypeDescriptor(new MethodParameter(getClass().getMethod("testParameterPrimitive", int.class), 0));
......@@ -121,12 +107,12 @@ public class TypeDescriptorTests {
assertEquals(0, desc.getAnnotations().length);
assertTrue(desc.isCollection());
assertFalse(desc.isArray());
assertEquals(List.class, desc.getElementType());
assertEquals(TypeDescriptor.nested(methodParameter, 1), desc.getElementTypeDescriptor());
assertEquals(TypeDescriptor.nested(methodParameter, 2), desc.getElementTypeDescriptor().getElementTypeDescriptor());
assertEquals(TypeDescriptor.nested(methodParameter, 3), desc.getElementTypeDescriptor().getElementTypeDescriptor().getMapValueTypeDescriptor());
assertEquals(Integer.class, desc.getElementTypeDescriptor().getElementTypeDescriptor().getMapKeyTypeDescriptor().getType());
assertEquals(Enum.class, desc.getElementTypeDescriptor().getElementTypeDescriptor().getMapValueTypeDescriptor().getType());
assertEquals(List.class, desc.getElementType().getType());
assertEquals(TypeDescriptor.nested(methodParameter, 1), desc.getElementType());
assertEquals(TypeDescriptor.nested(methodParameter, 2), desc.getElementType().getElementType());
assertEquals(TypeDescriptor.nested(methodParameter, 3), desc.getElementType().getElementType().getMapValueType());
assertEquals(Integer.class, desc.getElementType().getElementType().getMapKeyType().getType());
assertEquals(Enum.class, desc.getElementType().getElementType().getMapValueType().getType());
assertFalse(desc.isMap());
}
......@@ -141,13 +127,12 @@ public class TypeDescriptorTests {
assertEquals(List.class, desc.getType());
assertEquals(List.class, desc.getObjectType());
assertEquals("java.util.List", desc.getName());
assertEquals("java.util.List<java.lang.Object>", desc.toString());
assertEquals("java.util.List<?>", desc.toString());
assertTrue(!desc.isPrimitive());
assertEquals(0, desc.getAnnotations().length);
assertTrue(desc.isCollection());
assertFalse(desc.isArray());
assertEquals(Object.class, desc.getElementType());
assertEquals(TypeDescriptor.valueOf(Object.class), desc.getElementTypeDescriptor());
assertNull(desc.getElementType());
assertFalse(desc.isMap());
}
......@@ -167,8 +152,8 @@ public class TypeDescriptorTests {
assertEquals(0, desc.getAnnotations().length);
assertFalse(desc.isCollection());
assertTrue(desc.isArray());
assertEquals(Integer.class, desc.getElementType());
assertEquals(TypeDescriptor.valueOf(Integer.class), desc.getElementTypeDescriptor());
assertEquals(Integer.class, desc.getElementType().getType());
assertEquals(TypeDescriptor.valueOf(Integer.class), desc.getElementType());
assertFalse(desc.isMap());
}
......@@ -189,11 +174,11 @@ public class TypeDescriptorTests {
assertFalse(desc.isCollection());
assertFalse(desc.isArray());
assertTrue(desc.isMap());
assertEquals(TypeDescriptor.nested(methodParameter, 1), desc.getMapValueTypeDescriptor());
assertEquals(TypeDescriptor.nested(methodParameter, 2), desc.getMapValueTypeDescriptor().getElementTypeDescriptor());
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getType());
assertEquals(List.class, desc.getMapValueTypeDescriptor().getType());
assertEquals(String.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getType());
assertEquals(TypeDescriptor.nested(methodParameter, 1), desc.getMapValueType());
assertEquals(TypeDescriptor.nested(methodParameter, 2), desc.getMapValueType().getElementType());
assertEquals(Integer.class, desc.getMapKeyType().getType());
assertEquals(List.class, desc.getMapValueType().getType());
assertEquals(String.class, desc.getMapValueType().getElementType().getType());
}
public void testParameterMap(Map<Integer, List<String>> map) {
......@@ -222,8 +207,8 @@ public class TypeDescriptorTests {
public void propertyComplex() throws Exception {
PropertyDescriptor property = new PropertyDescriptor("complexProperty", getClass().getMethod("getComplexProperty", null), getClass().getMethod("setComplexProperty", Map.class));
TypeDescriptor desc = new TypeDescriptor(getClass(), property);
//assertEquals(String.class, desc.getMapKeyType());
assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getElementType());
assertEquals(String.class, desc.getMapKeyType().getType());
assertEquals(Integer.class, desc.getMapValueType().getElementType().getElementType().getType());
}
public Map<String, List<List<Integer>>> getComplexProperty() {
......@@ -248,7 +233,7 @@ public class TypeDescriptorTests {
PropertyDescriptor property = new PropertyDescriptor("listProperty", genericBean.getClass().getMethod("getListProperty", null), genericBean.getClass().getMethod("setListProperty", List.class));
TypeDescriptor desc = new TypeDescriptor(genericBean.getClass(), property);
assertEquals(List.class, desc.getType());
assertEquals(Integer.class, desc.getElementType());
assertEquals(Integer.class, desc.getElementType().getType());
}
public interface GenericType<T> {
......@@ -292,7 +277,7 @@ public class TypeDescriptorTests {
PropertyDescriptor property = new PropertyDescriptor("listProperty", genericBean.getClass().getMethod("getListProperty", null), genericBean.getClass().getMethod("setListProperty", List.class));
TypeDescriptor desc = new TypeDescriptor(genericBean.getClass(), property);
assertEquals(List.class, desc.getType());
assertEquals(Integer.class, desc.getElementType());
assertEquals(Integer.class, desc.getElementType().getType());
assertNotNull(desc.getAnnotation(MethodAnnotation1.class));
}
......@@ -323,8 +308,8 @@ public class TypeDescriptorTests {
public void property() throws Exception {
PropertyDescriptor property = new PropertyDescriptor("property", getClass().getMethod("getProperty", null), getClass().getMethod("setProperty", Map.class));
TypeDescriptor desc = new TypeDescriptor(getClass(), property);
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementType());
assertEquals(Long.class, desc.getMapValueTypeDescriptor().getElementType());
assertEquals(Integer.class, desc.getMapKeyType().getElementType().getType());
assertEquals(Long.class, desc.getMapValueType().getElementType().getType());
assertNotNull(desc.getAnnotation(MethodAnnotation1.class));
assertNotNull(desc.getAnnotation(MethodAnnotation2.class));
assertNotNull(desc.getAnnotation(MethodAnnotation3.class));
......@@ -379,8 +364,7 @@ public class TypeDescriptorTests {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfString"));
assertFalse(typeDescriptor.isArray());
assertEquals(List.class, typeDescriptor.getType());
assertEquals(String.class, typeDescriptor.getElementType());
// TODO caught shorten these names but it is OK that they are fully qualified for now
assertEquals(String.class, typeDescriptor.getElementType().getType());
assertEquals("java.util.List<java.lang.String>", typeDescriptor.toString());
}
......@@ -389,8 +373,8 @@ public class TypeDescriptorTests {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfListOfString"));
assertFalse(typeDescriptor.isArray());
assertEquals(List.class, typeDescriptor.getType());
assertEquals(List.class, typeDescriptor.getElementType());
assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType());
assertEquals(List.class, typeDescriptor.getElementType().getType());
assertEquals(String.class, typeDescriptor.getElementType().getElementType().getType());
assertEquals("java.util.List<java.util.List<java.lang.String>>", typeDescriptor.toString());
}
......@@ -399,16 +383,16 @@ public class TypeDescriptorTests {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfListOfUnknown"));
assertFalse(typeDescriptor.isArray());
assertEquals(List.class, typeDescriptor.getType());
assertEquals(List.class, typeDescriptor.getElementType());
assertEquals(Object.class, typeDescriptor.getElementTypeDescriptor().getElementType());
assertEquals("java.util.List<java.util.List<java.lang.Object>>", typeDescriptor.toString());
assertEquals(List.class, typeDescriptor.getElementType().getType());
assertNull(typeDescriptor.getElementType().getElementType());
assertEquals("java.util.List<java.util.List<?>>", typeDescriptor.toString());
}
@Test
public void fieldArray() throws Exception {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("intArray"));
assertTrue(typeDescriptor.isArray());
assertEquals(Integer.TYPE,typeDescriptor.getElementType());
assertEquals(Integer.TYPE,typeDescriptor.getElementType().getType());
assertEquals("int[]",typeDescriptor.toString());
}
......@@ -418,7 +402,7 @@ public class TypeDescriptorTests {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("arrayOfListOfString"));
assertTrue(typeDescriptor.isArray());
assertEquals(List.class,typeDescriptor.getElementType());
assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType());
assertEquals(String.class, typeDescriptor.getElementType().getElementType());
assertEquals("java.util.List[]",typeDescriptor.toString());
}
......@@ -426,9 +410,9 @@ public class TypeDescriptorTests {
public void fieldComplexTypeDescriptor2() 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(String.class,typeDescriptor.getMapKeyType().getType());
assertEquals(List.class, typeDescriptor.getMapValueType().getType());
assertEquals(Integer.class, typeDescriptor.getMapValueType().getElementType().getType());
assertEquals("java.util.Map<java.lang.String, java.util.List<java.lang.Integer>>", typeDescriptor.toString());
}
......@@ -438,8 +422,8 @@ public class TypeDescriptorTests {
// TODO: SPR-8394: typeIndex handling not currently supported by fields
TypeDescriptor desc = new TypeDescriptor(getClass().getField("fieldMap"));
assertTrue(desc.isMap());
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementType());
assertEquals(Long.class, desc.getMapValueTypeDescriptor().getElementType());
assertEquals(Integer.class, desc.getMapKeyType().getElementType());
assertEquals(Long.class, desc.getMapValueType().getElementType());
}
public Map<List<Integer>, List<Long>> fieldMap;
......@@ -488,7 +472,7 @@ public class TypeDescriptorTests {
assertTrue(typeDescriptor.isArray());
assertFalse(typeDescriptor.isCollection());
assertFalse(typeDescriptor.isMap());
assertEquals(Integer.TYPE, typeDescriptor.getElementType());
assertEquals(Integer.TYPE, typeDescriptor.getElementType().getType());
}
@Test
......@@ -497,117 +481,19 @@ public class TypeDescriptorTests {
assertTrue(typeDescriptor.isCollection());
assertFalse(typeDescriptor.isArray());
assertFalse(typeDescriptor.isMap());
assertEquals(Object.class, typeDescriptor.getElementType());
}
@Test
public void forObjectCollection() {
List<String> list = new ArrayList<String>();
list.add("1");
TypeDescriptor desc = TypeDescriptor.forObject(list);
assertEquals(String.class, desc.getElementType());
}
@Test
public void forObjectCollectionEmpty() {
List<String> list = new ArrayList<String>();
TypeDescriptor desc = TypeDescriptor.forObject(list);
assertNull(desc.getElementType());
}
@Test
public void forObjectCollectionSuperClassCommonType() throws SecurityException, NoSuchFieldException {
List<Number> list = new ArrayList<Number>();
list.add(1);
list.add(2L);
TypeDescriptor desc = TypeDescriptor.forObject(list);
assertEquals(Number.class, desc.getElementType());
assertNull(typeDescriptor.getElementType());
}
public List<Long> longs;
@Test
public void forObjectCollectionNoObviousCommonType() {
List<Object> collection = new ArrayList<Object>();
List<String> list = new ArrayList<String>();
list.add("1");
collection.add(list);
Map<String, String> map = new HashMap<String, String>();
collection.add(map);
map.put("1", "2");
TypeDescriptor desc = TypeDescriptor.forObject(collection);
assertEquals(Cloneable.class, desc.getElementType());
}
@Test
public void forObjectCollectionNoCommonType() {
List<Object> collection = new ArrayList<Object>();
collection.add(new Object());
collection.add("1");
TypeDescriptor desc = TypeDescriptor.forObject(collection);
assertEquals(Object.class, desc.getElementType());
}
@Test
public void forObjectCollectionNested() {
List<Object> collection = new ArrayList<Object>();
collection.add(Arrays.asList("1", "2"));
collection.add(Arrays.asList("3", "4"));
TypeDescriptor desc = TypeDescriptor.forObject(collection);
assertEquals(Arrays.asList("foo").getClass(), desc.getElementType());
assertEquals(String.class, desc.getElementTypeDescriptor().getElementType());
}
@Test
public void forObjectMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "2");
TypeDescriptor desc = TypeDescriptor.forObject(map);
assertEquals(String.class, desc.getMapKeyType());
assertEquals(String.class, desc.getMapValueType());
}
@Test
public void forObjectMapEmpty() {
Map<String, String> map = new HashMap<String, String>();
TypeDescriptor desc = TypeDescriptor.forObject(map);
assertNull(desc.getMapKeyType());
assertNull(desc.getMapValueType());
}
@Test
public void forObjectMapCommonSuperClass() {
Map<Number, Number> map = new HashMap<Number, Number>();
map.put(1, 2);
map.put(2L, 3L);
TypeDescriptor desc = TypeDescriptor.forObject(map);
assertEquals(Number.class, desc.getMapKeyType());
assertEquals(Number.class, desc.getMapValueType());
}
@Test
public void forObjectMapNoObviousCommonType() {
Map<Object, Object> map = new HashMap<Object, Object>();
map.put("1", "2");
map.put(2, 2);
TypeDescriptor desc = TypeDescriptor.forObject(map);
assertEquals(Comparable.class, desc.getMapKeyType());
assertEquals(Comparable.class, desc.getMapValueType());
public void forObject() {
TypeDescriptor desc = TypeDescriptor.forObject("3");
assertEquals(String.class, desc.getType());
}
@Test
public void forObjectMapNested() {
Map<Integer, List<String>> map = new HashMap<Integer, List<String>>();
map.put(1, Arrays.asList("1, 2"));
TypeDescriptor desc = TypeDescriptor.forObject(map);
assertEquals(Integer.class, desc.getMapKeyType());
assertEquals(String.class, desc.getMapValueTypeDescriptor().getElementType());
}
@Test
public void nestedMethodParameterType() throws Exception {
TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test1", List.class), 0), 1);
assertEquals(String.class, t1.getType());
public void forObjectNullTypeDescriptor() {
TypeDescriptor desc = TypeDescriptor.forObject(null);
assertNull(desc);
}
@Test
......@@ -692,8 +578,8 @@ public class TypeDescriptorTests {
assertEquals(0, desc.getAnnotations().length);
assertTrue(desc.isCollection());
assertFalse(desc.isArray());
assertEquals(Integer.class, desc.getElementType());
assertEquals(TypeDescriptor.valueOf(Integer.class), desc.getElementTypeDescriptor());
assertEquals(Integer.class, desc.getElementType().getType());
assertEquals(TypeDescriptor.valueOf(Integer.class), desc.getElementType());
assertFalse(desc.isMap());
}
......@@ -708,8 +594,8 @@ public class TypeDescriptorTests {
assertEquals(0, desc.getAnnotations().length);
assertTrue(desc.isCollection());
assertFalse(desc.isArray());
assertEquals(List.class, desc.getElementType());
assertEquals(TypeDescriptor.valueOf(Integer.class), desc.getElementTypeDescriptor().getElementTypeDescriptor());
assertEquals(List.class, desc.getElementType().getType());
assertEquals(TypeDescriptor.valueOf(Integer.class), desc.getElementType().getElementType());
assertFalse(desc.isMap());
}
......@@ -725,8 +611,8 @@ public class TypeDescriptorTests {
assertFalse(desc.isCollection());
assertFalse(desc.isArray());
assertTrue(desc.isMap());
assertEquals(String.class, desc.getMapKeyTypeDescriptor().getType());
assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getType());
assertEquals(String.class, desc.getMapKeyType().getType());
assertEquals(Integer.class, desc.getMapValueType().getType());
}
@Test
......@@ -742,9 +628,9 @@ public class TypeDescriptorTests {
assertFalse(desc.isCollection());
assertFalse(desc.isArray());
assertTrue(desc.isMap());
assertEquals(String.class, desc.getMapKeyTypeDescriptor().getType());
assertEquals(String.class, desc.getMapValueTypeDescriptor().getMapKeyTypeDescriptor().getType());
assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getMapValueTypeDescriptor().getType());
assertEquals(String.class, desc.getMapKeyType().getType());
assertEquals(String.class, desc.getMapValueType().getMapKeyType().getType());
assertEquals(Integer.class, desc.getMapValueType().getMapValueType().getType());
}
@Test
......
......@@ -16,6 +16,7 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.io.ClassPathResource;
......@@ -38,7 +39,12 @@ public class CollectionToCollectionConverterTests {
list.add("37");
TypeDescriptor sourceType = TypeDescriptor.forObject(list);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("scalarListTarget"));
assertFalse(conversionService.canConvert(sourceType, targetType));
assertTrue(conversionService.canConvert(sourceType, targetType));
try {
conversionService.convert(list, sourceType, targetType);
} catch (ConversionFailedException e) {
assertTrue(e.getCause() instanceof ConverterNotFoundException);
}
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
......@@ -169,7 +175,7 @@ public class CollectionToCollectionConverterTests {
assertEquals(resources, conversionService.convert(resources, sourceType, new TypeDescriptor(getClass().getField("resources"))));
}
@Test(expected=ConverterNotFoundException.class)
@Test
public void allNullsNotConvertible() throws Exception {
List<Resource> resources = new ArrayList<Resource>();
resources.add(null);
......@@ -180,7 +186,7 @@ public class CollectionToCollectionConverterTests {
public List<String> allNullsNotConvertible;
@Test(expected=ConverterNotFoundException.class)
@Test(expected=ConversionFailedException.class)
public void nothingInCommon() throws Exception {
List<Object> resources = new ArrayList<Object>();
resources.add(new ClassPathResource("test"));
......
......@@ -73,7 +73,7 @@ public class GenericConversionServiceTests {
@Test(expected=IllegalArgumentException.class)
public void convertNotNullSourceNullSourceTypeDescriptor() {
conversionService.convert("3", TypeDescriptor.NULL, TypeDescriptor.valueOf(int.class));
conversionService.convert("3", null, TypeDescriptor.valueOf(int.class));
}
@Test
......@@ -124,14 +124,15 @@ public class GenericConversionServiceTests {
assertNull(conversionService.convert(null, Integer.class));
}
@Test(expected=IllegalArgumentException.class)
public void convertNullTargetClass() {
assertNull(conversionService.convert("3", (Class<?>) null));
assertNull(conversionService.convert("3", TypeDescriptor.valueOf(String.class), TypeDescriptor.NULL));
assertNull(conversionService.convert("3", TypeDescriptor.valueOf(String.class), null));
}
@Test
@Test(expected=IllegalArgumentException.class)
public void convertNullTypeDescriptor() {
assertNull(conversionService.convert("3", TypeDescriptor.valueOf(String.class), TypeDescriptor.NULL));
assertNull(conversionService.convert("3", TypeDescriptor.valueOf(String.class), null));
}
@Test(expected=IllegalArgumentException.class)
......@@ -186,7 +187,12 @@ public class GenericConversionServiceTests {
@Test
public void genericConverterDelegatingBackToConversionServiceConverterNotFound() {
conversionService.addConverter(new ObjectToArrayConverter(conversionService));
assertFalse(conversionService.canConvert(String.class, Integer[].class));
assertTrue(conversionService.canConvert(String.class, Integer[].class));
try {
conversionService.convert("3,4,5", Integer[].class);
} catch (ConversionFailedException e) {
assertTrue(e.getCause() instanceof ConverterNotFoundException);
}
}
@Test
......
......@@ -12,6 +12,8 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
public class MapToMapConverterTests {
......@@ -30,7 +32,12 @@ public class MapToMapConverterTests {
map.put("2", "37");
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("scalarMapTarget"));
assertFalse(conversionService.canConvert(sourceType, targetType));
assertTrue(conversionService.canConvert(sourceType, targetType));
try {
conversionService.convert(map, sourceType, targetType);
} catch (ConversionFailedException e) {
assertTrue(e.getCause() instanceof ConverterNotFoundException);
}
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
......@@ -58,7 +65,19 @@ public class MapToMapConverterTests {
map.put("2", "37");
TypeDescriptor sourceType = new TypeDescriptor(getClass().getField("notGenericMapSource"));
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("scalarMapTarget"));
assertFalse(conversionService.canConvert(sourceType, targetType));
assertTrue(conversionService.canConvert(sourceType, targetType));
try {
conversionService.convert(map, sourceType, targetType);
} catch (ConversionFailedException e) {
assertTrue(e.getCause() instanceof ConverterNotFoundException);
}
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
Map<Integer, Integer> result = (Map<Integer, Integer>) conversionService.convert(map, sourceType, targetType);
assertFalse(map.equals(result));
assertEquals((Integer) 9, result.get(1));
assertEquals((Integer) 37, result.get(2));
}
public Map notGenericMapSource;
......@@ -70,7 +89,12 @@ public class MapToMapConverterTests {
map.put("2", Arrays.asList("37", "23"));
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("collectionMapTarget"));
assertFalse(conversionService.canConvert(sourceType, targetType));
assertTrue(conversionService.canConvert(sourceType, targetType));
try {
conversionService.convert(map, sourceType, targetType);
} catch (ConversionFailedException e) {
assertTrue(e.getCause() instanceof ConverterNotFoundException);
}
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
......@@ -83,8 +107,6 @@ public class MapToMapConverterTests {
public Map<Integer, List<Integer>> collectionMapTarget;
public Map<String, List<String>> sourceCollectionMapTarget;
@Test
public void collectionMapSourceTarget() throws Exception {
Map<String, List<String>> map = new HashMap<String, List<String>>();
......@@ -92,7 +114,12 @@ public class MapToMapConverterTests {
map.put("2", Arrays.asList("37", "23"));
TypeDescriptor sourceType = new TypeDescriptor(getClass().getField("sourceCollectionMapTarget"));
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("collectionMapTarget"));
assertFalse(conversionService.canConvert(sourceType, targetType));
assertTrue(conversionService.canConvert(sourceType, targetType));
try {
conversionService.convert(map, sourceType, targetType);
} catch (ConversionFailedException e) {
assertTrue(e.getCause() instanceof ConverterNotFoundException);
}
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
......@@ -103,6 +130,8 @@ public class MapToMapConverterTests {
assertEquals(Arrays.asList(37, 23), result.get(2));
}
public Map<String, List<String>> sourceCollectionMapTarget;
@Test
public void collectionMapNotGenericTarget() throws Exception {
Map<String, List<String>> map = new HashMap<String, List<String>>();
......
......@@ -16,9 +16,6 @@
package org.springframework.expression;
import java.util.Collection;
import java.util.Map;
import org.springframework.core.convert.TypeDescriptor;
/**
......@@ -56,7 +53,7 @@ public class TypedValue {
*/
public TypedValue(Object value, TypeDescriptor typeDescriptor) {
this.value = value;
this.typeDescriptor = initTypeDescriptor(value, typeDescriptor);
this.typeDescriptor = typeDescriptor;
}
public Object getValue() {
......@@ -77,20 +74,4 @@ public class TypedValue {
return str.toString();
}
// interal helpers
private static TypeDescriptor initTypeDescriptor(Object value, TypeDescriptor typeDescriptor) {
if (value == null) {
return typeDescriptor;
}
if (typeDescriptor.isCollection() && Object.class.equals(typeDescriptor.getElementType())) {
return typeDescriptor.resolveCollectionElementType((Collection<?>) value);
} else if (typeDescriptor.isMap() && Object.class.equals(typeDescriptor.getMapKeyType())
&& Object.class.equals(typeDescriptor.getMapValueType())){
return typeDescriptor.resolveMapKeyValueTypes((Map<?, ?>) value);
} else {
return typeDescriptor;
}
}
}
......@@ -41,7 +41,12 @@ public class FormatHelper {
if (i > 0) {
sb.append(",");
}
sb.append(formatClassNameForMessage(argumentTypes.get(i).getType()));
TypeDescriptor typeDescriptor = argumentTypes.get(i);
if (typeDescriptor != null) {
sb.append(formatClassNameForMessage(typeDescriptor.getClass()));
} else {
sb.append(formatClassNameForMessage(null));
}
}
sb.append(")");
return sb.toString();
......
......@@ -106,7 +106,7 @@ public class FunctionReference extends SpelNodeImpl {
try {
ReflectionUtils.makeAccessible(method);
Object result = method.invoke(method.getClass(), functionArgs);
return new TypedValue(result, new TypeDescriptor(new MethodParameter(method,-1)));
return new TypedValue(result, new TypeDescriptor(new MethodParameter(method,-1)).narrowType(result));
}
catch (Exception ex) {
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_FUNCTION_CALL,
......
......@@ -90,9 +90,12 @@ public class Indexer extends SpelNodeImpl {
// Indexing into a Map
if (targetObject instanceof Map) {
Object possiblyConvertedKey = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
Object o = ((Map<?, ?>) targetObject).get(possiblyConvertedKey);
return new TypedValue(o, targetObjectTypeDescriptor.getMapValueTypeDescriptor());
Object key = index;
if (targetObjectTypeDescriptor.getMapKeyType() != null) {
key = state.convertValue(key, targetObjectTypeDescriptor.getMapKeyType());
}
Object value = ((Map<?, ?>) targetObject).get(key);
return new TypedValue(value, targetObjectTypeDescriptor.mapValueType(value));
}
if (targetObject == null) {
......@@ -100,22 +103,22 @@ public class Indexer extends SpelNodeImpl {
}
// if the object is something that looks indexable by an integer, attempt to treat the index value as a number
if ((targetObject instanceof Collection ) || targetObject.getClass().isArray() || targetObject instanceof String) {
int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
if (targetObject instanceof Collection || targetObject.getClass().isArray() || targetObject instanceof String) {
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
if (targetObject.getClass().isArray()) {
Object arrayElement = accessArrayElement(targetObject, idx);
return new TypedValue(arrayElement, targetObjectTypeDescriptor.getElementTypeDescriptor());
return new TypedValue(arrayElement, targetObjectTypeDescriptor.elementType(arrayElement));
} else if (targetObject instanceof Collection) {
Collection c = (Collection) targetObject;
if (idx >= c.size()) {
if (!growCollection(state, targetObjectTypeDescriptor.getElementType(), idx, c)) {
if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
}
}
int pos = 0;
for (Object o : c) {
if (pos == idx) {
return new TypedValue(o, targetObjectTypeDescriptor.getElementTypeDescriptor());
return new TypedValue(o, targetObjectTypeDescriptor.elementType(o));
}
pos++;
}
......@@ -181,33 +184,38 @@ public class Indexer extends SpelNodeImpl {
throw new SpelEvaluationException(SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
}
// Indexing into a Map
if (targetObjectTypeDescriptor.isMap()) {
Map map = (Map)targetObject;
Object possiblyConvertedKey = index;
Object possiblyConvertedValue = newValue;
possiblyConvertedKey = state.convertValue(index.getValue(), targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
possiblyConvertedValue = state.convertValue(newValue, targetObjectTypeDescriptor.getMapValueTypeDescriptor());
map.put(possiblyConvertedKey,possiblyConvertedValue);
if (targetObject instanceof Map) {
Map map = (Map) targetObject;
Object key = index.getValue();
if (targetObjectTypeDescriptor.getMapKeyType() != null) {
key = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyType());
}
if (targetObjectTypeDescriptor.getMapValueType() != null) {
newValue = state.convertValue(newValue, targetObjectTypeDescriptor.getMapValueType());
}
map.put(key, newValue);
return;
}
if (targetObjectTypeDescriptor.isArray()) {
int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
setArrayElement(state, contextObject.getValue(), idx, newValue, targetObjectTypeDescriptor.getElementType());
setArrayElement(state, contextObject.getValue(), idx, newValue, targetObjectTypeDescriptor.getElementType().getType());
return;
}
else if (targetObjectTypeDescriptor.isCollection()) {
int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
else if (targetObject instanceof Collection) {
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
Collection c = (Collection) targetObject;
if (idx >= c.size()) {
if (!growCollection(state, targetObjectTypeDescriptor.getElementType(), idx, c)) {
if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
}
}
if (targetObject instanceof List) {
List list = (List)targetObject;
Object possiblyConvertedValue = state.convertValue(newValue, targetObjectTypeDescriptor.getElementTypeDescriptor());
list.set(idx,possiblyConvertedValue);
List list = (List) targetObject;
if (targetObjectTypeDescriptor.getElementType() != null) {
newValue = state.convertValue(newValue, targetObjectTypeDescriptor.getElementType());
}
list.set(idx, newValue);
return;
}
else {
......@@ -217,7 +225,7 @@ public class Indexer extends SpelNodeImpl {
// Try and treat the index value as a property of the context object
// TODO could call the conversion service to convert the value to a String
if (index.getTypeDescriptor().getType()==String.class) {
if (index.getTypeDescriptor().getType() == String.class) {
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
String name = (String)index.getValue();
EvaluationContext eContext = state.getEvaluationContext();
......@@ -260,20 +268,21 @@ public class Indexer extends SpelNodeImpl {
* @return true if collection growing succeeded, otherwise false
*/
@SuppressWarnings("unchecked")
private boolean growCollection(ExpressionState state, Class<?> elementType, int index,
private boolean growCollection(ExpressionState state, TypeDescriptor targetType, int index,
Collection collection) {
if (state.getConfiguration().isAutoGrowCollections()) {
if (targetType.getElementType() == null) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
}
TypeDescriptor elementType = targetType.getElementType();
Object newCollectionElement = null;
try {
int newElements = index-collection.size();
if (elementType == null || elementType == Object.class) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
}
int newElements = index - collection.size();
while (newElements>0) {
collection.add(elementType.newInstance());
collection.add(elementType.getType().newInstance());
newElements--;
}
newCollectionElement = elementType.newInstance();
newCollectionElement = elementType.getType().newInstance();
}
catch (Exception ex) {
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION);
......
......@@ -142,7 +142,7 @@ public class Selection extends SpelNodeImpl {
return new TypedValue(result);
}
else {
Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementType());
Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementType().getType());
Object resultArray = Array.newInstance(elementType, result.size());
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
return new TypedValue(resultArray);
......
......@@ -103,30 +103,29 @@ public class SpelExpression implements Expression {
return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType);
}
public Class getValueType() throws EvaluationException {
return ast.getValueInternal(new ExpressionState(getEvaluationContext(), configuration)).getTypeDescriptor().getType();
return getValueType(getEvaluationContext());
}
public Class getValueType(Object rootObject) throws EvaluationException {
return getValueType(getEvaluationContext(), rootObject);
}
public Class getValueType(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ExpressionState eState = new ExpressionState(context, configuration);
TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
return typeDescriptor.getType();
return typeDescriptor != null ? typeDescriptor.getType() : null;
}
public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), configuration);
TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
return typeDescriptor.getType();
}
public Class getValueType(Object rootObject) throws EvaluationException {
return ast.getValueInternal(new ExpressionState(getEvaluationContext(), configuration)).getTypeDescriptor().getType();
return typeDescriptor != null ? typeDescriptor.getType() : null;
}
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
return ast.getValueInternal(new ExpressionState(getEvaluationContext(), configuration)).getTypeDescriptor();
return getValueTypeDescriptor(getEvaluationContext());
}
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
......@@ -177,7 +176,6 @@ public class SpelExpression implements Expression {
Assert.notNull(context, "The EvaluationContext is required");
ast.setValue(new ExpressionState(context, toTypedValue(rootObject), configuration), value);
}
// impl only
......
......@@ -30,6 +30,7 @@ import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;
import org.springframework.util.ObjectUtils;
/**
* Utility methods used by the reflection resolver code to discover the appropriate
......@@ -63,7 +64,7 @@ public class ReflectionHelper {
TypeDescriptor expectedArg = expectedArgTypes.get(i);
if (!expectedArg.equals(suppliedArg)) {
// The user may supply null - and that will be ok unless a primitive is expected
if (suppliedArg == TypeDescriptor.NULL) {
if (suppliedArg == null) {
if (expectedArg.isPrimitive()) {
match = null;
}
......@@ -112,7 +113,7 @@ public class ReflectionHelper {
for (int i = 0,max=paramTypes.size(); i < max; i++) {
TypeDescriptor argType = argTypes.get(i);
TypeDescriptor paramType = paramTypes.get(i);
if (argType==TypeDescriptor.NULL) {
if (argType == null) {
if (paramType.isPrimitive()) {
return Integer.MAX_VALUE;
}
......@@ -120,7 +121,7 @@ public class ReflectionHelper {
if (!ClassUtils.isAssignable(paramType.getClass(), argType.getClass())) {
return Integer.MAX_VALUE;
}
if (argType != TypeDescriptor.NULL) {
if (argType != null) {
Class paramTypeClazz = paramType.getType();
if (paramTypeClazz.isPrimitive()) {
paramTypeClazz = Object.class;
......@@ -174,7 +175,7 @@ public class ReflectionHelper {
for (int i = 0; i < argCountUpToVarargs && match != null; i++) {
TypeDescriptor suppliedArg = suppliedArgTypes.get(i);
TypeDescriptor expectedArg = expectedArgTypes.get(i);
if (suppliedArg == TypeDescriptor.NULL) {
if (suppliedArg == null) {
if (expectedArg.isPrimitive()) {
match = null;
}
......@@ -213,13 +214,13 @@ public class ReflectionHelper {
else {
// Now... we have the final argument in the method we are checking as a match and we have 0 or more other
// arguments left to pass to it.
Class varargsParameterType = expectedArgTypes.get(expectedArgTypes.size() - 1).getElementType();
Class varargsParameterType = expectedArgTypes.get(expectedArgTypes.size() - 1).getElementType().getType();
// All remaining parameters must be of this type or convertable to this type
for (int i = expectedArgTypes.size() - 1; i < suppliedArgTypes.size(); i++) {
TypeDescriptor suppliedArg = suppliedArgTypes.get(i);
if (varargsParameterType != suppliedArg.getType()) {
if (suppliedArg == TypeDescriptor.NULL) {
if (!ObjectUtils.nullSafeEquals(varargsParameterType, suppliedArg)) {
if (suppliedArg == null) {
if (varargsParameterType.isPrimitive()) {
match = null;
}
......
......@@ -66,7 +66,8 @@ class ReflectiveMethodExecutor implements MethodExecutor {
arguments = ReflectionHelper.setupArgumentsForVarargsInvocation(this.method.getParameterTypes(), arguments);
}
ReflectionUtils.makeAccessible(this.method);
return new TypedValue(this.method.invoke(target, arguments), new TypeDescriptor(new MethodParameter(this.method, -1)));
Object value = this.method.invoke(target, arguments);
return new TypedValue(value, new TypeDescriptor(new MethodParameter(this.method, -1)).narrowType(value));
}
catch (Exception ex) {
throw new AccessException("Problem invoking method: " + this.method, ex);
......
......@@ -109,7 +109,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (target instanceof Class) {
throw new AccessException("Cannot access length on array class itself");
}
return new TypedValue(Array.getLength(target),TypeDescriptor.valueOf(Integer.TYPE));
return new TypedValue(Array.getLength(target));
}
CacheKey cacheKey = new CacheKey(type, name);
......@@ -508,7 +508,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (needsToBeMadeAccessible) {
ReflectionUtils.makeAccessible((Method) member);
}
return new TypedValue(((Method) member).invoke(target), typeDescriptor);
Object value = ((Method) member).invoke(target);
return new TypedValue(value, typeDescriptor.narrowType(value));
}
catch (Exception ex) {
throw new AccessException("Unable to access property '" + name + "' through getter", ex);
......@@ -519,7 +520,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (needsToBeMadeAccessible) {
ReflectionUtils.makeAccessible((Field)member);
}
return new TypedValue(((Field)member).get(target),typeDescriptor);
Object value = ((Field)member).get(target);
return new TypedValue(value, typeDescriptor.narrowType(value));
}
catch (Exception ex) {
throw new AccessException("Unable to access field: " + name, ex);
......
......@@ -16,15 +16,17 @@
package org.springframework.expression.spel;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static junit.framework.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
......@@ -72,13 +74,13 @@ public class ExpressionTestsUsingCoreConversionService extends ExpressionTestCas
TypeConvertorUsingConversionService tcs = new TypeConvertorUsingConversionService();
// ArrayList containing List<Integer> to List<String>
Class<?> clazz = typeDescriptorForListOfString.getElementType();
Class<?> clazz = typeDescriptorForListOfString.getElementType().getType();
assertEquals(String.class,clazz);
List l = (List) tcs.convertValue(listOfInteger, TypeDescriptor.forObject(listOfInteger), typeDescriptorForListOfString);
assertNotNull(l);
// ArrayList containing List<String> to List<Integer>
clazz = typeDescriptorForListOfInteger.getElementType();
clazz = typeDescriptorForListOfInteger.getElementType().getType();
assertEquals(Integer.class,clazz);
l = (List) tcs.convertValue(listOfString, TypeDescriptor.forObject(listOfString), typeDescriptorForListOfString);
......
package org.springframework.expression.spel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class IndexingTests {
@Test
@Ignore
public void indexIntoGenericPropertyContainingMap() {
Map<String, String> property = new HashMap<String, String>();
property.put("foo", "bar");
this.property = property;
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("property");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.HashMap<?, ?>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
assertEquals(property, expression.getValue(this, Map.class));
expression = parser.parseExpression("property['foo']");
assertEquals("bar", expression.getValue(this));
}
@FieldAnnotation
public Object property;
@Test
public void indexIntoGenericPropertyContainingMapObject() {
Map<String, Map<String, String>> property = new HashMap<String, Map<String, String>>();
Map<String, String> map = new HashMap<String, String>();
map.put("foo", "bar");
property.put("property", map);
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.addPropertyAccessor(new MapAccessor());
context.setRootObject(property);
Expression expression = parser.parseExpression("property");
assertEquals("java.util.HashMap<?, ?>", expression.getValueTypeDescriptor(context).toString());
assertEquals(map, expression.getValue(context));
assertEquals(map, expression.getValue(context, Map.class));
expression = parser.parseExpression("property['foo']");
assertEquals("bar", expression.getValue(context));
}
public static class MapAccessor implements PropertyAccessor {
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
return (((Map) target).containsKey(name));
}
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
return new TypedValue(((Map) target).get(name));
}
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
return true;
}
@SuppressWarnings("unchecked")
public void write(EvaluationContext context, Object target, String name, Object newValue)
throws AccessException {
((Map) target).put(name, newValue);
}
public Class<?>[] getSpecificTargetClasses() {
return new Class[] { Map.class };
}
}
@Test
public void setGenericPropertyContainingMap() {
Map<String, String> property = new HashMap<String, String>();
property.put("foo", "bar");
this.property = property;
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("property");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.HashMap<?, ?>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("property['foo']");
assertEquals("bar", expression.getValue(this));
expression.setValue(this, "baz");
assertEquals("baz", expression.getValue(this));
}
@Test
public void setPropertyContainingMap() {
Map<Integer, Integer> property = new HashMap<Integer, Integer>();
property.put(9, 3);
this.parameterizedMap = property;
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("parameterizedMap");
assertEquals("java.util.HashMap<java.lang.Integer, java.lang.Integer>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("parameterizedMap['9']");
assertEquals(3, expression.getValue(this));
expression.setValue(this, "37");
assertEquals(37, expression.getValue(this));
}
public Map<Integer, Integer> parameterizedMap;
@Test
public void setPropertyContainingMapAutoGrow() {
SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, false));
Expression expression = parser.parseExpression("parameterizedMap");
assertEquals("java.util.Map<java.lang.Integer, java.lang.Integer>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("parameterizedMap['9']");
assertEquals(null, expression.getValue(this));
expression.setValue(this, "37");
assertEquals(37, expression.getValue(this));
}
@Test
public void indexIntoGenericPropertyContainingList() {
List<String> property = new ArrayList<String>();
property.add("bar");
this.property = property;
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("property");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.ArrayList<?>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("property[0]");
assertEquals("bar", expression.getValue(this));
}
@Test
public void setGenericPropertyContainingList() {
List<Integer> property = new ArrayList<Integer>();
property.add(3);
this.property = property;
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("property");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.ArrayList<?>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("property[0]");
assertEquals(3, expression.getValue(this));
expression.setValue(this, "4");
assertEquals("4", expression.getValue(this));
}
@Test
public void setGenericPropertyContainingListAutogrow() {
List<Integer> property = new ArrayList<Integer>();
this.property = property;
SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
Expression expression = parser.parseExpression("property");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.ArrayList<?>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("property[0]");
try {
expression.setValue(this, "4");
} catch (EvaluationException e) {
assertTrue(e.getMessage().startsWith("EL1053E"));
}
}
@Test
public void indexIntoPropertyContainingList() {
List<Integer> property = new ArrayList<Integer>();
property.add(3);
this.parameterizedList = property;
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("parameterizedList");
assertEquals("java.util.ArrayList<java.lang.Integer>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("parameterizedList[0]");
assertEquals(3, expression.getValue(this));
}
public List<Integer> parameterizedList;
@Test
public void indexIntoPropertyContainingListOfList() {
List<List<Integer>> property = new ArrayList<List<Integer>>();
property.add(Arrays.asList(3));
this.parameterizedListOfList = property;
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("parameterizedListOfList[0]");
assertEquals("java.util.Arrays$ArrayList<java.lang.Integer>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property.get(0), expression.getValue(this));
expression = parser.parseExpression("parameterizedListOfList[0][0]");
assertEquals(3, expression.getValue(this));
}
public List<List<Integer>> parameterizedListOfList;
@Test
public void setPropertyContainingList() {
List<Integer> property = new ArrayList<Integer>();
property.add(3);
this.parameterizedList = property;
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("parameterizedList");
assertEquals("java.util.ArrayList<java.lang.Integer>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("parameterizedList[0]");
assertEquals(3, expression.getValue(this));
expression.setValue(this, "4");
assertEquals(4, expression.getValue(this));
}
@Test
public void indexIntoGenericPropertyContainingNullList() {
SpelParserConfiguration configuration = new SpelParserConfiguration(true, true);
SpelExpressionParser parser = new SpelExpressionParser(configuration);
Expression expression = parser.parseExpression("property");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.lang.Object", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("property[0]");
try {
assertEquals("bar", expression.getValue(this));
} catch (EvaluationException e) {
assertTrue(e.getMessage().startsWith("EL1027E"));
}
}
@Test
public void indexIntoGenericPropertyContainingGrowingList() {
List<String> property = new ArrayList<String>();
this.property = property;
SpelParserConfiguration configuration = new SpelParserConfiguration(true, true);
SpelExpressionParser parser = new SpelExpressionParser(configuration);
Expression expression = parser.parseExpression("property");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.ArrayList<?>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("property[0]");
try {
assertEquals("bar", expression.getValue(this));
} catch (EvaluationException e) {
assertTrue(e.getMessage().startsWith("EL1053E"));
}
}
@Test
public void indexIntoGenericPropertyContainingGrowingList2() {
List<String> property2 = new ArrayList<String>();
this.property2 = property2;
SpelParserConfiguration configuration = new SpelParserConfiguration(true, true);
SpelExpressionParser parser = new SpelExpressionParser(configuration);
Expression expression = parser.parseExpression("property2");
assertEquals("java.util.ArrayList<?>", expression.getValueTypeDescriptor(this).toString());
assertEquals(property2, expression.getValue(this));
expression = parser.parseExpression("property2[0]");
try {
assertEquals("bar", expression.getValue(this));
} catch (EvaluationException e) {
assertTrue(e.getMessage().startsWith("EL1053E"));
}
}
public List property2;
@Test
public void indexIntoGenericPropertyContainingArray() {
String[] property = new String[] { "bar" };
this.property = property;
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("property");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.lang.String[]", expression.getValueTypeDescriptor(this).toString());
assertEquals(property, expression.getValue(this));
expression = parser.parseExpression("property[0]");
assertEquals("bar", expression.getValue(this));
}
@Test
public void emptyList() {
listOfScalarNotGeneric = new ArrayList();
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("listOfScalarNotGeneric");
assertEquals("java.util.List<java.lang.Object>", expression.getValueTypeDescriptor(this).toString());
assertEquals("java.util.ArrayList<?>", expression.getValueTypeDescriptor(this).toString());
assertEquals("", expression.getValue(this, String.class));
}
@Test
@Ignore
public void resolveCollectionElementType() {
listNotGeneric = new ArrayList();
listNotGeneric.add(5);
listNotGeneric.add(6);
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("listNotGeneric");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.List<@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.lang.Integer>", expression.getValueTypeDescriptor(this).toString());
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.ArrayList<?>", expression.getValueTypeDescriptor(this).toString());
assertEquals("5,6", expression.getValue(this, String.class));
}
......@@ -44,7 +306,7 @@ public class IndexingTests {
public void resolveCollectionElementTypeNull() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("listNotGeneric");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.List<@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.lang.Object>", expression.getValueTypeDescriptor(this).toString());
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.List<?>", expression.getValueTypeDescriptor(this).toString());
}
@FieldAnnotation
......@@ -63,7 +325,7 @@ public class IndexingTests {
mapNotGeneric.put("bonusAmount", 7.17);
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("mapNotGeneric");
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.Map<java.lang.String, java.lang.Double>", expression.getValueTypeDescriptor(this).toString());
assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.HashMap<?, ?>", expression.getValueTypeDescriptor(this).toString());
}
@FieldAnnotation
......
......@@ -16,6 +16,9 @@
package org.springframework.expression.spel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
......@@ -23,9 +26,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
......@@ -112,7 +113,7 @@ public class SelectionAndProjectionTests {
Object value = expression.getValue(context);
assertTrue(value.getClass().isArray());
TypedValue typedValue = new TypedValue(value);
assertEquals(Integer.class, typedValue.getTypeDescriptor().getElementType());
assertEquals(Integer.class, typedValue.getTypeDescriptor().getElementType().getType());
Integer[] array = (Integer[]) value;
assertEquals(5, array.length);
assertEquals(new Integer(0), array[0]);
......@@ -147,7 +148,7 @@ public class SelectionAndProjectionTests {
Object value = expression.getValue(context);
assertTrue(value.getClass().isArray());
TypedValue typedValue = new TypedValue(value);
assertEquals(Integer.class, typedValue.getTypeDescriptor().getElementType());
assertEquals(Integer.class, typedValue.getTypeDescriptor().getElementType().getType());
Integer[] array = (Integer[]) value;
assertEquals(5, array.length);
assertEquals(new Integer(0), array[0]);
......@@ -249,7 +250,7 @@ public class SelectionAndProjectionTests {
Object value = expression.getValue(context);
assertTrue(value.getClass().isArray());
TypedValue typedValue = new TypedValue(value);
assertEquals(Number.class, typedValue.getTypeDescriptor().getElementType());
assertEquals(Number.class, typedValue.getTypeDescriptor().getElementType().getType());
Number[] array = (Number[]) value;
assertEquals(3, array.length);
assertEquals(new Integer(5), array[0]);
......
......@@ -26,7 +26,6 @@ import java.util.Map;
import junit.framework.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
......@@ -206,7 +205,6 @@ public class SpelDocumentationTests extends ExpressionTestCase {
@Test
@Ignore
public void testDictionaryAccess() throws Exception {
StandardEvaluationContext societyContext = new StandardEvaluationContext();
societyContext.setRootObject(new IEEE());
......
......@@ -698,7 +698,6 @@ public class SpringEL300Tests extends ExpressionTestCase {
}
@Test
@Ignore
@SuppressWarnings("unchecked")
public void testMapOfMap_SPR7244() throws Exception {
Map<String,Object> map = new LinkedHashMap();
......@@ -727,10 +726,11 @@ public class SpringEL300Tests extends ExpressionTestCase {
String el1 = "ls.![#this.equals('abc')]";
SpelExpression exp = parser.parseRaw(el1);
List value = (List)exp.getValue(ctx);
System.out.println(value);
// value is list containing [true,false]
Assert.assertEquals(Boolean.class,value.get(0).getClass());
TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx);
Assert.assertEquals(Boolean.class,evaluated.getElementType());
Assert.assertEquals(null, evaluated.getElementType());
}
@Test
......@@ -743,7 +743,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
// value is array containing [true,false]
Assert.assertEquals(Boolean.class,value[0].getClass());
TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx);
Assert.assertEquals(Boolean.class,evaluated.getElementType());
Assert.assertEquals(Boolean.class, evaluated.getElementType().getType());
}
@Test
......@@ -756,7 +756,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
// value is list containing [true,false]
Assert.assertEquals(Boolean.class,value.get(0).getClass());
TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx);
Assert.assertEquals(Boolean.class,evaluated.getElementType());
Assert.assertEquals(null, evaluated.getElementType());
}
static class C {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册