diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java index 76d8d519458..4eff2715713 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java @@ -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); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java index 1faf39f1947..1bdf992bffe 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java @@ -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 diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java index ebc6d12c147..a2e6ebb4cf6 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java @@ -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); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java index afa7a913d25..aa40d92d1d6 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java @@ -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 diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/CommonElement.java b/org.springframework.core/src/main/java/org/springframework/core/convert/CommonElement.java deleted file mode 100644 index 51ca44a2f24..00000000000 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/CommonElement.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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> interfaces = new LinkedHashSet>(); - LinkedList> classQueue = new LinkedList>(); - 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> interfaces) { - interfaces.add(interfaceType); - for (Class inheritedInterface : interfaceType.getInterfaces()) { - addInterfaceHierarchy(inheritedInterface, interfaces); - } - } - -} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionService.java index 2995f74f621..97bce8933da 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionService.java @@ -41,7 +41,7 @@ public interface ConversionService { * @throws ConversionException if an exception occurred */ T convert(Object source, Class 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. diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java index 3f7a7d26e92..40490d252e2 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java @@ -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); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java index 465348c0663..93f12d181b5 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java @@ -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); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 997329c8b1c..6749804565e 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -38,9 +38,6 @@ public class TypeDescriptor { static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; - /** Constant defining a TypeDescriptor for a null value */ - public static final TypeDescriptor NULL = new TypeDescriptor(); - private static final Map, TypeDescriptor> typeDescriptorCache = new HashMap, 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<String> 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 null 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; + boolean typesAssignable = targetType.getObjectType().isAssignableFrom(getObjectType()); + if (!typesAssignable) { + return false; } - if (isCollection() && targetType.isCollection() || isArray() && targetType.isArray()) { - return targetType.getType().isAssignableFrom(getType()) && - getElementTypeDescriptor().isAssignableTo(targetType.getElementTypeDescriptor()); + if (isArray() && targetType.isArray()) { + return getElementType().isAssignableTo(targetType.getElementType()); } - else if (isMap() && targetType.isMap()) { - return targetType.getType().isAssignableFrom(getType()) && - getMapKeyTypeDescriptor().isAssignableTo(targetType.getMapKeyTypeDescriptor()) && - getMapValueTypeDescriptor().isAssignableTo(targetType.getMapValueTypeDescriptor()); - } - 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(); + return 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 null if not a collection or array. - * @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 null 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 Class getElementType() { - return getElementTypeDescriptor().getType(); - } - - /** - * 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 - */ - 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 - * 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 null 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 null 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 null 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 null 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 null 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. - * 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 diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java index 70eabb2a6e4..51799159593 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java @@ -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); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java index d1ae65fbc87..70edcbb81ca 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java @@ -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 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); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java index 404218ef542..842afd8524f 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java @@ -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); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java index 550039dbc61..f2960d93b07 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java @@ -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); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java index 8a29db460ca..34c509e5747 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java @@ -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; diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java index 1a54472d37a..731c94f3aec 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java @@ -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 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; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java index c97b4c9f685..58a978d2c8d 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java @@ -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 diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java index 39fc0c4debf..f7c65b4b5e9 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java @@ -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(); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index d17332a8db6..5752247ccb3 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -138,18 +138,16 @@ public class GenericConversionService implements ConfigurableConversionService { @SuppressWarnings("unchecked") public T convert(Object source, Class 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()) { diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java index 44c8a0892ab..ecd47687749 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java @@ -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 sourceMap = (Map) source; Map 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 entry : sourceMap.entrySet()) { - targetMap.put(entry.getKey(), entry.getValue()); - } - } else { - for (Map.Entry 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 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 diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java index 83428f504cb..bef3a32ddce 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java @@ -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; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java index 0f6342cd99b..9cc26d83ec3 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java @@ -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 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; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java index f96ffb48fd3..865ac12b05a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java @@ -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); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java index 98adc753837..c2323901588 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java @@ -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; diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java index 686a62cddb7..fb1cf034e21 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java @@ -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 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 diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java index bcb7460e511..46b10d7ae1d 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java @@ -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> nestedMapField = new HashMap>(); - @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", 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> 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>> 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 { @@ -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", 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>", 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>", typeDescriptor.toString()); + assertEquals(List.class, typeDescriptor.getElementType().getType()); + assertNull(typeDescriptor.getElementType().getElementType()); + assertEquals("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>", 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> 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()); + assertNull(typeDescriptor.getElementType()); } @Test - public void forObjectCollection() { - List list = new ArrayList(); - list.add("1"); - TypeDescriptor desc = TypeDescriptor.forObject(list); - assertEquals(String.class, desc.getElementType()); + public void forObject() { + TypeDescriptor desc = TypeDescriptor.forObject("3"); + assertEquals(String.class, desc.getType()); } @Test - public void forObjectCollectionEmpty() { - List list = new ArrayList(); - TypeDescriptor desc = TypeDescriptor.forObject(list); - assertNull(desc.getElementType()); - } - - @Test - public void forObjectCollectionSuperClassCommonType() throws SecurityException, NoSuchFieldException { - List list = new ArrayList(); - list.add(1); - list.add(2L); - TypeDescriptor desc = TypeDescriptor.forObject(list); - assertEquals(Number.class, desc.getElementType()); - } - - public List longs; - - @Test - public void forObjectCollectionNoObviousCommonType() { - List collection = new ArrayList(); - List list = new ArrayList(); - list.add("1"); - collection.add(list); - Map map = new HashMap(); - collection.add(map); - map.put("1", "2"); - TypeDescriptor desc = TypeDescriptor.forObject(collection); - assertEquals(Cloneable.class, desc.getElementType()); - } - - @Test - public void forObjectCollectionNoCommonType() { - List collection = new ArrayList(); - collection.add(new Object()); - collection.add("1"); - TypeDescriptor desc = TypeDescriptor.forObject(collection); - assertEquals(Object.class, desc.getElementType()); - } - - @Test - public void forObjectCollectionNested() { - List collection = new ArrayList(); - 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 map = new HashMap(); - map.put("1", "2"); - TypeDescriptor desc = TypeDescriptor.forObject(map); - assertEquals(String.class, desc.getMapKeyType()); - assertEquals(String.class, desc.getMapValueType()); - } - - @Test - public void forObjectMapEmpty() { - Map map = new HashMap(); - TypeDescriptor desc = TypeDescriptor.forObject(map); - assertNull(desc.getMapKeyType()); - assertNull(desc.getMapValueType()); - } - - @Test - public void forObjectMapCommonSuperClass() { - Map map = new HashMap(); - 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 map = new HashMap(); - map.put("1", "2"); - map.put(2, 2); - TypeDescriptor desc = TypeDescriptor.forObject(map); - assertEquals(Comparable.class, desc.getMapKeyType()); - assertEquals(Comparable.class, desc.getMapValueType()); - } - - @Test - public void forObjectMapNested() { - Map> map = new HashMap>(); - 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 diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java index bc4f2ed07d6..4e51cfa932c 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java @@ -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 resources = new ArrayList(); resources.add(null); @@ -180,7 +186,7 @@ public class CollectionToCollectionConverterTests { public List allNullsNotConvertible; - @Test(expected=ConverterNotFoundException.class) + @Test(expected=ConversionFailedException.class) public void nothingInCommon() throws Exception { List resources = new ArrayList(); resources.add(new ClassPathResource("test")); diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index 9bc9ada7923..be679632121 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java @@ -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 diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java index 7e8f7086380..1459693411d 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java @@ -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 result = (Map) 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> collectionMapTarget; - public Map> sourceCollectionMapTarget; - @Test public void collectionMapSourceTarget() throws Exception { Map> map = new HashMap>(); @@ -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> sourceCollectionMapTarget; + @Test public void collectionMapNotGenericTarget() throws Exception { Map> map = new HashMap>(); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/TypedValue.java b/org.springframework.expression/src/main/java/org/springframework/expression/TypedValue.java index 508f34c0bcd..a6b8d209583 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/TypedValue.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/TypedValue.java @@ -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; - } - } - } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FormatHelper.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FormatHelper.java index 15223788f8e..377cb5e415c 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FormatHelper.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FormatHelper.java @@ -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(); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java index 2b326e895a3..8d5283040b0 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java @@ -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, diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index 05eec095b56..5e31ad6c4d9 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -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); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java index 5b29402ae66..3cbac19ca4c 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java @@ -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); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java index 272103301da..753f8536ad6 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java @@ -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 diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index 73b0ed5ab85..f83b4c0415f 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -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; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java index 89b51f0ca03..25d1aad2b76 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java @@ -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); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index 1f9e808398d..7718f8694db 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -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); diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java index 5307978702f..0d0b50cb808 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java @@ -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 to List - 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 to List - clazz = typeDescriptorForListOfInteger.getElementType(); + clazz = typeDescriptorForListOfInteger.getElementType().getType(); assertEquals(Integer.class,clazz); l = (List) tcs.convertValue(listOfString, TypeDescriptor.forObject(listOfString), typeDescriptorForListOfString); diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/IndexingTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/IndexingTests.java index da6a982d01c..617d69fa66a 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/IndexingTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/IndexingTests.java @@ -1,42 +1,304 @@ 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 property = new HashMap(); + 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> property = new HashMap>(); + Map map = new HashMap(); + 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 property = new HashMap(); + 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 property = new HashMap(); + property.put(9, 3); + this.parameterizedMap = property; + SpelExpressionParser parser = new SpelExpressionParser(); + Expression expression = parser.parseExpression("parameterizedMap"); + assertEquals("java.util.HashMap", 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 parameterizedMap; + + @Test + public void setPropertyContainingMapAutoGrow() { + SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, false)); + Expression expression = parser.parseExpression("parameterizedMap"); + assertEquals("java.util.Map", 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 property = new ArrayList(); + 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 property = new ArrayList(); + 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 property = new ArrayList(); + 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 property = new ArrayList(); + property.add(3); + this.parameterizedList = property; + SpelExpressionParser parser = new SpelExpressionParser(); + Expression expression = parser.parseExpression("parameterizedList"); + assertEquals("java.util.ArrayList", expression.getValueTypeDescriptor(this).toString()); + assertEquals(property, expression.getValue(this)); + expression = parser.parseExpression("parameterizedList[0]"); + assertEquals(3, expression.getValue(this)); + } + + public List parameterizedList; + + @Test + public void indexIntoPropertyContainingListOfList() { + List> property = new ArrayList>(); + property.add(Arrays.asList(3)); + this.parameterizedListOfList = property; + SpelExpressionParser parser = new SpelExpressionParser(); + Expression expression = parser.parseExpression("parameterizedListOfList[0]"); + assertEquals("java.util.Arrays$ArrayList", expression.getValueTypeDescriptor(this).toString()); + assertEquals(property.get(0), expression.getValue(this)); + expression = parser.parseExpression("parameterizedListOfList[0][0]"); + assertEquals(3, expression.getValue(this)); + } + + public List> parameterizedListOfList; + + @Test + public void setPropertyContainingList() { + List property = new ArrayList(); + property.add(3); + this.parameterizedList = property; + SpelExpressionParser parser = new SpelExpressionParser(); + Expression expression = parser.parseExpression("parameterizedList"); + assertEquals("java.util.ArrayList", 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 property = new ArrayList(); + 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 property2 = new ArrayList(); + 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", 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", expression.getValueTypeDescriptor(this).toString()); + assertEquals("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.HashMap", expression.getValueTypeDescriptor(this).toString()); } @FieldAnnotation diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java index 99931e577b8..9f0a64ec1c8 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java @@ -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]); diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java index 54c9b4171bb..1dad2431208 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java @@ -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()); diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java index c4cbf75467f..29315eea0d4 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java @@ -698,7 +698,6 @@ public class SpringEL300Tests extends ExpressionTestCase { } @Test - @Ignore @SuppressWarnings("unchecked") public void testMapOfMap_SPR7244() throws Exception { Map 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 {