From 1e2a8083a128d8b3c9c24198e7fa140bb57fe040 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Wed, 5 Jan 2011 05:49:33 +0000 Subject: [PATCH] TypeDescriptor cleanup and general polishing; fixed a number of bugs related to TypeDescriptor usage in client code across beans and spel packages git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@3846 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../beans/BeanWrapperImpl.java | 20 +- .../beans/TypeConverterDelegate.java | 2 +- org.springframework.context/.classpath | 81 +-- .../core/convert/TypeDescriptor.java | 614 ++++++++---------- .../support/ArrayToCollectionConverter.java | 3 +- .../support/CollectionToArrayConverter.java | 2 +- .../CollectionToCollectionConverter.java | 5 +- .../support/CollectionToObjectConverter.java | 2 +- .../support/CollectionToStringConverter.java | 3 +- .../convert/support/MapToMapConverter.java | 8 +- .../support/ObjectToCollectionConverter.java | 3 +- .../support/PropertyTypeDescriptor.java | 94 ++- .../support/StringToCollectionConverter.java | 4 +- .../core/convert/TypeDescriptorTests.java | 25 +- .../support/DefaultConversionTests.java | 30 +- .../expression/TypedValue.java | 2 +- .../expression/common/ExpressionUtils.java | 2 +- .../expression/spel/ExpressionState.java | 4 +- .../spel/ast/ConstructorReference.java | 5 +- .../expression/spel/ast/Indexer.java | 22 +- .../expression/spel/ast/InlineList.java | 6 +- .../expression/spel/ast/OpDivide.java | 3 +- .../expression/spel/ast/Projection.java | 4 +- .../expression/spel/ast/Selection.java | 4 +- .../spel/support/BooleanTypedValue.java | 3 +- .../spel/support/ReflectionHelper.java | 4 +- .../ReflectiveConstructorExecutor.java | 3 +- .../support/ReflectivePropertyAccessor.java | 6 +- .../spel/ExpressionLanguageScenarioTests.java | 6 +- .../expression/spel/ExpressionStateTests.java | 2 +- .../expression/spel/PropertyAccessTests.java | 2 +- .../spel/ScenariosForSpringSecurity.java | 6 +- .../spel/SpelDocumentationTests.java | 8 + 33 files changed, 449 insertions(+), 539 deletions(-) diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index b42d41205db..35b41881a82 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -369,10 +369,10 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra if (pd != null) { Class type = getPropertyType(propertyName); if (pd.getReadMethod() != null) { - return new PropertyTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), type); + return new PropertyTypeDescriptor(type, new MethodParameter(pd.getReadMethod(), -1), pd); } else if (pd.getWriteMethod() != null) { - return new PropertyTypeDescriptor(pd, BeanUtils.getWriteMethodParameter(pd), type); + return new PropertyTypeDescriptor(type, BeanUtils.getWriteMethodParameter(pd), pd); } } } @@ -468,12 +468,6 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } } - private Object convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class requiredType) - throws TypeMismatchException { - - return convertIfNecessary(propertyName, oldValue, newValue, requiredType, TypeDescriptor.valueOf(requiredType)); - } - /** * Convert the given value for the specified property to the latter's type. *

This method is only intended for optimizations in a BeanFactory. @@ -497,7 +491,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra throws TypeMismatchException { return convertIfNecessary(propertyName, oldValue, newValue, pd.getPropertyType(), - new PropertyTypeDescriptor(pd, BeanUtils.getWriteMethodParameter(pd))); + new PropertyTypeDescriptor(BeanUtils.getWriteMethodParameter(pd), pd)); } @@ -794,7 +788,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra // IMPORTANT: Do not pass full property name in here - property editors // must not kick in for map keys but rather only for map values. Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, - new PropertyTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), mapKeyType)); + new PropertyTypeDescriptor(mapKeyType, new MethodParameter(pd.getReadMethod(), -1), pd)); value = map.get(convertedMapKey); } else { @@ -942,7 +936,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra oldValue = Array.get(propValue, arrayIndex); } Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, - new PropertyTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), requiredType)); + new PropertyTypeDescriptor(requiredType, new MethodParameter(pd.getReadMethod(), -1), pd)); Array.set(propValue, arrayIndex, convertedValue); } catch (IndexOutOfBoundsException ex) { @@ -961,7 +955,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra oldValue = list.get(index); } Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, - new PropertyTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), requiredType)); + new PropertyTypeDescriptor(requiredType, new MethodParameter(pd.getReadMethod(), -1), pd)); if (index < list.size()) { list.set(index, convertedValue); } @@ -990,7 +984,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra // IMPORTANT: Do not pass full property name in here - property editors // must not kick in for map keys but rather only for map values. Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, - new PropertyTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), mapKeyType)); + new PropertyTypeDescriptor(mapKeyType, new MethodParameter(pd.getReadMethod(), -1), pd)); Object oldValue = null; if (isExtractOldValueForEditor()) { oldValue = map.get(convertedMapKey); 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 dedda9eb1f1..a676439cbe9 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 @@ -136,7 +136,7 @@ class TypeConverterDelegate { ConversionService conversionService = this.propertyEditorRegistry.getConversionService(); if (editor == null && conversionService != null && convertedValue != null) { TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(convertedValue); - TypeDescriptor targetTypeDesc = typeDescriptor.forElementType(requiredType); + TypeDescriptor targetTypeDesc = typeDescriptor; if (conversionService.canConvert(sourceTypeDesc, targetTypeDesc)) { return (T) conversionService.convert(convertedValue, sourceTypeDesc, targetTypeDesc); } diff --git a/org.springframework.context/.classpath b/org.springframework.context/.classpath index 9eb9e41365f..f9fabe5616c 100644 --- a/org.springframework.context/.classpath +++ b/org.springframework.context/.classpath @@ -1,39 +1,42 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 59335f2b7a1..d91ed06f000 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 @@ -42,9 +42,6 @@ public class TypeDescriptor { /** Constant defining a TypeDescriptor for a null value */ public static final TypeDescriptor NULL = new TypeDescriptor(); - /** Constant defining a TypeDescriptor for 'unknown type' */ - private static final TypeDescriptor UNKNOWN = new TypeDescriptor(Object.class); - private static final Map, TypeDescriptor> typeDescriptorCache = new HashMap, TypeDescriptor>(); private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; @@ -78,8 +75,6 @@ public class TypeDescriptor { private int fieldNestingLevel = 1; - private Object value; - private TypeDescriptor elementType; private TypeDescriptor mapKeyType; @@ -97,6 +92,7 @@ public class TypeDescriptor { */ public TypeDescriptor(MethodParameter methodParameter) { Assert.notNull(methodParameter, "MethodParameter must not be null"); + this.type = methodParameter.getParameterType(); this.methodParameter = methodParameter; } @@ -107,112 +103,49 @@ public class TypeDescriptor { */ public TypeDescriptor(Field field) { Assert.notNull(field, "Field must not be null"); + this.type = field.getType(); this.field = field; } /** - * Create a new type descriptor from a method or constructor parameter. - *

Use this constructor when a target conversion point originates from a method parameter, - * such as a setter method argument. - * @param methodParameter the MethodParameter to wrap - * @param type the specific type to expose (may be an array/collection element) + * Create a new type descriptor for the given class. + * @param type the class + * @return the type descriptor */ - public TypeDescriptor(MethodParameter methodParameter, Class type) { - Assert.notNull(methodParameter, "MethodParameter must not be null"); - this.methodParameter = methodParameter; - this.type = type; + public static TypeDescriptor valueOf(Class type) { + if (type == null) { + return NULL; + } + TypeDescriptor desc = typeDescriptorCache.get(type); + return (desc != null ? desc : new TypeDescriptor(type)); } - + /** - * Create a new type descriptor for a field. - * Use this constructor when a target conversion point originates from a field. - * @param field the field to wrap - * @param type the specific type to expose (may be an array/collection element) + * Create a new type descriptor for the class of the given object. + * @param object the object + * @return the type descriptor */ - public TypeDescriptor(Field field, Class type) { - Assert.notNull(field, "Field must not be null"); - this.field = field; - this.type = type; + public static TypeDescriptor forObject(Object object) { + if (object == null) { + return NULL; + } + if (object instanceof Collection) { + return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType((Collection) object)); + } + else if (object instanceof Map) { + return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType(((Map) object).keySet()), CollectionUtils.findCommonElementType(((Map) object).values())); + } + else { + return valueOf(object.getClass()); + } } - - /** - * Create a new type descriptor for a field. - * Use this constructor when a target conversion point originates from a field. - * @param field the field to wrap - * @param type the specific type to expose (may be an array/collection element) - */ - private TypeDescriptor(Field field, int nestingLevel, Class type) { - Assert.notNull(field, "Field must not be null"); - this.field = field; - this.fieldNestingLevel = nestingLevel; - this.type = type; - } - - /** - * Internal constructor for a NULL descriptor. - */ - private TypeDescriptor() { - } - - /** - * Create a new descriptor for the type of the given value. - *

Use this constructor when a conversion point comes from a source such as a Map or - * Collection, where no additional context is available but elements can be introspected. - * @param value the value to determine the actual type from - */ - private TypeDescriptor(Object value) { - Assert.notNull(value, "Value must not be null"); - this.value = value; - this.type = value.getClass(); - } - - /** - * Create a new descriptor for the given type. - *

Use this constructor when a conversion point comes from a plain source type, - * where no additional context is available. - * @param type the actual type to wrap - */ - private TypeDescriptor(Class type) { - Assert.notNull(type, "Type must not be null"); - this.type = type; - } - - - /** - * Return the wrapped MethodParameter, if any. - *

Note: Either MethodParameter or Field is available. - * @return the MethodParameter, or null if none - */ - public MethodParameter getMethodParameter() { - return this.methodParameter; - } - - /** - * Return the wrapped Field, if any. - *

Note: Either MethodParameter or Field is available. - * @return the Field, or null if none - */ - public Field getField() { - return this.field; - } - + /** * Determine the declared (non-generic) type of the wrapped parameter/field. * @return the declared type, or null if this is {@link TypeDescriptor#NULL} */ public Class getType() { - if (this.type != null) { - return this.type; - } - else if (this.field != null) { - return this.field.getType(); - } - else if (this.methodParameter != null) { - return this.methodParameter.getParameterType(); - } - else { - return null; - } + return type; } /** @@ -220,140 +153,21 @@ public class TypeDescriptor { * Returns the Object wrapper type if the underlying type is a primitive. */ public Class getObjectType() { - Class type = getType(); - return (type != null ? ClassUtils.resolvePrimitiveIfNecessary(type) : type); + return ClassUtils.resolvePrimitiveIfNecessary(getType()); } /** * Returns the name of this type: the fully qualified class name. */ public String getName() { - Class type = getType(); - return (type != null ? ClassUtils.getQualifiedName(type) : null); + return ClassUtils.getQualifiedName(getType()); } /** * Is this type a primitive type? */ public boolean isPrimitive() { - Class type = getType(); - return (type != null && type.isPrimitive()); - } - - /** - * Is this type an array type? - */ - public boolean isArray() { - Class type = getType(); - return (type != null && type.isArray()); - } - - /** - * Is this type a {@link Collection} type? - */ - public boolean isCollection() { - return Collection.class.isAssignableFrom(getType()); - } - - /** - * If this type is an array type or {@link Collection} type, returns the underlying element type. - * Returns null if the type is neither an array or collection. - */ - public Class getElementType() { - return getElementTypeDescriptor().getType(); - } - - /** - * Return the element type as a type descriptor. - */ - public synchronized TypeDescriptor getElementTypeDescriptor() { - if (this.elementType == null) { - this.elementType = forElementType(resolveElementType()); - } - return this.elementType; - } - - /** - * Return the element type as a type descriptor. If the element type is null - * (cannot be determined), the type descriptor is derived from the element argument. - * @param element the element - * @return the element type descriptor - */ - public TypeDescriptor getElementTypeDescriptor(Object element) { - TypeDescriptor elementType = getElementTypeDescriptor(); - return (elementType != TypeDescriptor.UNKNOWN ? elementType : forObject(element)); - } - - /** - * Is this type a {@link Map} type? - */ - public boolean isMap() { - return Map.class.isAssignableFrom(getType()); - } - - /** - * Is this descriptor for a map where the key type and value type are known? - */ - public boolean isMapEntryTypeKnown() { - return (isMap() && getMapKeyType() != null && getMapValueType() != null); - } - - /** - * Determine the generic key type of the wrapped Map parameter/field, if any. - * @return the generic type, or null if none - */ - public Class getMapKeyType() { - return getMapKeyTypeDescriptor().getType(); - } - - /** - * Returns map key type as a type descriptor. - */ - public synchronized TypeDescriptor getMapKeyTypeDescriptor() { - if (this.mapKeyType == null) { - this.mapKeyType = forElementType(resolveMapKeyType()); - } - return this.mapKeyType; - } - - /** - * Return the map key type as a type descriptor. If the key type is null - * (cannot be determined), the type descriptor is derived from the key argument. - * @param key the key - * @return the map key type descriptor - */ - public TypeDescriptor getMapKeyTypeDescriptor(Object key) { - TypeDescriptor keyType = getMapKeyTypeDescriptor(); - return (keyType != TypeDescriptor.UNKNOWN ? keyType : TypeDescriptor.forObject(key)); - } - - /** - * Determine the generic value type of the wrapped Map parameter/field, if any. - * @return the generic type, or null if none - */ - public Class getMapValueType() { - return getMapValueTypeDescriptor().getType(); - } - - /** - * Returns map value type as a type descriptor. - */ - public synchronized TypeDescriptor getMapValueTypeDescriptor() { - if (this.mapValueType == null) { - this.mapValueType = forElementType(resolveMapValueType()); - } - return this.mapValueType; - } - - /** - * Return the map value type as a type descriptor. If the value type is null - * (cannot be determined), the type descriptor is derived from the value argument. - * @param value the value - * @return the map value type descriptor - */ - public TypeDescriptor getMapValueTypeDescriptor(Object value) { - TypeDescriptor valueType = getMapValueTypeDescriptor(); - return (valueType != TypeDescriptor.UNKNOWN ? valueType : TypeDescriptor.forObject(value)); + return getType().isPrimitive(); } /** @@ -402,28 +216,130 @@ public class TypeDescriptor { } /** - * Create a copy of this type descriptor, preserving the context information - * but exposing the specified element type (e.g. an array/collection/map element). - * @param elementType the desired type to expose - * @return the type descriptor + * A textual representation of the type descriptor (eg. Map) for use in messages. */ - public TypeDescriptor forElementType(Class elementType) { - if (elementType == null) { - return TypeDescriptor.UNKNOWN; - } - else if (this.methodParameter != null) { - MethodParameter nested = new MethodParameter(this.methodParameter); - nested.increaseNestingLevel(); - return new TypeDescriptor(nested, elementType); - } - else if (this.field != null) { - return new TypeDescriptor(this.field, this.fieldNestingLevel + 1, elementType); - } - else { - return TypeDescriptor.valueOf(elementType); - } + public String asString() { + return toString(); + } + + // indexable type descriptor operations + + /** + * Is this type an array type? + */ + public boolean isArray() { + return getType().isArray(); } + /** + * Is this type a {@link Collection} type? + */ + public boolean isCollection() { + return Collection.class.isAssignableFrom(getType()); + } + + /** + * If this type is an array type or {@link Collection} type, returns the underlying element type. + * Returns null if the type is neither an array or collection. + */ + public Class getElementType() { + return getElementTypeDescriptor().getType(); + } + + /** + * Return the element type as a type descriptor. + */ + public synchronized TypeDescriptor getElementTypeDescriptor() { + if (!isCollection() && !isArray()) { + throw new IllegalStateException("Not a collection or array type"); + } + if (this.elementType == null) { + this.elementType = resolveElementTypeDescriptor(); + } + return this.elementType; + } + + // map type descriptor operations + + /** + * Is this type a {@link Map} type? + */ + public boolean isMap() { + return Map.class.isAssignableFrom(getType()); + } + + /** + * Determine the generic key type of the wrapped Map parameter/field, if any. + * @return the generic type, or null if none + */ + public Class getMapKeyType() { + return getMapKeyTypeDescriptor().getType(); + } + + /** + * Returns map key type as a type descriptor. + */ + public synchronized TypeDescriptor getMapKeyTypeDescriptor() { + if (!isMap()) { + throw new IllegalStateException("Not a Map type"); + } + if (this.mapKeyType == null) { + this.mapKeyType = resolveMapKeyTypeDescriptor(); + } + return this.mapKeyType; + } + + /** + * Determine the generic value type of the wrapped Map parameter/field, if any. + * @return the generic type, or null if none + */ + public Class getMapValueType() { + return getMapValueTypeDescriptor().getType(); + } + + /** + * Returns map value type as a type descriptor. + */ + public synchronized TypeDescriptor getMapValueTypeDescriptor() { + if (this.mapValueType == null) { + this.mapValueType = resolveMapValueTypeDescriptor(); + } + return this.mapValueType; + } + + // special case public operations + + public TypeDescriptor(Class componentType, MethodParameter methodParameter) { + if (componentType == null) { + componentType = Object.class; + } + this.type = componentType; + this.methodParameter = methodParameter; + } + + public MethodParameter getMethodParameter() { + return methodParameter; + } + + public TypeDescriptor applyType(Object object) { + if (object == null) { + return this; + } + // TODO preserve binding context with returned copy + // TODO fall back to generic info if collection is empty + if (object instanceof Collection) { + return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType((Collection) object)); + } + else if (object instanceof Map) { + return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType(((Map) object).keySet()), CollectionUtils.findCommonElementType(((Map) object).values())); + } + else { + return valueOf(object.getClass()); + } + } + + // extending Object + public boolean equals(Object obj) { if (this == obj) { return true; @@ -432,8 +348,7 @@ public class TypeDescriptor { return false; } TypeDescriptor other = (TypeDescriptor) obj; - boolean annotatedTypeEquals = - getType().equals(other.getType()) && ObjectUtils.nullSafeEquals(getAnnotations(), other.getAnnotations()); + boolean annotatedTypeEquals = getType().equals(other.getType()) && ObjectUtils.nullSafeEquals(getAnnotations(), other.getAnnotations()); if (isCollection()) { return annotatedTypeEquals && ObjectUtils.nullSafeEquals(getElementType(), other.getElementType()); } @@ -450,13 +365,6 @@ public class TypeDescriptor { return (this == TypeDescriptor.NULL ? 0 : getType().hashCode()); } - /** - * A textual representation of the type descriptor (eg. Map) for use in messages. - */ - public String asString() { - return toString(); - } - public String toString() { if (this == TypeDescriptor.NULL) { return "null"; @@ -479,82 +387,9 @@ public class TypeDescriptor { } } - - // internal helpers - - private Class resolveElementType() { - if (isArray()) { - return getType().getComponentType(); - } - else if (isCollection()) { - return resolveCollectionElementType(); - } - else { - return null; - } - } - - @SuppressWarnings("unchecked") - private Class resolveCollectionElementType() { - if (this.field != null) { - return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.fieldNestingLevel); - } - else if (this.methodParameter != null) { - return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter); - } - else if (this.value instanceof Collection) { - Class elementType = CollectionUtils.findCommonElementType((Collection) this.value); - if (elementType != null) { - return elementType; - } - } - else if (this.type != null) { - return GenericCollectionTypeResolver.getCollectionType((Class) this.type); - } - return null; - } - - @SuppressWarnings("unchecked") - private Class resolveMapKeyType() { - if (this.field != null) { - return GenericCollectionTypeResolver.getMapKeyFieldType(this.field); - } - else if (this.methodParameter != null) { - return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter); - } - else if (this.value instanceof Map) { - Class keyType = CollectionUtils.findCommonElementType(((Map) this.value).keySet()); - if (keyType != null) { - return keyType; - } - } - else if (this.type != null && isMap()) { - return GenericCollectionTypeResolver.getMapKeyType((Class) this.type); - } - return null; - } - - @SuppressWarnings("unchecked") - private Class resolveMapValueType() { - if (this.field != null) { - return GenericCollectionTypeResolver.getMapValueFieldType(this.field); - } - else if (this.methodParameter != null) { - return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter); - } - else if (this.value instanceof Map) { - Class valueType = CollectionUtils.findCommonElementType(((Map) this.value).values()); - if (valueType != null) { - return valueType; - } - } - else if (this.type != null && isMap()) { - return GenericCollectionTypeResolver.getMapValueType((Class) this.type); - } - return null; - } - - private Annotation[] resolveAnnotations() { + // subclassing hooks + + protected Annotation[] resolveAnnotations() { if (this.field != null) { return this.field.getAnnotations(); } @@ -571,37 +406,118 @@ public class TypeDescriptor { } } + protected TypeDescriptor newComponentTypeDescriptor(Class componentType, MethodParameter nested) { + return new TypeDescriptor(componentType, nested); + } + + // internal helpers - // static factory methods - - /** - * Create a new type descriptor for the class of the given object. - * @param object the object - * @return the type descriptor - */ - public static TypeDescriptor forObject(Object object) { - if (object == null) { - return NULL; - } - else if (object instanceof Collection || object instanceof Map) { - return new TypeDescriptor(object); + private TypeDescriptor resolveElementTypeDescriptor() { + if (isCollection()) { + return createComponentTypeDescriptor(resolveCollectionElementType()); } else { - return valueOf(object.getClass()); + // TODO: GenericCollectionTypeResolver is not capable of applying nesting levels to array fields; + // this means generic info of nested lists or maps stored inside array method parameters or fields is not obtainable + return createComponentTypeDescriptor(getType().getComponentType()); } } - /** - * Create a new type descriptor for the given class. - * @param type the class - * @return the type descriptor - */ - public static TypeDescriptor valueOf(Class type) { - if (type == null) { - return TypeDescriptor.NULL; - } - TypeDescriptor desc = typeDescriptorCache.get(type); - return (desc != null ? desc : new TypeDescriptor(type)); + private TypeDescriptor resolveMapKeyTypeDescriptor() { + return createComponentTypeDescriptor(resolveMapKeyType()); } -} + private TypeDescriptor resolveMapValueTypeDescriptor() { + return createComponentTypeDescriptor(resolveMapValueType()); + } + + private TypeDescriptor createComponentTypeDescriptor(Class componentType) { + if (componentType == null) { + componentType = Object.class; + } + if (this.methodParameter != null) { + MethodParameter nested = new MethodParameter(this.methodParameter); + nested.increaseNestingLevel(); + return newComponentTypeDescriptor(componentType, nested); + } + else if (this.field != null) { + return new TypeDescriptor(componentType, this.field, this.fieldNestingLevel + 1); + } + else { + return TypeDescriptor.valueOf(componentType); + } + } + + private Class resolveCollectionElementType() { + if (this.methodParameter != null) { + return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter); + } + else if (this.field != null) { + return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.fieldNestingLevel); + } + else { + return GenericCollectionTypeResolver.getCollectionType((Class) this.type); + } + } + + private Class resolveMapKeyType() { + if (this.methodParameter != null) { + return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter); + } + else if (this.field != null) { + return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.fieldNestingLevel); + } + else { + return GenericCollectionTypeResolver.getMapKeyType((Class) this.type); + } + } + + private Class resolveMapValueType() { + if (this.methodParameter != null) { + return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter); + } + else if (this.field != null) { + return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.fieldNestingLevel); + } + else { + return GenericCollectionTypeResolver.getMapValueType((Class) this.type); + } + } + + // internal constructors + + private TypeDescriptor(Class componentType, Field field, int nestingLevel) { + this.type = componentType; + this.field = field; + this.fieldNestingLevel = nestingLevel; + } + + private TypeDescriptor() { + } + + private TypeDescriptor(Class type) { + Assert.notNull(type, "Type must not be null"); + this.type = type; + } + + private TypeDescriptor(Class collectionType, Class elementType) { + this.type = collectionType; + if (elementType == null) { + elementType = Object.class; + } + this.elementType = TypeDescriptor.valueOf(elementType); + } + + private TypeDescriptor(Class mapType, Class keyType, Class valueType) { + this.type = mapType; + if (keyType == null) { + keyType = Object.class; + } + if (valueType == null) { + valueType = Object.class; + } + this.mapKeyType = TypeDescriptor.valueOf(keyType); + this.mapValueType = TypeDescriptor.valueOf(valueType); + } + +} \ No newline at end of file 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 5608a4ed575..5c0b5c96e48 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 @@ -52,7 +52,6 @@ final class ArrayToCollectionConverter implements ConditionalGenericConverter { return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor()); } - @SuppressWarnings("unchecked") public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; @@ -61,7 +60,7 @@ final class ArrayToCollectionConverter implements ConditionalGenericConverter { Collection target = CollectionFactory.createCollection(targetType.getType(), length); for (int i = 0; i < length; i++) { Object sourceElement = Array.get(source, i); - Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(sourceElement)); + Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor()); target.add(targetElement); } return target; 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 e4d70903e39..e26bae837f7 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 @@ -60,7 +60,7 @@ final class CollectionToArrayConverter implements ConditionalGenericConverter { Object array = Array.newInstance(targetType.getElementType(), sourceCollection.size()); int i = 0; for (Object sourceElement : sourceCollection) { - Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(sourceElement), targetType.getElementTypeDescriptor()); + Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor()); Array.set(array, i++, targetElement); } return array; 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 906bc423e90..4b573b10517 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 @@ -52,7 +52,6 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor()); } - @SuppressWarnings("unchecked") public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; @@ -63,9 +62,7 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert } Collection target = CollectionFactory.createCollection(targetType.getType(), sourceCollection.size()); for (Object sourceElement : sourceCollection) { - Object targetElement = this.conversionService.convert(sourceElement, - sourceType.getElementTypeDescriptor(sourceElement), - targetType.getElementTypeDescriptor(sourceElement)); + Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor()); target.add(targetElement); } return target; 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 9f1ac3b3ce2..c97b4c9f685 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 @@ -55,7 +55,7 @@ final class CollectionToObjectConverter implements ConditionalGenericConverter { return null; } Object firstElement = sourceCollection.iterator().next(); - return this.conversionService.convert(firstElement, sourceType.getElementTypeDescriptor(firstElement), targetType); + return this.conversionService.convert(firstElement, sourceType.getElementTypeDescriptor(), targetType); } } \ 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 d5ca3c8da94..39fc0c4debf 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 @@ -62,8 +62,7 @@ final class CollectionToStringConverter implements ConditionalGenericConverter { if (i > 0) { string.append(DELIMITER); } - Object targetElement = this.conversionService.convert( - sourceElement, sourceType.getElementTypeDescriptor(sourceElement), targetType); + Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType); string.append(targetElement); i++; } 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 172bda410a3..9a099773c6e 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 @@ -67,12 +67,8 @@ final class MapToMapConverter implements ConditionalGenericConverter { Map.Entry sourceMapEntry = (Map.Entry) entry; Object sourceKey = sourceMapEntry.getKey(); Object sourceValue = sourceMapEntry.getValue(); - Object targetKey = this.conversionService.convert(sourceKey, - sourceType.getMapKeyTypeDescriptor(sourceKey), - targetType.getMapKeyTypeDescriptor(sourceKey)); - Object targetValue = this.conversionService.convert(sourceValue, - sourceType.getMapValueTypeDescriptor(sourceValue), - targetType.getMapValueTypeDescriptor(sourceValue)); + Object targetKey = this.conversionService.convert(sourceKey, sourceType.getMapKeyTypeDescriptor(), targetType.getMapKeyTypeDescriptor()); + Object targetValue = this.conversionService.convert(sourceValue, sourceType.getMapValueTypeDescriptor(), targetType.getMapValueTypeDescriptor()); targetMap.put(targetKey, targetValue); } return targetMap; 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 6e9bdd94bc7..79204683bec 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 @@ -49,13 +49,12 @@ final class ObjectToCollectionConverter implements ConditionalGenericConverter { return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()); } - @SuppressWarnings("unchecked") public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } Collection target = CollectionFactory.createCollection(targetType.getType(), 1); - TypeDescriptor elementType = targetType.getElementTypeDescriptor(source); + TypeDescriptor elementType = targetType.getElementTypeDescriptor(); // Avoid potential recursion... if (!Collection.class.isAssignableFrom(elementType.getType())) { target.add(this.conversionService.convert(source, sourceType, elementType)); diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java index d6b6b07ceaf..dff40931476 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java @@ -40,31 +40,21 @@ public class PropertyTypeDescriptor extends TypeDescriptor { private final PropertyDescriptor propertyDescriptor; - private Annotation[] cachedAnnotations; - - /** * Create a new BeanTypeDescriptor for the given bean property. * @param propertyDescriptor the corresponding JavaBean PropertyDescriptor * @param methodParameter the target method parameter */ - public PropertyTypeDescriptor(PropertyDescriptor propertyDescriptor, MethodParameter methodParameter) { + public PropertyTypeDescriptor(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) { super(methodParameter); this.propertyDescriptor = propertyDescriptor; } - - /** - * Create a new BeanTypeDescriptor for the given bean property. - * @param propertyDescriptor the corresponding JavaBean PropertyDescriptor - * @param methodParameter the target method parameter - * @param type the specific type to expose (may be an array/collection element) - */ - public PropertyTypeDescriptor(PropertyDescriptor propertyDescriptor, MethodParameter methodParameter, Class type) { - super(methodParameter, type); + + public PropertyTypeDescriptor(Class componentType, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) { + super(componentType, methodParameter); this.propertyDescriptor = propertyDescriptor; } - /** * Return the underlying PropertyDescriptor. */ @@ -72,58 +62,48 @@ public class PropertyTypeDescriptor extends TypeDescriptor { return this.propertyDescriptor; } - public Annotation[] getAnnotations() { - Annotation[] anns = this.cachedAnnotations; - if (anns == null) { - Map, Annotation> annMap = new LinkedHashMap, Annotation>(); - String name = this.propertyDescriptor.getName(); - if (StringUtils.hasLength(name)) { - Class clazz = getMethodParameter().getMethod().getDeclaringClass(); - Field field = ReflectionUtils.findField(clazz, name); + protected Annotation[] resolveAnnotations() { + Map, Annotation> annMap = new LinkedHashMap, Annotation>(); + String name = this.propertyDescriptor.getName(); + if (StringUtils.hasLength(name)) { + Class clazz = getMethodParameter().getMethod().getDeclaringClass(); + Field field = ReflectionUtils.findField(clazz, name); + if (field == null) { + // Same lenient fallback checking as in CachedIntrospectionResults... + field = ReflectionUtils.findField(clazz, name.substring(0, 1).toLowerCase() + name.substring(1)); if (field == null) { - // Same lenient fallback checking as in CachedIntrospectionResults... - field = ReflectionUtils.findField(clazz, name.substring(0, 1).toLowerCase() + name.substring(1)); - if (field == null) { - field = ReflectionUtils.findField(clazz, name.substring(0, 1).toUpperCase() + name.substring(1)); - } - } - if (field != null) { - for (Annotation ann : field.getAnnotations()) { - annMap.put(ann.annotationType(), ann); - } + field = ReflectionUtils.findField(clazz, name.substring(0, 1).toUpperCase() + name.substring(1)); } } - Method writeMethod = this.propertyDescriptor.getWriteMethod(); - Method readMethod = this.propertyDescriptor.getReadMethod(); - if (writeMethod != null && writeMethod != getMethodParameter().getMethod()) { - for (Annotation ann : writeMethod.getAnnotations()) { + if (field != null) { + for (Annotation ann : field.getAnnotations()) { annMap.put(ann.annotationType(), ann); } } - if (readMethod != null && readMethod != getMethodParameter().getMethod()) { - for (Annotation ann : readMethod.getAnnotations()) { - annMap.put(ann.annotationType(), ann); - } - } - for (Annotation ann : getMethodParameter().getMethodAnnotations()) { - annMap.put(ann.annotationType(), ann); - } - for (Annotation ann : getMethodParameter().getParameterAnnotations()) { - annMap.put(ann.annotationType(), ann); - } - anns = annMap.values().toArray(new Annotation[annMap.size()]); - this.cachedAnnotations = anns; } - return anns; + Method writeMethod = this.propertyDescriptor.getWriteMethod(); + Method readMethod = this.propertyDescriptor.getReadMethod(); + if (writeMethod != null && writeMethod != getMethodParameter().getMethod()) { + for (Annotation ann : writeMethod.getAnnotations()) { + annMap.put(ann.annotationType(), ann); + } + } + if (readMethod != null && readMethod != getMethodParameter().getMethod()) { + for (Annotation ann : readMethod.getAnnotations()) { + annMap.put(ann.annotationType(), ann); + } + } + for (Annotation ann : getMethodParameter().getMethodAnnotations()) { + annMap.put(ann.annotationType(), ann); + } + for (Annotation ann : getMethodParameter().getParameterAnnotations()) { + annMap.put(ann.annotationType(), ann); + } + return annMap.values().toArray(new Annotation[annMap.size()]); } - public TypeDescriptor forElementType(Class elementType) { - if (elementType != null) { - return new PropertyTypeDescriptor(this.propertyDescriptor, getMethodParameter(), elementType); - } - else { - return super.forElementType(null); - } + public TypeDescriptor newComponentTypeDescriptor(Class componentType, MethodParameter nested) { + return new PropertyTypeDescriptor(componentType, nested, this.propertyDescriptor); } } 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 744caa0b6e8..767d495a025 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 @@ -48,7 +48,6 @@ final class StringToCollectionConverter implements ConditionalGenericConverter { return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()); } - @SuppressWarnings("unchecked") public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; @@ -57,8 +56,7 @@ final class StringToCollectionConverter implements ConditionalGenericConverter { String[] fields = StringUtils.commaDelimitedListToStringArray(string); Collection target = CollectionFactory.createCollection(targetType.getType(), fields.length); for (String sourceElement : fields) { - Object targetElement = this.conversionService.convert(sourceElement.trim(), - sourceType, targetType.getElementTypeDescriptor(sourceElement)); + Object targetElement = this.conversionService.convert(sourceElement.trim(), sourceType, targetType.getElementTypeDescriptor()); target.add(targetElement); } return target; 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 64f04e66df9..7c7eca223e3 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 @@ -16,16 +16,17 @@ package org.springframework.core.convert; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.assertFalse; +import org.junit.Ignore; import org.junit.Test; /** @@ -47,7 +48,8 @@ public class TypeDescriptorTests { public Map mapField = new HashMap(); - + public Map> nestedMapField = new HashMap>(); + @Test public void listDescriptor() throws Exception { TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfString")); @@ -94,14 +96,27 @@ public class TypeDescriptorTests { } @Test + @Ignore public void complexTypeDescriptor() throws Exception { TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("arrayOfListOfString")); assertTrue(typeDescriptor.isArray()); assertEquals(List.class,typeDescriptor.getElementType()); + assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType()); + // TODO asc notice that the type of the list elements is lost: typeDescriptor.getElementType() should return a TypeDescriptor assertEquals("java.util.List[]",typeDescriptor.asString()); } + @Test + public void complexTypeDescriptor2() throws Exception { + TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("nestedMapField")); + assertTrue(typeDescriptor.isMap()); + assertEquals(String.class,typeDescriptor.getMapKeyType()); + assertEquals(List.class, typeDescriptor.getMapValueType()); + assertEquals(Integer.class, typeDescriptor.getMapValueTypeDescriptor().getElementType()); + assertEquals("java.util.Map>", typeDescriptor.asString()); + } + @Test public void testEquals() throws Exception { TypeDescriptor t1 = TypeDescriptor.valueOf(String.class); diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java index 94bdb79b342..b87e8f39ec2 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java @@ -16,6 +16,13 @@ package org.springframework.core.convert.support; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.awt.Color; import java.math.BigDecimal; import java.math.BigInteger; import java.util.AbstractList; @@ -33,13 +40,14 @@ import java.util.Map; import java.util.Properties; import java.util.Set; -import static org.junit.Assert.*; import org.junit.Test; - +import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterRegistry; /** * @author Keith Donald @@ -286,6 +294,24 @@ public class DefaultConversionTests { assertEquals(new Integer("3"), result.get(2)); } + @Test + public void testSpr7766() throws Exception { + ConverterRegistry registry = ((ConverterRegistry) conversionService); + registry.addConverter(new ColorConverter()); + List colors = (List) conversionService.convert(new String[] { "ffffff", "#000000" }, TypeDescriptor.valueOf(String[].class), new TypeDescriptor(new MethodParameter(getClass().getMethod("handlerMethod", List.class), 0))); + assertEquals(2, colors.size()); + assertEquals(Color.WHITE, colors.get(0)); + assertEquals(Color.BLACK, colors.get(1)); + } + + public class ColorConverter implements Converter { + public Color convert(String source) { if (!source.startsWith("#")) source = "#" + source; return Color.decode(source); } + } + + public void handlerMethod(List color) { + + } + @Test public void convertArrayToCollectionImpl() { LinkedList result = conversionService.convert(new String[] { "1", "2", "3" }, LinkedList.class); 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 f3bed382e64..df0dd25181a 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 @@ -29,7 +29,7 @@ import org.springframework.core.convert.TypeDescriptor; */ public class TypedValue { - public static final TypedValue NULL = new TypedValue(null, TypeDescriptor.NULL); + public static final TypedValue NULL = new TypedValue(null); private final Object value; diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/common/ExpressionUtils.java b/org.springframework.expression/src/main/java/org/springframework/expression/common/ExpressionUtils.java index 0a591c6388d..b2f00b30cd9 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/common/ExpressionUtils.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/common/ExpressionUtils.java @@ -44,7 +44,7 @@ public abstract class ExpressionUtils { */ public static T convert(EvaluationContext context, Object value, Class targetType) throws EvaluationException { // TODO remove this function over time and use the one it delegates to - return convertTypedValue(context,new TypedValue(value,TypeDescriptor.forObject(value)),targetType); + return convertTypedValue(context,new TypedValue(value),targetType); } /** diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java index d4c8a6a60a0..dc1a1025618 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -124,7 +124,7 @@ public class ExpressionState { return TypedValue.NULL; } else { - return new TypedValue(value, TypeDescriptor.forObject(value)); + return new TypedValue(value); } } @@ -183,7 +183,7 @@ public class ExpressionState { OperatorOverloader overloader = this.relatedContext.getOperatorOverloader(); if (overloader.overridesOperation(op, left, right)) { Object returnValue = overloader.operate(op, left, right); - return new TypedValue(returnValue,TypeDescriptor.forObject(returnValue)); + return new TypedValue(returnValue); } else { String leftType = (left==null?"null":left.getClass().getName()); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java index b877f28abb0..faedfdc2ade 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java @@ -17,8 +17,8 @@ package org.springframework.expression.spel.ast; import java.lang.reflect.Array; -import java.util.List; import java.util.ArrayList; +import java.util.List; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; @@ -234,7 +234,6 @@ public class ConstructorReference extends SpelNodeImpl { else { componentType = arrayTypeCode.getType(); } - TypeDescriptor td = TypeDescriptor.valueOf(componentType); Object newArray; if (!hasInitializer()) { // Confirm all dimensions were specified (for example [3][][5] is missing the 2nd dimension) @@ -313,7 +312,7 @@ public class ConstructorReference extends SpelNodeImpl { throw new IllegalStateException(arrayTypeCode.name()); } } - return new TypedValue(newArray, td); + return new TypedValue(newArray); } private void populateReferenceTypeArray(ExpressionState state, Object newArray, TypeConverter typeConverter, 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 31b02615649..d18c8bbea64 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,20 +90,10 @@ public class Indexer extends SpelNodeImpl { // Indexing into a Map if (targetObject instanceof Map) { - if (targetObject == null) { - // Current decision: attempt to index into null map == exception and does not just return null - throw new SpelEvaluationException(getStartPosition(),SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE); - } Object possiblyConvertedKey = index; - if (targetObjectTypeDescriptor.isMapEntryTypeKnown()) { - possiblyConvertedKey = state.convertValue(index,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getMapKeyType())); - } + possiblyConvertedKey = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyTypeDescriptor()); Object o = ((Map) targetObject).get(possiblyConvertedKey); - if (targetObjectTypeDescriptor.isMapEntryTypeKnown()) { - return new TypedValue(o, targetObjectTypeDescriptor.getMapValueTypeDescriptor()); - } else { - return new TypedValue(o); - } + return new TypedValue(o, targetObjectTypeDescriptor.getMapValueTypeDescriptor().applyType(o)); } if (targetObject == null) { @@ -125,7 +115,7 @@ public class Indexer extends SpelNodeImpl { int pos = 0; for (Object o : c) { if (pos == idx) { - return new TypedValue(o, targetObjectTypeDescriptor.getElementTypeDescriptor()); + return new TypedValue(o, targetObjectTypeDescriptor.getElementTypeDescriptor().applyType(o)); } pos++; } @@ -195,10 +185,8 @@ public class Indexer extends SpelNodeImpl { Map map = (Map)targetObject; Object possiblyConvertedKey = index; Object possiblyConvertedValue = newValue; - if (targetObjectTypeDescriptor.isMapEntryTypeKnown()) { - possiblyConvertedKey = state.convertValue(index.getValue(),TypeDescriptor.valueOf(targetObjectTypeDescriptor.getMapKeyType())); - possiblyConvertedValue = state.convertValue(newValue,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getMapValueType())); - } + possiblyConvertedKey = state.convertValue(index.getValue(), targetObjectTypeDescriptor.getMapKeyTypeDescriptor()); + possiblyConvertedValue = state.convertValue(newValue, targetObjectTypeDescriptor.getMapValueTypeDescriptor()); map.put(possiblyConvertedKey,possiblyConvertedValue); return; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java index dbf4e941c4f..a5a818a3b27 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationException; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; @@ -72,8 +71,7 @@ public class InlineList extends SpelNodeImpl { constantList.add(((InlineList) child).getConstantValue()); } } - this.constant = new TypedValue(Collections.unmodifiableList(constantList), TypeDescriptor - .valueOf(List.class)); + this.constant = new TypedValue(Collections.unmodifiableList(constantList)); } } @@ -87,7 +85,7 @@ public class InlineList extends SpelNodeImpl { for (int c = 0; c < childcount; c++) { returnValue.add(getChild(c).getValue(expressionState)); } - return new TypedValue(returnValue, TypeDescriptor.valueOf(List.class)); + return new TypedValue(returnValue); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OpDivide.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OpDivide.java index 75ae01c62cf..19bd86cce82 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OpDivide.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OpDivide.java @@ -16,7 +16,6 @@ package org.springframework.expression.spel.ast; -import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationException; import org.springframework.expression.Operation; import org.springframework.expression.TypedValue; @@ -50,7 +49,7 @@ public class OpDivide extends Operator { } } Object result = state.operate(Operation.DIVIDE, operandOne, operandTwo); - return new TypedValue(result,TypeDescriptor.forObject(result)); + return new TypedValue(result); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Projection.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Projection.java index 6dc633c3bc5..e9c47e5e0df 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Projection.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Projection.java @@ -68,14 +68,14 @@ public class Projection extends SpelNodeImpl { List result = new ArrayList(); for (Map.Entry entry : mapData.entrySet()) { try { - state.pushActiveContextObject(new TypedValue(entry, TypeDescriptor.valueOf(Map.Entry.class))); + state.pushActiveContextObject(new TypedValue(entry)); result.add(this.children[0].getValueInternal(state).getValue()); } finally { state.popActiveContextObject(); } } - return new TypedValue(result,TypeDescriptor.valueOf(List.class)); // TODO unable to build correct type descriptor + return new TypedValue(result); // TODO unable to build correct type descriptor } else if (operand instanceof Collection || operandIsArray) { Collection data = (operand instanceof Collection ? (Collection) operand : 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 657d8ab0ceb..60639315a6c 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 @@ -74,7 +74,7 @@ public class Selection extends SpelNodeImpl { Object lastKey = null; for (Map.Entry entry : mapdata.entrySet()) { try { - TypedValue kvpair = new TypedValue(entry,TypeDescriptor.valueOf(Map.Entry.class)); + TypedValue kvpair = new TypedValue(entry); state.pushActiveContextObject(kvpair); Object o = selectionCriteria.getValueInternal(state).getValue(); if (o instanceof Boolean) { @@ -95,7 +95,7 @@ public class Selection extends SpelNodeImpl { } } if ((variant == FIRST || variant == LAST) && result.size() == 0) { - return new TypedValue(null,TypeDescriptor.NULL); + return new TypedValue(null); } if (variant == LAST) { Map resultMap = new HashMap(); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java index ec365788e87..bf350081b5d 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java @@ -16,7 +16,6 @@ package org.springframework.expression.spel.support; -import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.TypedValue; /** @@ -31,7 +30,7 @@ public class BooleanTypedValue extends TypedValue { private BooleanTypedValue(boolean b) { - super(b, TypeDescriptor.valueOf(Boolean.class)); + super(b); } 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 2ff2a6b6af1..8f7debd0ac8 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 @@ -236,7 +236,7 @@ public class ReflectionHelper { TypeDescriptor targetType; if (varargsPosition != null && argPosition >= varargsPosition) { MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition); - targetType = new TypeDescriptor(methodParam, methodParam.getParameterType().getComponentType()); + targetType = new TypeDescriptor(methodParam.getParameterType().getComponentType(), methodParam); } else { targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, argPosition)); @@ -268,7 +268,7 @@ public class ReflectionHelper { TypeDescriptor targetType; if (varargsPosition != null && argPosition >= varargsPosition) { MethodParameter methodParam = new MethodParameter(method, varargsPosition); - targetType = new TypeDescriptor(methodParam, methodParam.getParameterType().getComponentType()); + targetType = new TypeDescriptor(methodParam.getParameterType().getComponentType(), methodParam); } else { targetType = new TypeDescriptor(new MethodParameter(method, argPosition)); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java index 2e050192594..d392b6067eb 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java @@ -18,7 +18,6 @@ package org.springframework.expression.spel.support; import java.lang.reflect.Constructor; -import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.ConstructorExecutor; import org.springframework.expression.EvaluationContext; @@ -65,7 +64,7 @@ class ReflectiveConstructorExecutor implements ConstructorExecutor { arguments = ReflectionHelper.setupArgumentsForVarargsInvocation(this.ctor.getParameterTypes(), arguments); } ReflectionUtils.makeAccessible(this.ctor); - return new TypedValue(this.ctor.newInstance(arguments), TypeDescriptor.valueOf(this.ctor.getDeclaringClass())); + return new TypedValue(this.ctor.newInstance(arguments)); } catch (Exception ex) { throw new AccessException("Problem invoking constructor: " + this.ctor, 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 4cfc67861e5..e30e2c37db2 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 @@ -80,7 +80,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { // The readerCache will only contain gettable properties (let's not worry about setters for now) PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, method, null); TypeDescriptor typeDescriptor = - new PropertyTypeDescriptor(propertyDescriptor, new MethodParameter(method, -1)); + new PropertyTypeDescriptor(new MethodParameter(method, -1), propertyDescriptor); this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor)); this.typeDescriptorCache.put(cacheKey, typeDescriptor); return true; @@ -128,7 +128,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { // The readerCache will only contain gettable properties (let's not worry about setters for now) PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, method, null); TypeDescriptor typeDescriptor = - new PropertyTypeDescriptor(propertyDescriptor, new MethodParameter(method, -1)); + new PropertyTypeDescriptor(new MethodParameter(method, -1), propertyDescriptor); invoker = new InvokerPair(method, typeDescriptor); this.readerCache.put(cacheKey, invoker); } @@ -192,7 +192,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { throw new AccessException("Unable to access property '" + name + "' through setter "+method, ex); } MethodParameter mp = new MethodParameter(method,0); - TypeDescriptor typeDescriptor = new PropertyTypeDescriptor(propertyDescriptor, mp); + TypeDescriptor typeDescriptor = new PropertyTypeDescriptor(mp, propertyDescriptor); this.writerCache.put(cacheKey, method); this.typeDescriptorCache.put(cacheKey, typeDescriptor); return true; diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java index a4ab45bf05b..ed7d79f5dde 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java @@ -26,7 +26,6 @@ import java.util.Map; import junit.framework.Assert; import org.junit.Test; -import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; @@ -247,7 +246,6 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase { private static class FruitColourAccessor implements PropertyAccessor { private static Map propertyMap = new HashMap(); - private static TypeDescriptor mapElementTypeDescriptor = TypeDescriptor.valueOf(Color.class); static { propertyMap.put("banana",Color.yellow); @@ -267,7 +265,7 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase { } public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { - return new TypedValue(propertyMap.get(name),mapElementTypeDescriptor); + return new TypedValue(propertyMap.get(name)); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { @@ -306,7 +304,7 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase { } public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { - return new TypedValue(propertyMap.get(name),TypeDescriptor.valueOf(Color.class)); + return new TypedValue(propertyMap.get(name)); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java index af4fa8ed7a5..f984c985401 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java @@ -127,7 +127,7 @@ public class ExpressionStateTests extends ExpressionTestCase { Assert.assertEquals(TypedValue.NULL,state.getRootContextObject()); - ((StandardEvaluationContext)state.getEvaluationContext()).setRootObject(null,TypeDescriptor.NULL); + ((StandardEvaluationContext)state.getEvaluationContext()).setRootObject(null); Assert.assertEquals(null,state.getRootContextObject().getValue()); } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java index d9f98002714..2610b3a6be9 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java @@ -180,7 +180,7 @@ public class PropertyAccessTests extends ExpressionTestCase { public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { if (!name.equals("flibbles")) throw new RuntimeException("Assertion Failed! name should be flibbles"); - return new TypedValue(flibbles, TypeDescriptor.valueOf(String.class)); + return new TypedValue(flibbles); } public void write(EvaluationContext context, Object target, String name, Object newValue) diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java index 36534666642..fa05420e451 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java @@ -20,8 +20,8 @@ import java.lang.reflect.Method; import java.util.List; import junit.framework.Assert; -import org.junit.Test; +import org.junit.Test; import org.springframework.core.MethodParameter; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; @@ -222,7 +222,7 @@ public class ScenariosForSpringSecurity extends ExpressionTestCase { } public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { - return new TypedValue(new Principal(),TypeDescriptor.valueOf(Principal.class)); + return new TypedValue(new Principal()); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { @@ -252,7 +252,7 @@ public class ScenariosForSpringSecurity extends ExpressionTestCase { } public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { - return new TypedValue(activePerson,TypeDescriptor.valueOf(Person.class)); + return new TypedValue(activePerson); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { 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 02709cad162..71e56642fb7 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 @@ -69,6 +69,9 @@ public class SpelDocumentationTests extends ExpressionTestCase { public Inventor[] Members = new Inventor[1]; public List Members2 = new ArrayList(); public Map officers = new HashMap(); + + public List reverse = new ArrayList>(); + @SuppressWarnings("unchecked") IEEE() { officers.put("president",pupin); @@ -77,6 +80,8 @@ public class SpelDocumentationTests extends ExpressionTestCase { officers.put("advisors",linv); Members2.add(tesla); Members2.add(pupin); + + reverse.add(officers); } public boolean isMember(String name) { @@ -215,6 +220,9 @@ public class SpelDocumentationTests extends ExpressionTestCase { parser.parseExpression("officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia"); + Inventor i2 = parser.parseExpression("reverse[0]['advisors'][0]").getValue(societyContext,Inventor.class); + Assert.assertEquals("Nikola Tesla",i2.getName()); + } // 7.5.3