From d3b43ebccb923c6d5825b10d4f4f305b54ee549d Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Fri, 18 Sep 2009 19:57:59 +0000 Subject: [PATCH] refined generic converter concept --- .../beans/TypeConverterDelegate.java | 2 +- .../convert/ConversionFailedException.java | 18 +- .../core/convert/ConversionService.java | 26 ++- .../convert/ConverterNotFoundException.java | 10 +- .../core/convert/TypeDescriptor.java | 15 ++ .../support/ArrayGenericConverter.java | 56 +++++++ ...r.java => CollectionGenericConverter.java} | 31 ++-- .../support/GenericConversionService.java | 158 ++++++++++-------- .../convert/support/GenericConverter.java | 17 +- ...onverter.java => MapGenericConverter.java} | 58 ++++--- .../support/MatchableGenericConverter.java | 9 + .../GenericConversionServiceTests.java | 37 ++-- .../spel/support/StandardTypeConverter.java | 12 +- ...essionTestsUsingCoreConversionService.java | 14 +- 14 files changed, 290 insertions(+), 173 deletions(-) create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayGenericConverter.java rename org.springframework.core/src/main/java/org/springframework/core/convert/support/{CollectionToCollectionGenericConverter.java => CollectionGenericConverter.java} (65%) rename org.springframework.core/src/main/java/org/springframework/core/convert/support/{MapToMapGenericConverter.java => MapGenericConverter.java} (57%) create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/support/MatchableGenericConverter.java 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 f284d284c64..1725ae68e2a 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 @@ -174,7 +174,7 @@ class TypeConverterDelegate { else { typeDesc = TypeDescriptor.valueOf(requiredType); } - if (conversionService.canConvert(convertedValue.getClass(), typeDesc)) { + if (conversionService.matches(convertedValue.getClass(), typeDesc)) { return (T) conversionService.convert(convertedValue, typeDesc); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java index 1f9ef12b8b2..5846cfbade7 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java @@ -16,9 +16,6 @@ package org.springframework.core.convert; -import org.springframework.core.style.StylerUtils; -import org.springframework.util.ClassUtils; - /** * Thrown when an attempt to execute a type conversion fails. * @@ -27,7 +24,7 @@ import org.springframework.util.ClassUtils; */ public class ConversionFailedException extends ConversionException { - private Class sourceType; + private TypeDescriptor sourceType; private TypeDescriptor targetType; @@ -38,7 +35,7 @@ public class ConversionFailedException extends ConversionException { * @param targetType the value's target type * @param cause the cause of the conversion failure */ - public ConversionFailedException(Class sourceType, TypeDescriptor targetType, Object value, Throwable cause) { + public ConversionFailedException(TypeDescriptor sourceType, TypeDescriptor targetType, Object value, Throwable cause) { super(buildDefaultMessage(value, sourceType, targetType, cause), cause); this.sourceType = sourceType; this.targetType = targetType; @@ -47,7 +44,7 @@ public class ConversionFailedException extends ConversionException { /** * Return the source type we tried to convert the value from. */ - public Class getSourceType() { + public TypeDescriptor getSourceType() { return this.sourceType; } @@ -58,11 +55,10 @@ public class ConversionFailedException extends ConversionException { return this.targetType; } - - private static String buildDefaultMessage(Object value, Class sourceType, TypeDescriptor targetType, Throwable cause) { - return "Unable to convert value " + StylerUtils.style(value) + " from type '" + - ClassUtils.getQualifiedName(sourceType) + "' to type '" + - ClassUtils.getQualifiedName(targetType.getType()) + "'; reason = '" + cause.getMessage() + "'"; + private static String buildDefaultMessage(Object value, TypeDescriptor sourceType, TypeDescriptor targetType, + Throwable cause) { + return "Unable to convert value " + value + " from type [" + sourceType.getName() + "] to type [" + + targetType.getName() + "]; reason = '" + cause.getMessage() + "'"; } } \ 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 aebf448cf35..059858c724b 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 @@ -18,10 +18,7 @@ package org.springframework.core.convert; /** * A service interface for type conversion. This is the entry point into the convert system. - * - *

Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system. - * Call {@link #convert(Object, TypeDescriptor)} to perform a conversion with additional context - * about the targetType to convert to. + * Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system. * * @author Keith Donald * @since 3.0 @@ -35,7 +32,7 @@ public interface ConversionService { * @return true if a conversion can be performed, false if not */ boolean canConvert(Class sourceType, Class targetType); - + /** * Convert the source to targetType. * @param source the source to convert from (may be null) @@ -46,24 +43,25 @@ public interface ConversionService { T convert(Object source, Class targetType); /** - * Returns true if objects of sourceType can be converted to the targetType described by the TypeDescriptor. - * The TypeDescriptor provides additional context about the point where conversion is needed, often an object property location. + * Returns true if objects of sourceType can be converted to the targetType. + * The TypeDescriptors provide additional context about the variable locations where conversion would occur, often object property locations. * This flavor of the canConvert operation is mainly for use by a data binding framework, and not by user code. - * @param source the source type to convert from (required) + * @param source context about the source type to convert from (required) * @param targetType context about the target type to convert to (required) - * @return true if a conversion can be performed, false if not + * @return true if a conversion can be performed between the source and target types, false if not */ - boolean canConvert(Class sourceType, TypeDescriptor targetType); + boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); /** - * Convert the source to the targetType described by the TypeDescriptor. - * The TypeDescriptor provides additional context about the point where conversion is needed, often a object property location. + * Convert the source to targetTyp. + * The TypeDescriptors provide additional context about the variable locations where conversion will occur, often object property locations. * This flavor of the convert operation is mainly for use by a data binding framework, and not by user code. * @param source the source to convert from (may be null) + * @param sourceType context about the source type to convert from (required) * @param targetType context about the target type to convert to (required) - * @return the converted object, an instance of {@link TypeDescriptor#getType()} + * @return the converted object, an instance of {@link TypeDescriptor#getObjectType()} * @throws ConversionException if an exception occurred */ - Object convert(Object source, TypeDescriptor targetType); + Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ConverterNotFoundException.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ConverterNotFoundException.java index 53537457ea3..6e7bdb89edc 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ConverterNotFoundException.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ConverterNotFoundException.java @@ -24,9 +24,9 @@ package org.springframework.core.convert; */ public class ConverterNotFoundException extends ConversionException { - private final Class sourceType; + private final TypeDescriptor sourceType; - private final Class targetType; + private final TypeDescriptor targetType; /** * Creates a new conversion executor not found exception. @@ -34,7 +34,7 @@ public class ConverterNotFoundException extends ConversionException { * @param targetType the target type requested to convert to * @param message a descriptive message */ - public ConverterNotFoundException(Class sourceType, Class targetType) { + public ConverterNotFoundException(TypeDescriptor sourceType, TypeDescriptor targetType) { super("No converter found capable of converting from [" + sourceType.getName() + "] to [" + targetType.getName() + "]"); this.sourceType = sourceType; this.targetType = targetType; @@ -43,14 +43,14 @@ public class ConverterNotFoundException extends ConversionException { /** * Returns the source type that was requested to convert from. */ - public Class getSourceType() { + public TypeDescriptor getSourceType() { return this.sourceType; } /** * Returns the target type that was requested to convert to. */ - public Class getTargetType() { + public TypeDescriptor getTargetType() { return this.targetType; } 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 ddaa6e4e4af..4a41eae0f87 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 @@ -178,6 +178,13 @@ public class TypeDescriptor { } } + /** + * Return the element type as a type descriptor. + */ + public TypeDescriptor getElementTypeDescriptor() { + return TypeDescriptor.valueOf(getElementType()); + } + /** * Is this type a {@link Map} type? */ @@ -224,6 +231,14 @@ public class TypeDescriptor { } } + public TypeDescriptor getMapKeyTypeDescriptor() { + return TypeDescriptor.valueOf(getMapKeyType()); + } + + public TypeDescriptor getMapValueTypeDescriptor() { + return TypeDescriptor.valueOf(getMapValueType()); + } + /** * Obtain the annotations associated with the wrapped parameter/field, if any. */ diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayGenericConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayGenericConverter.java new file mode 100644 index 00000000000..fcac427afbd --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayGenericConverter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2009 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.support; + +import java.lang.reflect.Array; + +import org.springframework.core.convert.TypeDescriptor; + +/** + * A generic converter that can convert from one array type to another. + * + * @author Keith Donald + * @author Juergen Hoeller + * @since 3.0 + */ +class ArrayGenericConverter implements GenericConverter { + + private GenericConversionService conversionService; + + public ArrayGenericConverter(GenericConversionService conversionService) { + this.conversionService = conversionService; + } + + public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + return sourceType.isArray() && targetType.isArray(); + } + + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + if (sourceType.isAssignableTo(targetType)) { + return source; + } + TypeDescriptor targetElementType = targetType.getElementTypeDescriptor(); + TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor(); + Object target = Array.newInstance(targetElementType.getType(), Array.getLength(source)); + GenericConverter converter = conversionService.getConverter(sourceElementType, targetElementType); + for (int i = 0; i < Array.getLength(target); i++) { + Array.set(target, i, converter.convert(Array.get(source, i), sourceElementType, targetElementType)); + } + return target; + } + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionGenericConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionGenericConverter.java similarity index 65% rename from org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionGenericConverter.java rename to org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionGenericConverter.java index 4c8c686845a..aa9ffb4ff73 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionGenericConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionGenericConverter.java @@ -28,38 +28,43 @@ import org.springframework.core.convert.TypeDescriptor; * @author Juergen Hoeller * @since 3.0 */ -class CollectionToCollectionGenericConverter implements GenericConverter { +class CollectionGenericConverter implements GenericConverter { private GenericConversionService conversionService; - public CollectionToCollectionGenericConverter(GenericConversionService conversionService) { + public CollectionGenericConverter(GenericConversionService conversionService) { this.conversionService = conversionService; } + + public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + return sourceType.isCollection() && targetType.isCollection(); + } - public Object convert(Object source, TypeDescriptor targetType) { + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { Collection sourceCollection = (Collection) source; - Class targetElementType = targetType.getElementType(); - if (targetElementType == null) { + TypeDescriptor targetElementType = targetType.getElementTypeDescriptor(); + if (targetElementType == TypeDescriptor.NULL) { return compatibleCollectionWithoutElementConversion(sourceCollection, targetType); } - Class sourceElementType = getElementType(sourceCollection); - if (sourceElementType == null || targetElementType.isAssignableFrom(sourceElementType)) { + TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor(); + if (sourceElementType == TypeDescriptor.NULL) { + sourceElementType = getElementType(sourceCollection); + } + if (sourceElementType == TypeDescriptor.NULL || sourceElementType.isAssignableTo(targetElementType)) { return compatibleCollectionWithoutElementConversion(sourceCollection, targetType); } Collection targetCollection = CollectionFactory.createCollection(targetType.getType(), sourceCollection.size()); - TypeDescriptor targetElementTypeDescriptor = TypeDescriptor.valueOf(targetElementType); - GenericConverter elementConverter = conversionService.getConverter(sourceElementType, - targetElementTypeDescriptor); + GenericConverter elementConverter = conversionService.getConverter(sourceElementType, targetElementType); for (Object element : sourceCollection) { - targetCollection.add(elementConverter.convert(element, targetElementTypeDescriptor)); + targetCollection.add(elementConverter.convert(element, sourceElementType, targetElementType)); } return targetCollection; } - private Class getElementType(Collection collection) { + private TypeDescriptor getElementType(Collection collection) { for (Object element : collection) { if (element != null) { - return element.getClass(); + return TypeDescriptor.valueOf(element.getClass()); } } return null; 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 914b69fc8b4..c77876600a8 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 @@ -20,9 +20,9 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -38,7 +38,6 @@ import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.ConverterInfo; import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** * Base implementation of a conversion service. @@ -56,9 +55,12 @@ public class GenericConversionService implements ConversionService, ConverterReg private final Map> sourceTypeConverters = new HashMap>(); + private final Set matchableConverters = new LinkedHashSet(); + public GenericConversionService() { - addGenericConverter(Collection.class, Collection.class, new CollectionToCollectionGenericConverter(this)); - addGenericConverter(Map.class, Map.class, new MapToMapGenericConverter(this)); + addGenericConverter(new CollectionGenericConverter(this)); + addGenericConverter(new MapGenericConverter(this)); + addGenericConverter(new ArrayGenericConverter(this)); } /** @@ -122,106 +124,81 @@ public class GenericConversionService implements ConversionService, ConverterReg } public void removeConvertible(Class sourceType, Class targetType) { - Map sourceMap = getSourceMap(sourceType); - sourceMap.remove(targetType); + getSourceMap(sourceType).remove(targetType); } // implementing ConversionService public boolean canConvert(Class sourceType, Class targetType) { - return canConvert(sourceType, TypeDescriptor.valueOf(targetType)); + return canConvert(TypeDescriptor.valueOf(sourceType), TypeDescriptor.valueOf(targetType)); + } + + public T convert(Object source, Class targetType) { + Assert.notNull(targetType, "The targetType to convert to is required"); + return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType)); } - public boolean canConvert(Class sourceType, TypeDescriptor targetType) { + public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(sourceType, "The sourceType to convert from is required"); Assert.notNull(targetType, "The targetType to convert to is required"); if (targetType == TypeDescriptor.NULL) { return true; } - Class sourceClass = ClassUtils.resolvePrimitiveIfNecessary(sourceType); - Class targetClass = targetType.getObjectType(); - return getConverter(sourceClass, targetType) != null || this.parent != null - && this.parent.canConvert(sourceClass, targetClass); + return getConverter(sourceType, targetType) != null || this.parent != null + && this.parent.canConvert(sourceType, targetType); } - public T convert(Object source, Class targetType) { - Assert.notNull(targetType, "The targetType to convert to is required"); - return (T) convert(source, TypeDescriptor.valueOf(targetType)); - } - - public Object convert(Object source, TypeDescriptor targetType) { + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + Assert.notNull(sourceType, "The source type to convert to is required"); Assert.notNull(targetType, "The targetType to convert to is required"); if (source == null) { return null; } + Assert.isTrue(sourceType != TypeDescriptor.NULL, + "The source TypeDescriptor must not be TypeDescriptor.NULL when source != null"); if (targetType == TypeDescriptor.NULL) { return null; } - Class sourceType = ClassUtils.resolvePrimitiveIfNecessary(source.getClass()); GenericConverter converter = getConverter(sourceType, targetType); if (converter != null) { try { - return converter.convert(source, targetType); + return converter.convert(source, sourceType, targetType); } catch (ConversionFailedException e) { throw e; } catch (Exception e) { throw new ConversionFailedException(sourceType, targetType, source, e); } } else { - if (targetType.isAssignableValue(source)) { - return source; + if (this.parent != null) { + return this.parent.convert(source, sourceType, targetType); } else { - throw new ConverterNotFoundException(sourceType, targetType.getObjectType()); + if (targetType.isAssignableValue(source)) { + return source; + } else { + throw new ConverterNotFoundException(sourceType, targetType); + } } } } // subclassing hooks - protected GenericConverter getConverter(Class sourceType, TypeDescriptor targetType) { - if (sourceType.isInterface()) { - LinkedList classQueue = new LinkedList(); - classQueue.addFirst(sourceType); - while (!classQueue.isEmpty()) { - Class currentClass = classQueue.removeLast(); - Map converters = getConvertersForSource(currentClass); - GenericConverter converter = getConverter(converters, targetType); - if (converter != null) { - return converter; - } - Class[] interfaces = currentClass.getInterfaces(); - for (Class ifc : interfaces) { - classQueue.addFirst(ifc); - } - } - Map objectConverters = getConvertersForSource(Object.class); - return getConverter(objectConverters, targetType); - } else { - LinkedList classQueue = new LinkedList(); - classQueue.addFirst(sourceType); - while (!classQueue.isEmpty()) { - Class currentClass = classQueue.removeLast(); - Map converters = getConvertersForSource(currentClass); - GenericConverter converter = getConverter(converters, targetType); - if (converter != null) { - return converter; - } - if (currentClass.getSuperclass() != null) { - classQueue.addFirst(currentClass.getSuperclass()); - } - Class[] interfaces = currentClass.getInterfaces(); - for (Class ifc : interfaces) { - classQueue.addFirst(ifc); + protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { + GenericConverter converter = matchConverterByClassPair(sourceType.getObjectType(), targetType.getObjectType()); + if (converter == null) { + for (GenericConverter matchableConverter : this.matchableConverters) { + if (matchableConverter.canConvert(sourceType, targetType)) { + return matchableConverter; } } - return null; } + return converter; } // internal helpers - private void addGenericConverter(Class sourceType, Class targetType, GenericConverter converter) { - getSourceMap(sourceType).put(targetType, converter); + private void addGenericConverter(GenericConverter converter) { + this.matchableConverters.add(converter); } private List getRequiredTypeInfo(Object converter) { @@ -275,6 +252,46 @@ public class GenericConversionService implements ConversionService, ConverterReg return null; } + private GenericConverter matchConverterByClassPair(Class sourceType, Class targetType) { + if (sourceType.isInterface()) { + LinkedList classQueue = new LinkedList(); + classQueue.addFirst(sourceType); + while (!classQueue.isEmpty()) { + Class currentClass = classQueue.removeLast(); + Map converters = getConvertersForSource(currentClass); + GenericConverter converter = getConverter(converters, targetType); + if (converter != null) { + return converter; + } + Class[] interfaces = currentClass.getInterfaces(); + for (Class ifc : interfaces) { + classQueue.addFirst(ifc); + } + } + Map objectConverters = getConvertersForSource(Object.class); + return getConverter(objectConverters, targetType); + } else { + LinkedList classQueue = new LinkedList(); + classQueue.addFirst(sourceType); + while (!classQueue.isEmpty()) { + Class currentClass = classQueue.removeLast(); + Map converters = getConvertersForSource(currentClass); + GenericConverter converter = getConverter(converters, targetType); + if (converter != null) { + return converter; + } + if (currentClass.getSuperclass() != null) { + classQueue.addFirst(currentClass.getSuperclass()); + } + Class[] interfaces = currentClass.getInterfaces(); + for (Class ifc : interfaces) { + classQueue.addFirst(ifc); + } + } + return null; + } + } + private Map getSourceMap(Class sourceType) { Map sourceMap = sourceTypeConverters.get(sourceType); if (sourceMap == null) { @@ -292,11 +309,10 @@ public class GenericConversionService implements ConversionService, ConverterReg return converters; } - private GenericConverter getConverter(Map converters, TypeDescriptor targetType) { - Class targetClass = targetType.getObjectType(); - if (targetClass.isInterface()) { + private GenericConverter getConverter(Map converters, Class targetType) { + if (targetType.isInterface()) { LinkedList classQueue = new LinkedList(); - classQueue.addFirst(targetClass); + classQueue.addFirst(targetType); while (!classQueue.isEmpty()) { Class currentClass = classQueue.removeLast(); GenericConverter converter = converters.get(currentClass); @@ -311,7 +327,7 @@ public class GenericConversionService implements ConversionService, ConverterReg return converters.get(Object.class); } else { LinkedList classQueue = new LinkedList(); - classQueue.addFirst(targetClass); + classQueue.addFirst(targetType); while (!classQueue.isEmpty()) { Class currentClass = classQueue.removeLast(); GenericConverter converter = converters.get(currentClass); @@ -338,7 +354,11 @@ public class GenericConversionService implements ConversionService, ConverterReg this.converter = converter; } - public Object convert(Object source, TypeDescriptor type) { + public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + throw new UnsupportedOperationException("Should not be called"); + } + + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return converter.convert(source); } @@ -352,7 +372,11 @@ public class GenericConversionService implements ConversionService, ConverterReg this.converterFactory = converterFactory; } - public Object convert(Object source, TypeDescriptor targetType) { + public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + throw new UnsupportedOperationException("Should not be called"); + } + + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return converterFactory.getConverter(targetType.getObjectType()).convert(source); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConverter.java index f17cef46d86..d21c875a1ba 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConverter.java @@ -21,9 +21,9 @@ import org.springframework.core.convert.converter.ConverterFactory; /** * Uniform Converter interface used by GenericConversionService. - * This interface is an internal detail of the GenericConversionService implementation. + * This interface is primarily an internal detail of the GenericConversionService implementation. * It should generally not be implemented by application code directly. - * See {@link Converter} and {@link ConverterFactory} interfaces for public converter SPIs. + * See {@link Converter} and {@link ConverterFactory} interfaces for simpler public converter SPIs. * @author Keith Donald * @since 3.0 * @see Converter @@ -31,12 +31,21 @@ import org.springframework.core.convert.converter.ConverterFactory; */ public interface GenericConverter { + /** + * Can this converter convert the sourceType to targetType. + * @param sourceType context about the sourceType to convert to + * @param targetType context about the targetType to convert to + * @return true if so, false otherwise + */ + boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); + /** * Convert the source to the targetType described by the TypeDescriptor. * @param source the source object to convert (never null) - * @param targetType the target type to convert to + * @param sourceType context about the source type to convert from + * @param targetType context about the target type to convert to * @return the converted object */ - Object convert(Object source, TypeDescriptor targetType); + Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapGenericConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapGenericConverter.java similarity index 57% rename from org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapGenericConverter.java rename to org.springframework.core/src/main/java/org/springframework/core/convert/support/MapGenericConverter.java index 4eb57b8979d..1141ddf884d 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapGenericConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapGenericConverter.java @@ -5,33 +5,37 @@ import java.util.Map; import org.springframework.core.CollectionFactory; import org.springframework.core.convert.TypeDescriptor; -class MapToMapGenericConverter implements GenericConverter { +class MapGenericConverter implements GenericConverter { private GenericConversionService conversionService; - public MapToMapGenericConverter(GenericConversionService conversionService) { + public MapGenericConverter(GenericConversionService conversionService) { this.conversionService = conversionService; } - public Object convert(Object source, TypeDescriptor targetType) { + public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + return sourceType.isMap() && targetType.isMap(); + } + + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { Map sourceMap = (Map) source; - Class targetKeyType = targetType.getMapKeyType(); - Class targetValueType = targetType.getMapValueType(); + TypeDescriptor targetKeyType = targetType.getMapKeyTypeDescriptor(); + TypeDescriptor targetValueType = targetType.getMapValueTypeDescriptor(); if (targetKeyType == null && targetValueType == null) { return compatibleMapWithoutEntryConversion(sourceMap, targetType); } - Class[] sourceEntryTypes = getMapEntryTypes(sourceMap); - Class sourceKeyType = sourceEntryTypes[0]; - Class sourceValueType = sourceEntryTypes[1]; + TypeDescriptor[] sourceEntryTypes = getMapEntryTypes(sourceMap); + TypeDescriptor sourceKeyType = sourceEntryTypes[0]; + TypeDescriptor sourceValueType = sourceEntryTypes[1]; if (sourceKeyType == null && sourceValueType == null) { return compatibleMapWithoutEntryConversion(sourceMap, targetType); } boolean keysCompatible = false; - if (targetKeyType != null && sourceKeyType != null && targetKeyType.isAssignableFrom(sourceKeyType)) { + if (sourceKeyType != null && targetKeyType != null && sourceKeyType.isAssignableTo(targetKeyType)) { keysCompatible = true; } boolean valuesCompatible = false; - if (targetValueType != null && sourceValueType != null && targetValueType.isAssignableFrom(sourceValueType)) { + if (sourceValueType != null && targetValueType != null && sourceValueType.isAssignableTo(targetValueType)) { valuesCompatible = true; } if (keysCompatible && valuesCompatible) { @@ -46,7 +50,7 @@ class MapToMapGenericConverter implements GenericConverter { return targetMap; } - private Class[] getMapEntryTypes(Map sourceMap) { + private TypeDescriptor[] getMapEntryTypes(Map sourceMap) { Class keyType = null; Class valueType = null; for (Object entry : sourceMap.entrySet()) { @@ -63,7 +67,7 @@ class MapToMapGenericConverter implements GenericConverter { break; } } - return new Class[] { keyType, valueType }; + return new TypeDescriptor[] { TypeDescriptor.valueOf(keyType), TypeDescriptor.valueOf(valueType) }; } private Map compatibleMapWithoutEntryConversion(Map source, TypeDescriptor targetType) { @@ -82,26 +86,32 @@ class MapToMapGenericConverter implements GenericConverter { private GenericConverter valueConverter; - private TypeDescriptor targetKeyTypeDescriptor; + private TypeDescriptor sourceKeyType; - private TypeDescriptor targetValueTypeDescriptor; + private TypeDescriptor sourceValueType; + + private TypeDescriptor targetKeyType; + + private TypeDescriptor targetValueType; - public MapEntryConverter(Class sourceKeyType, Class sourceValueType, Class targetKeyType, - Class targetValueType, boolean keysCompatible, boolean valuesCompatible, + public MapEntryConverter(TypeDescriptor sourceKeyType, TypeDescriptor sourceValueType, TypeDescriptor targetKeyType, + TypeDescriptor targetValueType, boolean keysCompatible, boolean valuesCompatible, GenericConversionService conversionService) { - if (sourceKeyType != null && targetKeyType != null && !keysCompatible) { - this.targetKeyTypeDescriptor = TypeDescriptor.valueOf(targetKeyType); - this.keyConverter = conversionService.getConverter(sourceKeyType, targetKeyTypeDescriptor); + if (sourceKeyType != TypeDescriptor.NULL && targetKeyType != TypeDescriptor.NULL && !keysCompatible) { + this.keyConverter = conversionService.getConverter(sourceKeyType, targetKeyType); + this.sourceKeyType = sourceKeyType; + this.targetKeyType = targetKeyType; } - if (sourceValueType != null && targetValueType != null && !valuesCompatible) { - this.targetValueTypeDescriptor = TypeDescriptor.valueOf(targetValueType); - this.valueConverter = conversionService.getConverter(sourceValueType, targetValueTypeDescriptor); + if (sourceValueType != TypeDescriptor.NULL && targetValueType != TypeDescriptor.NULL && !valuesCompatible) { + this.valueConverter = conversionService.getConverter(sourceValueType, targetValueType); + this.targetKeyType = targetKeyType; + this.targetValueType = targetValueType; } } public Object convertKey(Object sourceKey) { if (sourceKey != null && this.keyConverter != null) { - return this.keyConverter.convert(sourceKey, targetKeyTypeDescriptor); + return this.keyConverter.convert(sourceKey, sourceKeyType, targetKeyType); } else { return sourceKey; } @@ -109,7 +119,7 @@ class MapToMapGenericConverter implements GenericConverter { public Object convertValue(Object sourceValue) { if (sourceValue != null && this.valueConverter != null) { - return this.valueConverter.convert(sourceValue, targetValueTypeDescriptor); + return this.valueConverter.convert(sourceValue, sourceValueType, targetValueType); } else { return sourceValue; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/MatchableGenericConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/MatchableGenericConverter.java new file mode 100644 index 00000000000..3898392541a --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/MatchableGenericConverter.java @@ -0,0 +1,9 @@ +package org.springframework.core.convert.support; + +import org.springframework.core.convert.TypeDescriptor; + +public interface MatchableGenericConverter extends GenericConverter { + + boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType); + +} 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 6c840a2c078..59685165ddd 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 @@ -53,7 +53,7 @@ public class GenericConversionServiceTests { public void executeConversionNullSource() { assertEquals(null, converter.convert(null, Integer.class)); } - + @Test public void executeCompatibleSource() { assertEquals(Boolean.FALSE, converter.convert(false, boolean.class)); @@ -86,18 +86,17 @@ public class GenericConversionServiceTests { public void convertNull() { assertNull(converter.convert(null, Integer.class)); } - - @Test(expected=IllegalArgumentException.class) + + @Test(expected = IllegalArgumentException.class) public void convertNullTargetClass() { - assertEquals("3", converter.convert("3", (Class)null)); + assertEquals("3", converter.convert("3", (Class) null)); } @Test public void convertNullConversionPointType() { - assertEquals(null, converter.convert("3", TypeDescriptor.NULL)); + assertEquals(null, converter.convert(3, TypeDescriptor.valueOf(String.class), TypeDescriptor.NULL)); } - @Test public void convertWrongTypeArgument() { converter.addConverterFactory(new StringToNumberConverterFactory()); @@ -128,7 +127,6 @@ public class GenericConversionServiceTests { } @Test - @Ignore public void convertArrayToArray() { converter.addConverterFactory(new StringToNumberConverterFactory()); Integer[] result = converter.convert(new String[] { "1", "2", "3" }, Integer[].class); @@ -138,7 +136,6 @@ public class GenericConversionServiceTests { } @Test - @Ignore public void convertArrayToPrimitiveArray() { converter.addConverterFactory(new StringToNumberConverterFactory()); int[] result = converter.convert(new String[] { "1", "2", "3" }, int[].class); @@ -146,6 +143,14 @@ public class GenericConversionServiceTests { assertEquals(2, result[1]); assertEquals(3, result[2]); } + + @Test + public void convertArrayToArrayAssignable() { + int[] result = converter.convert(new int[] { 1, 2, 3 }, int[].class); + assertEquals(1, result[0]); + assertEquals(2, result[1]); + assertEquals(3, result[2]); + } @Test @Ignore @@ -162,7 +167,8 @@ public class GenericConversionServiceTests { @Ignore public void convertArrayToListGenericTypeConversion() throws Exception { converter.addConverterFactory(new StringToNumberConverterFactory()); - List result = (List) converter.convert(new String[] { "1", "2", "3" }, new TypeDescriptor(getClass().getDeclaredField("genericList"))); + List result = (List) converter.convert(new String[] { "1", "2", "3" }, TypeDescriptor + .valueOf(String[].class), new TypeDescriptor(getClass().getDeclaredField("genericList"))); assertEquals(new Integer("1"), result.get(0)); assertEquals(new Integer("2"), result.get(1)); assertEquals(new Integer("3"), result.get(2)); @@ -221,12 +227,13 @@ public class GenericConversionServiceTests { foo.add("1"); foo.add("2"); foo.add("3"); - List bar = (List)converter.convert(foo, new TypeDescriptor(getClass().getField("genericList"))); + List bar = (List) converter.convert(foo, TypeDescriptor.valueOf(List.class), + new TypeDescriptor(getClass().getField("genericList"))); assertEquals(new Integer(1), bar.get(0)); assertEquals(new Integer(2), bar.get(1)); assertEquals(new Integer(3), bar.get(2)); } - + public Map genericMap = new HashMap(); @Test @@ -236,7 +243,8 @@ public class GenericConversionServiceTests { foo.put("2", "BAZ"); converter.addConverterFactory(new StringToNumberConverterFactory()); converter.addConverterFactory(new StringToEnumConverterFactory()); - Map map = (Map) converter.convert(foo, new TypeDescriptor(getClass().getField("genericMap"))); + Map map = (Map) converter.convert(foo, TypeDescriptor.valueOf(Map.class), + new TypeDescriptor(getClass().getField("genericMap"))); assertEquals(map.get(1), FooEnum.BAR); assertEquals(map.get(2), FooEnum.BAZ); } @@ -250,11 +258,11 @@ public class GenericConversionServiceTests { assertEquals("2", result[1]); assertEquals("3", result[2]); } - + @Test @Ignore public void convertStringToArrayWithElementConversion() { - converter.addConverterFactory(new StringToNumberConverterFactory()); + converter.addConverterFactory(new StringToNumberConverterFactory()); Integer[] result = converter.convert("1,2,3", Integer[].class); assertEquals(3, result.length); assertEquals(new Integer(1), result[0]); @@ -262,7 +270,6 @@ public class GenericConversionServiceTests { assertEquals(new Integer(3), result[2]); } - public static enum FooEnum { BAR, BAZ } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java index 5602841d965..f201bf18653 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java @@ -38,26 +38,24 @@ import org.springframework.util.Assert; */ public class StandardTypeConverter implements TypeConverter { - private final ConversionService typeConverter; - + private final ConversionService conversionService; public StandardTypeConverter() { - this.typeConverter = new DefaultConversionService(); + this.conversionService = new DefaultConversionService(); } public StandardTypeConverter(ConversionService typeConverter) { Assert.notNull(typeConverter, "ConversionService must not be null"); - this.typeConverter = typeConverter; + this.conversionService = typeConverter; } - public boolean canConvert(Class sourceType, Class targetType) { - return this.typeConverter.canConvert(sourceType, targetType); + return this.conversionService.canConvert(sourceType, targetType); } public Object convertValue(Object value, TypeDescriptor typeDescriptor) throws EvaluationException { try { - return this.typeConverter.convert(value, typeDescriptor); + return this.conversionService.convert(value, TypeDescriptor.forObject(value), typeDescriptor); } catch (ConverterNotFoundException cenfe) { throw new SpelEvaluationException(cenfe, SpelMessage.TYPE_CONVERSION_ERROR, value.getClass(), typeDescriptor.asString()); 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 b136d45642b..cbed2bd8681 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 @@ -101,21 +101,11 @@ public class ExpressionTestsUsingCoreConversionService extends ExpressionTestCas private final DefaultConversionService service = new DefaultConversionService(); public boolean canConvert(Class sourceType, Class targetType) { - return this.service.canConvert(sourceType, TypeDescriptor.valueOf(targetType)); + return this.service.canConvert(sourceType, targetType); } - public boolean canConvert(Class sourceType, TypeDescriptor typeDescriptor) { - return this.service.canConvert(sourceType, typeDescriptor); - } - - @SuppressWarnings("cast") - public T convertValue(Object value, Class targetType) throws EvaluationException { - return (T) this.service.convert(value,TypeDescriptor.valueOf(targetType)); - } - - @SuppressWarnings("unchecked") public Object convertValue(Object value, TypeDescriptor typeDescriptor) throws EvaluationException { - return this.service.convert(value, typeDescriptor); + return this.service.convert(value, TypeDescriptor.forObject(value), typeDescriptor); } }