From 0ef0ff60f0b08b521ddc8fc093b38d7c9eb98326 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Sun, 8 Mar 2009 08:47:10 +0000 Subject: [PATCH] super converter implementation --- .../core/convert/ConversionExecutor.java | 15 +- .../core/convert/ConversionService.java | 16 +- .../core/convert/converter/Converter.java | 6 +- .../convert/converter/NumberToNumber.java | 6 +- .../core/convert/converter/StringToEnum.java | 10 +- .../convert/converter/SuperConverter.java | 24 +- .../converter/SuperTwoWayConverter.java | 27 ++ .../SuperTwoWayConverterConverter.java | 31 +++ .../core/convert/service/ArrayToArray.java | 15 -- .../convert/service/ArrayToCollection.java | 46 +--- .../service/CollectionConversionUtils.java | 43 +++ .../service/CollectionToCollection.java | 48 +--- .../service/DefaultConversionService.java | 3 +- .../service/GenericConversionService.java | 173 +++++++++--- .../core/convert/service/NoOpConverter.java | 5 - .../core/convert/service/ObjectToArray.java | 14 +- .../convert/service/ObjectToCollection.java | 46 +--- .../service/ReverseSuperConverter.java | 34 +++ .../service/StaticConversionExecutor.java | 21 +- .../StaticSuperConversionExecutor.java | 23 +- .../DefaultConversionServiceTests.java | 159 ----------- .../GenericConversionServiceTests.java | 253 ++++++++++++++++++ 22 files changed, 598 insertions(+), 420 deletions(-) create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverter.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverterConverter.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionConversionUtils.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/service/ReverseSuperConverter.java delete mode 100644 org.springframework.core/src/test/java/org/springframework/core/convert/service/DefaultConversionServiceTests.java create mode 100644 org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionExecutor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionExecutor.java index 5d8b5e65814..56d7553e79f 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionExecutor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionExecutor.java @@ -16,7 +16,7 @@ package org.springframework.core.convert; /** - * A command object that is parameterized with the information necessary to perform a conversion of a source input to a + * A command parameterized with the information necessary to perform a conversion of a source input to a * target output. Encapsulates knowledge about how to convert source objects to a specific target type using a specific * converter. * @@ -25,20 +25,19 @@ package org.springframework.core.convert; public interface ConversionExecutor { /** - * Returns the source class of conversions performed by this executor. - * @return the source class + * The type this executor converts from. */ public Class getSourceClass(); /** - * Returns the target class of conversions performed by this executor. - * @return the target class - */ + * The type this executor converts to. +s */ public Class getTargetClass(); /** - * Execute the conversion for the provided source object. - * @param source the source object to convert + * Convert the source to T. + * @param source the source to convert + * @throws ConversionExecutionException if an exception occurs during type conversion */ public T execute(S source) throws ConversionExecutionException; 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 9735a60986c..1413acca4e2 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 @@ -26,7 +26,7 @@ package org.springframework.core.convert; public interface ConversionService { /** - * Convert the source object to targetClass + * Convert the source to target class T. * @param source the source to convert from (may be null) * @param targetClass the target class to convert to * @return the converted object, an instance of the targetClass, or null if a null source @@ -39,7 +39,7 @@ public interface ConversionService { ConversionException; /** - * Convert the source object to targetClass using a custom converter. + * Convert the source to target class T with a custom converter. * @param converterId the id of the custom converter, which must be registered with this conversion service and * capable of converting to the targetClass * @param source the source to convert from (may be null) @@ -54,7 +54,7 @@ public interface ConversionService { throws ConversionExecutorNotFoundException, ConversionException; /** - * Get a ConversionExecutor capable of converting objects from sourceClass to targetClass. + * Get a ConversionExecutor that converts objects from S to T. * The returned ConversionExecutor is thread-safe and may safely be cached for later use by client code. * @param sourceClass the source class to convert from (required) * @param targetClass the target class to convert to (required) @@ -65,9 +65,8 @@ public interface ConversionService { throws ConversionExecutorNotFoundException; /** - * Get a ConversionExecutor that uses a custom converter to capable convert objects from sourceClass to - * targetClass. The returned ConversionExecutor is thread-safe and may safely be cached for use in - * client code. + * Get a ConversionExecutor that that converts objects from S to T with a custom converter. + * The returned ConversionExecutor is thread-safe and may safely be cached for use in client code. * @param converterId the id of the custom converter, which must be registered with this conversion service and * capable of converting from sourceClass to targetClass (required) * @param sourceClass the source class to convert from (required) @@ -79,9 +78,8 @@ public interface ConversionService { Class targetClass) throws ConversionExecutorNotFoundException; /** - * Lookup a class by its well-known alias. For example, long for java.lang.Long - * @param alias the class alias - * @return the class, or null if no alias exists + * Get a class by its alias. + * @return the class, or null if no such alias exists */ public Class getClassForAlias(String alias); diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/Converter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/Converter.java index 112cad0d161..0ace3199579 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/Converter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/Converter.java @@ -19,7 +19,7 @@ import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionService; /** - * A converter converts a source object of type S to a target type of type T and back. + * A converter converts a source object of type S to a target of type T and back. *

* Implementations of this interface are thread-safe and can be shared. Converters are typically registered with and * accessed through a {@link ConversionService}. @@ -29,7 +29,7 @@ import org.springframework.core.convert.ConversionService; public interface Converter { /** - * Convert the source S to target type T. + * Convert the source of type S to target type T. * @param source the source object to convert, which must be an instance of S * @return the converted object, which must be an instance of T * @throws Exception an exception occurred performing the conversion; may be any checked exception, the conversion @@ -39,7 +39,7 @@ public interface Converter { public T convert(S source) throws Exception; /** - * Convert the target T back to source type S. + * Convert the target of type T back to source type S. * @param target the target object to convert, which must be an instance of T * @return the converted object, which must be an instance of S * @throws Exception an exception occurred performing the conversion; may be any checked exception, the conversion diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/NumberToNumber.java b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/NumberToNumber.java index c32dad52066..3bcdc366724 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/NumberToNumber.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/NumberToNumber.java @@ -37,12 +37,8 @@ import org.springframework.util.NumberUtils; */ public class NumberToNumber implements SuperConverter { - public N convert(Number source, Class targetClass) throws Exception { + public RT convert(Number source, Class targetClass) throws Exception { return NumberUtils.convertNumberToTargetClass(source, targetClass); } - public Number convertBack(Number target) throws Exception { - return target; - } - } \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/StringToEnum.java b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/StringToEnum.java index d0219dd6e25..c1db9bb25a0 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/StringToEnum.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/StringToEnum.java @@ -16,19 +16,19 @@ package org.springframework.core.convert.converter; /** - * Converts from a String to Enum using {@link Enum#valueOf(Class, String)}. + * Converts a String to a Enum using {@link Enum#valueOf(Class, String)}. * * @author Keith Donald */ @SuppressWarnings("unchecked") -public class StringToEnum implements SuperConverter { +public class StringToEnum implements SuperTwoWayConverter { - public E convert(String source, Class targetClass) throws Exception { + public RT convert(String source, Class targetClass) throws Exception { return Enum.valueOf(targetClass, source); } - public String convertBack(Enum target) throws Exception { - return target.name(); + public RS convertBack(Enum target, Class sourceClass) throws Exception { + return (RS) target.name(); } } \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperConverter.java index 89a98e91543..4ee7275c2b7 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperConverter.java @@ -19,8 +19,7 @@ import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionService; /** - * A super converter converts a source object of type S to a target type of type AT and back, where AT is equal to or a - * subclass of T, T being the "super" target type. This allows a single converter to convert to objects that are part of a common class hierarchy. + * A super converter converts a source object of type S to a target of class hierarchy T. *

* Implementations of this interface are thread-safe and can be shared. Converters are typically registered with and * accessed through a {@link ConversionService}. @@ -30,25 +29,14 @@ import org.springframework.core.convert.ConversionService; public interface SuperConverter { /** - * Convert the source S to an instance of AT. - * @param source the source object to convert, which must be an instance of S - * @param actualTargetClass the actual target class to convert to (AT), which must be equal to or a specialization - * of T. - * @return the converted object, which must be an instance of AT + * Convert the source of type S to an instance of type RT. + * @param source the source object to convert, whose class must be equal to or a subclass of S + * @param targetClass the requested target class to convert to (RT), which must be equal to T or extend from T + * @return the converted object, which must be an instance of RT * @throws Exception an exception occurred performing the conversion; may be any checked exception, the conversion * system will handle wrapping the failure in a {@link ConversionException} that provides a consistent type * conversion error context */ - public AT convert(S source, Class actualTargetClass) throws Exception; - - /** - * Convert the target T to an instance of S. - * @param target the target object to convert, which must be an instance of T. - * @return the converted object, which must be an instance of S. - * @throws Exception an exception occurred performing the conversion; may be any checked exception, the conversion - * system will handle wrapping the failure in a {@link ConversionException} that provides a consistent type - * conversion error context - */ - public S convertBack(T target) throws Exception; + public RT convert(S source, Class targetClass) throws Exception; } \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverter.java new file mode 100644 index 00000000000..2605da39ba8 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverter.java @@ -0,0 +1,27 @@ +package org.springframework.core.convert.converter; + +import org.springframework.core.convert.ConversionException; +import org.springframework.core.convert.ConversionService; + +/** + * A super converter that can also convert a target object of type T to a source of class hierarchy S. + *

+ * Implementations of this interface are thread-safe and can be shared. Converters are typically registered with and + * accessed through a {@link ConversionService}. + *

+ * @author Keith Donald + */ +public interface SuperTwoWayConverter extends SuperConverter { + + /** + * Convert the target of type T to an instance of S. + * @param target the target object to convert, whose class must be equal to or a subclass of T + * @param sourceClass the requested source class to convert to, which must be equal to S or extend from S + * @return the converted object, which must be an instance of S + * @throws Exception an exception occurred performing the conversion; may be any checked exception, the conversion + * system will handle wrapping the failure in a {@link ConversionException} that provides a consistent type + * conversion error context + */ + public RS convertBack(T target, Class sourceClass) throws Exception; + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverterConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverterConverter.java new file mode 100644 index 00000000000..0fe221038a3 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverterConverter.java @@ -0,0 +1,31 @@ +package org.springframework.core.convert.converter; + +/** + * Adapts a {@link SuperTwoWayConverter} to the {@link Converter} interface in a type safe way. This adapter is useful + * for applying more general {@link SuperConverter} logic to a specific source/target class pair. + */ +@SuppressWarnings("unchecked") +public class SuperTwoWayConverterConverter implements Converter { + + private SuperTwoWayConverter superConverter; + + private Class sourceClass; + + private Class targetClass; + + public SuperTwoWayConverterConverter(SuperTwoWayConverter superConverter, + Class sourceClass, Class targetClass) { + this.superConverter = superConverter; + this.sourceClass = sourceClass; + this.targetClass = targetClass; + } + + public T convert(S source) throws Exception { + return (T) superConverter.convert(source, targetClass); + } + + public S convertBack(T target) throws Exception { + return (S) superConverter.convertBack(target, sourceClass); + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToArray.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToArray.java index 8737613d40d..072bc5292c4 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToArray.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToArray.java @@ -53,18 +53,7 @@ class ArrayToArray implements SuperConverter { this.elementConverter = elementConverter; } - public Class getSourceClass() { - return Object[].class; - } - - public Class getSuperTargetClass() { - return Object[].class; - } - public Object convert(Object source, Class targetClass) throws Exception { - if (source == null) { - return null; - } Class sourceComponentType = source.getClass().getComponentType(); Class targetComponentType = targetClass.getComponentType(); int length = Array.getLength(source); @@ -77,10 +66,6 @@ class ArrayToArray implements SuperConverter { return targetArray; } - public Object convertBack(Object target) throws Exception { - throw new UnsupportedOperationException("Not supported"); - } - private ConversionExecutor getElementConverter(Class sourceComponentType, Class targetComponentType) { if (elementConverter != null) { return elementConverter; diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToCollection.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToCollection.java index 92404851338..869c8f4b404 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToCollection.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToCollection.java @@ -17,19 +17,13 @@ package org.springframework.core.convert.service; import java.lang.reflect.Array; import java.lang.reflect.Constructor; -import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.convert.ConversionExecutor; import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.converter.SuperConverter; +import org.springframework.core.convert.converter.SuperTwoWayConverter; /** * Special converter that converts from a source array to a target collection. Supports the selection of an @@ -45,7 +39,7 @@ import org.springframework.core.convert.converter.SuperConverter; * @author Keith Donald */ @SuppressWarnings("unchecked") -class ArrayToCollection implements SuperConverter { +class ArrayToCollection implements SuperTwoWayConverter { private ConversionService conversionService; @@ -69,11 +63,8 @@ class ArrayToCollection implements SuperConverter { } public Object convert(Object source, Class targetClass) throws Exception { - if (source == null) { - return null; - } - Class collectionImplClass = getCollectionImplClass(targetClass); - Constructor constructor = collectionImplClass.getConstructor((Class[]) null); + Class implClass = CollectionConversionUtils.getImpl(targetClass); + Constructor constructor = implClass.getConstructor((Class[]) null); Collection collection = (Collection) constructor.newInstance((Object[]) null); ConversionExecutor converter = getArrayElementConverter(source, targetClass); int length = Array.getLength(source); @@ -87,16 +78,10 @@ class ArrayToCollection implements SuperConverter { return collection; } - public Object convertBack(Object target) throws Exception { - throw new UnsupportedOperationException("Should never be called"); - } - public Object convertBack(Object target, Class sourceClass) throws Exception { - if (target == null) { - return null; - } Collection collection = (Collection) target; - Object array = Array.newInstance(sourceClass.getComponentType(), collection.size()); + Class elementType = sourceClass.getComponentType(); + Object array = Array.newInstance(elementType, collection.size()); int i = 0; for (Iterator it = collection.iterator(); it.hasNext(); i++) { Object value = it.next(); @@ -105,8 +90,7 @@ class ArrayToCollection implements SuperConverter { if (elementConverter != null) { converter = elementConverter; } else { - converter = conversionService.getConversionExecutor(value.getClass(), sourceClass - .getComponentType()); + converter = conversionService.getConversionExecutor(value.getClass(), elementType); } value = converter.execute(value); } @@ -115,22 +99,6 @@ class ArrayToCollection implements SuperConverter { return array; } - private Class getCollectionImplClass(Class targetClass) { - if (targetClass.isInterface()) { - if (List.class.equals(targetClass)) { - return ArrayList.class; - } else if (Set.class.equals(targetClass)) { - return LinkedHashSet.class; - } else if (SortedSet.class.equals(targetClass)) { - return TreeSet.class; - } else { - throw new IllegalArgumentException("Unsupported collection interface [" + targetClass.getName() + "]"); - } - } else { - return targetClass; - } - } - private ConversionExecutor getArrayElementConverter(Object source, Class targetClass) { if (elementConverter != null) { return elementConverter; diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionConversionUtils.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionConversionUtils.java new file mode 100644 index 00000000000..10d6805427d --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionConversionUtils.java @@ -0,0 +1,43 @@ +/* + * Copyright 2004-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.service; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +class CollectionConversionUtils { + + static Class getImpl(Class targetClass) { + if (targetClass.isInterface()) { + if (List.class.equals(targetClass)) { + return ArrayList.class; + } else if (Set.class.equals(targetClass)) { + return LinkedHashSet.class; + } else if (SortedSet.class.equals(targetClass)) { + return TreeSet.class; + } else { + throw new IllegalArgumentException("Unsupported collection interface [" + targetClass.getName() + "]"); + } + } else { + return targetClass; + } + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionToCollection.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionToCollection.java index 799987475e0..0effacd8ee3 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionToCollection.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionToCollection.java @@ -15,14 +15,8 @@ */ package org.springframework.core.convert.service; -import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.convert.ConversionExecutor; @@ -35,7 +29,7 @@ import org.springframework.core.convert.converter.SuperConverter; * @author Keith Donald */ @SuppressWarnings("unchecked") -class CollectionToCollection implements SuperConverter { +class CollectionToCollection implements SuperConverter { private ConversionService conversionService; @@ -58,21 +52,10 @@ class CollectionToCollection implements SuperConverter { this.elementConverter = elementConverter; } - public Class getSourceClass() { - return Collection.class; - } - - public Class getSuperTargetClass() { - return Collection.class; - } - - public Object convert(Object source, Class targetClass) throws Exception { - if (source == null) { - return null; - } - Class targetCollectionImpl = getCollectionImplClass(targetClass); - Collection targetCollection = (Collection) targetCollectionImpl.getConstructor((Class[]) null).newInstance( - (Object[]) null); + public Collection convert(Collection source, Class targetClass) throws Exception { + Class implClass = CollectionConversionUtils.getImpl(targetClass); + Collection targetCollection = (Collection) implClass.getConstructor((Class[]) null) + .newInstance((Object[]) null); ConversionExecutor elementConverter = getElementConverter(source, targetClass); Collection sourceCollection = (Collection) source; Iterator it = sourceCollection.iterator(); @@ -86,27 +69,6 @@ class CollectionToCollection implements SuperConverter { return targetCollection; } - public Object convertBack(Object target) throws Exception { - throw new UnsupportedOperationException("Not supported"); - } - - // this code is duplicated in ArrayToCollection.java and ObjectToCollection too - private Class getCollectionImplClass(Class targetClass) { - if (targetClass.isInterface()) { - if (List.class.equals(targetClass)) { - return ArrayList.class; - } else if (Set.class.equals(targetClass)) { - return LinkedHashSet.class; - } else if (SortedSet.class.equals(targetClass)) { - return TreeSet.class; - } else { - throw new IllegalArgumentException("Unsupported collection interface [" + targetClass.getName() + "]"); - } - } else { - return targetClass; - } - } - private ConversionExecutor getElementConverter(Object source, Class targetClass) { if (elementConverter != null) { return elementConverter; diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/DefaultConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/DefaultConversionService.java index 10c6da011d9..220540f4e06 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/DefaultConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/DefaultConversionService.java @@ -35,7 +35,7 @@ import org.springframework.core.convert.converter.StringToLong; import org.springframework.core.convert.converter.StringToShort; /** - * Default, local implementation of a conversion service. Will automatically register from string converters for + * Default implementation of a conversion service. Will automatically register from string converters for * a number of standard Java types like Class, Number, Boolean and so on. * * @author Keith Donald @@ -67,6 +67,7 @@ public class DefaultConversionService extends GenericConversionService { addConverter(new StringToLocale()); addConverter(new StringToEnum()); addConverter(new NumberToNumber()); + // TODO probably don't allow these to be customized, or at least make public addConverter(new ObjectToCollection(this)); addConverter(new CollectionToCollection(this)); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java index 649b7b9205b..62cdd078b94 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java @@ -34,11 +34,14 @@ import org.springframework.core.convert.ConversionExecutorNotFoundException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.SuperConverter; +import org.springframework.core.convert.converter.SuperTwoWayConverter; import org.springframework.util.Assert; /** * Base implementation of a conversion service. Initially empty, e.g. no converters are registered by default. * + * TODO custom converters TODO handle case where no Converter/SuperConverter generic param info is available + * * @author Keith Donald */ @SuppressWarnings("unchecked") @@ -50,6 +53,12 @@ public class GenericConversionService implements ConversionService { */ private final Map sourceClassConverters = new HashMap(); + /** + * An indexed map of Converters. Each Map.Entry key is a source class (S) that can be converted from. Each Map.Entry + * value is a Map that defines the targetClass-to-Converter mappings for that source. + */ + private final Map sourceClassSuperConverters = new HashMap(); + /** * Indexes classes by well-known aliases. */ @@ -95,7 +104,17 @@ public class GenericConversionService implements ConversionService { * @param converter the super converter to register */ public void addConverter(SuperConverter converter) { - // TODO + List typeInfo = getTypeInfo(converter); + Class sourceClass = (Class) typeInfo.get(0); + Class targetClass = (Class) typeInfo.get(1); + // index forward + Map sourceMap = getSourceSuperConverterMap(sourceClass); + sourceMap.put(targetClass, converter); + if (converter instanceof SuperTwoWayConverter) { + // index reverse + sourceMap = getSourceSuperConverterMap(targetClass); + sourceMap.put(sourceClass, new ReverseSuperConverter((SuperTwoWayConverter) converter)); + } } /** @@ -109,22 +128,14 @@ public class GenericConversionService implements ConversionService { public Object executeConversion(Object source, Class targetClass) throws ConversionExecutorNotFoundException, ConversionException { - if (source != null) { - ConversionExecutor conversionExecutor = getConversionExecutor(source.getClass(), targetClass); - return conversionExecutor.execute(source); - } else { - return null; - } + ConversionExecutor conversionExecutor = getConversionExecutor(source.getClass(), targetClass); + return conversionExecutor.execute(source); } public Object executeConversion(String converterId, Object source, Class targetClass) throws ConversionExecutorNotFoundException, ConversionException { - if (source != null) { - ConversionExecutor conversionExecutor = getConversionExecutor(converterId, source.getClass(), targetClass); - return conversionExecutor.execute(source); - } else { - return null; - } + ConversionExecutor conversionExecutor = getConversionExecutor(converterId, source.getClass(), targetClass); + return conversionExecutor.execute(source); } public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) @@ -151,7 +162,8 @@ public class GenericConversionService implements ConversionService { } if (targetClass.isArray()) { if (Collection.class.isAssignableFrom(sourceClass)) { - return new StaticSuperConversionExecutor(sourceClass, targetClass, new CollectionToArray(this)); + SuperConverter collectionToArray = new ReverseSuperConverter(new ArrayToCollection(this)); + return new StaticSuperConversionExecutor(sourceClass, targetClass, collectionToArray); } else { return new StaticSuperConversionExecutor(sourceClass, targetClass, new ObjectToArray(this)); } @@ -161,6 +173,10 @@ public class GenericConversionService implements ConversionService { // we found a converter return new StaticConversionExecutor(sourceClass, targetClass, converter); } else { + SuperConverter superConverter = findRegisteredSuperConverter(sourceClass, targetClass); + if (superConverter != null) { + return new StaticSuperConversionExecutor(sourceClass, targetClass, superConverter); + } if (parent != null) { // try the parent return parent.getConversionExecutor(sourceClass, targetClass); @@ -192,7 +208,7 @@ public class GenericConversionService implements ConversionService { // internal helpers - private List getTypeInfo(Converter converter) { + private List getTypeInfo(Object converter) { List typeInfo = new ArrayList(2); Class classToIntrospect = converter.getClass(); while (classToIntrospect != null) { @@ -200,7 +216,8 @@ public class GenericConversionService implements ConversionService { for (Type genericInterface : genericInterfaces) { if (genericInterface instanceof ParameterizedType) { ParameterizedType parameterizedInterface = (ParameterizedType) genericInterface; - if (Converter.class.equals(parameterizedInterface.getRawType())) { + if (Converter.class.equals(parameterizedInterface.getRawType()) + || SuperConverter.class.isAssignableFrom((Class) parameterizedInterface.getRawType())) { Type s = parameterizedInterface.getActualTypeArguments()[0]; Type t = parameterizedInterface.getActualTypeArguments()[1]; typeInfo.add(getParameterClass(s, converter.getClass())); @@ -213,12 +230,12 @@ public class GenericConversionService implements ConversionService { return typeInfo; } - private Class getParameterClass(Type parameterType, Class converterClass) { + private Class getParameterClass(Type parameterType, Class converterClass) { if (parameterType instanceof TypeVariable) { - parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass); + parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass); } if (parameterType instanceof Class) { - return (Class) parameterType; + return (Class) parameterType; } // when would this happen? return null; @@ -227,12 +244,21 @@ public class GenericConversionService implements ConversionService { private Map getSourceMap(Class sourceClass) { Map sourceMap = (Map) sourceClassConverters.get(sourceClass); if (sourceMap == null) { - sourceMap = new HashMap, Converter>(); + sourceMap = new HashMap(); sourceClassConverters.put(sourceClass, sourceMap); } return sourceMap; } + private Map getSourceSuperConverterMap(Class sourceClass) { + Map sourceMap = (Map) sourceClassSuperConverters.get(sourceClass); + if (sourceMap == null) { + sourceMap = new HashMap(); + sourceClassSuperConverters.put(sourceClass, sourceMap); + } + return sourceMap; + } + private Class convertToWrapperClassIfNecessary(Class targetType) { if (targetType.isPrimitive()) { if (targetType.equals(int.class)) { @@ -265,8 +291,8 @@ public class GenericConversionService implements ConversionService { classQueue.addFirst(sourceClass); while (!classQueue.isEmpty()) { Class currentClass = (Class) classQueue.removeLast(); - Map sourceTargetConverters = findConvertersForSource(currentClass); - Converter converter = findTargetConverter(sourceTargetConverters, targetClass); + Map converters = getConvertersForSource(currentClass); + Converter converter = getConverter(converters, targetClass); if (converter != null) { return converter; } @@ -275,15 +301,15 @@ public class GenericConversionService implements ConversionService { classQueue.addFirst(interfaces[i]); } } - Map objectConverters = findConvertersForSource(Object.class); - return findTargetConverter(objectConverters, targetClass); + Map objectConverters = getConvertersForSource(Object.class); + return getConverter(objectConverters, targetClass); } else { LinkedList classQueue = new LinkedList(); classQueue.addFirst(sourceClass); while (!classQueue.isEmpty()) { Class currentClass = (Class) classQueue.removeLast(); - Map sourceTargetConverters = findConvertersForSource(currentClass); - Converter converter = findTargetConverter(sourceTargetConverters, targetClass); + Map converters = getConvertersForSource(currentClass); + Converter converter = getConverter(converters, targetClass); if (converter != null) { return converter; } @@ -299,15 +325,98 @@ public class GenericConversionService implements ConversionService { } } - private Map findConvertersForSource(Class sourceClass) { - Map sourceConverters = (Map) sourceClassConverters.get(sourceClass); - return sourceConverters != null ? sourceConverters : Collections.emptyMap(); + private Map getConvertersForSource(Class sourceClass) { + Map converters = (Map) sourceClassConverters.get(sourceClass); + return converters != null ? converters : Collections.emptyMap(); } - private Converter findTargetConverter(Map sourceTargetConverters, Class targetClass) { - if (sourceTargetConverters.isEmpty()) { + private Converter getConverter(Map converters, Class targetClass) { + return (Converter) converters.get(targetClass); + } + + private SuperConverter findRegisteredSuperConverter(Class sourceClass, Class targetClass) { + if (sourceClass.isInterface()) { + LinkedList classQueue = new LinkedList(); + classQueue.addFirst(sourceClass); + while (!classQueue.isEmpty()) { + Class currentClass = (Class) classQueue.removeLast(); + Map converters = getSuperConvertersForSource(currentClass); + SuperConverter converter = findSuperConverter(converters, targetClass); + if (converter != null) { + return converter; + } + Class[] interfaces = currentClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + classQueue.addFirst(interfaces[i]); + } + } + Map objectConverters = getSuperConvertersForSource(Object.class); + return findSuperConverter(objectConverters, targetClass); + } else { + LinkedList classQueue = new LinkedList(); + classQueue.addFirst(sourceClass); + while (!classQueue.isEmpty()) { + Class currentClass = (Class) classQueue.removeLast(); + Map converters = getSuperConvertersForSource(currentClass); + SuperConverter converter = findSuperConverter(converters, targetClass); + if (converter != null) { + return converter; + } + if (currentClass.getSuperclass() != null) { + classQueue.addFirst(currentClass.getSuperclass()); + } + Class[] interfaces = currentClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + classQueue.addFirst(interfaces[i]); + } + } return null; } - return (Converter) sourceTargetConverters.get(targetClass); } + + private Map getSuperConvertersForSource(Class sourceClass) { + Map converters = (Map) sourceClassSuperConverters.get(sourceClass); + return converters != null ? converters : Collections.emptyMap(); + } + + private SuperConverter findSuperConverter(Map converters, Class targetClass) { + if (converters.isEmpty()) { + return null; + } + if (targetClass.isInterface()) { + LinkedList classQueue = new LinkedList(); + classQueue.addFirst(targetClass); + while (!classQueue.isEmpty()) { + Class currentClass = (Class) classQueue.removeLast(); + SuperConverter converter = (SuperConverter) converters.get(currentClass); + if (converter != null) { + return converter; + } + Class[] interfaces = currentClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + classQueue.addFirst(interfaces[i]); + } + } + return (SuperConverter) converters.get(Object.class); + } else { + LinkedList classQueue = new LinkedList(); + classQueue.addFirst(targetClass); + while (!classQueue.isEmpty()) { + Class currentClass = (Class) classQueue.removeLast(); + SuperConverter converter = (SuperConverter) converters.get(currentClass); + if (converter != null) { + return converter; + } + if (currentClass.getSuperclass() != null) { + classQueue.addFirst(currentClass.getSuperclass()); + } + Class[] interfaces = currentClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + classQueue.addFirst(interfaces[i]); + } + } + return null; + } + } + } \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java index 777a361aab4..b9f8df87804 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java @@ -17,11 +17,6 @@ package org.springframework.core.convert.service; import org.springframework.core.convert.converter.Converter; -/** - * Package private converter that is a "no op". - * - * @author Keith Donald - */ @SuppressWarnings("unchecked") class NoOpConverter implements Converter { diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToArray.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToArray.java index eb7507ad1b9..897e8ef47c2 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToArray.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToArray.java @@ -22,7 +22,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.SuperConverter; /** - * Special two-way converter that converts an object to an single-element array. Mainly used internally by + * Special converter that converts an object to an single-element array. Mainly used internally by * {@link ConversionService} implementations. * * @author Keith Donald @@ -51,14 +51,6 @@ class ObjectToArray implements SuperConverter { this.elementConverter = elementConverter; } - public Class getSourceClass() { - return Object.class; - } - - public Class getSuperTargetClass() { - return Object[].class; - } - public Object convert(Object source, Class targetClass) throws Exception { Class componentType = targetClass.getComponentType(); Object array = Array.newInstance(componentType, 1); @@ -72,8 +64,4 @@ class ObjectToArray implements SuperConverter { return array; } - public Object convertBack(Object target) throws Exception { - throw new UnsupportedOperationException("Not supported"); - } - } \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToCollection.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToCollection.java index f3169c81514..9620624b2ec 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToCollection.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToCollection.java @@ -16,13 +16,7 @@ package org.springframework.core.convert.service; import java.lang.reflect.Constructor; -import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.convert.ConversionExecutor; @@ -36,7 +30,7 @@ import org.springframework.core.convert.converter.SuperConverter; * @author Keith Donald */ @SuppressWarnings("unchecked") -class ObjectToCollection implements SuperConverter { +class ObjectToCollection implements SuperConverter { private ConversionService conversionService; @@ -59,20 +53,9 @@ class ObjectToCollection implements SuperConverter { this.elementConverter = elementConverter; } - public Class getSourceClass() { - return Object.class; - } - - public Class getSuperTargetClass() { - return Collection.class; - } - - public Object convert(Object source, Class targetClass) throws Exception { - if (source == null) { - return null; - } - Class collectionImplClass = getCollectionImplClass(targetClass); - Constructor constructor = collectionImplClass.getConstructor((Class[]) null); + public Collection convert(Object source, Class targetClass) throws Exception { + Class implClass = CollectionConversionUtils.getImpl(targetClass); + Constructor constructor = implClass.getConstructor((Class[]) null); Collection collection = (Collection) constructor.newInstance((Object[]) null); ConversionExecutor converter = getElementConverter(source, targetClass); Object value; @@ -85,27 +68,6 @@ class ObjectToCollection implements SuperConverter { return collection; } - public Object convertBack(Object target) throws Exception { - throw new UnsupportedOperationException("Not supported"); - } - - // this code is duplicated in ArrayToCollection and CollectionToCollection - private Class getCollectionImplClass(Class targetClass) { - if (targetClass.isInterface()) { - if (List.class.equals(targetClass)) { - return ArrayList.class; - } else if (Set.class.equals(targetClass)) { - return LinkedHashSet.class; - } else if (SortedSet.class.equals(targetClass)) { - return TreeSet.class; - } else { - throw new IllegalArgumentException("Unsupported collection interface [" + targetClass.getName() + "]"); - } - } else { - return targetClass; - } - } - private ConversionExecutor getElementConverter(Object source, Class targetClass) { if (elementConverter != null) { return elementConverter; diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ReverseSuperConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ReverseSuperConverter.java new file mode 100644 index 00000000000..8400ae3c57a --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ReverseSuperConverter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2004-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.service; + +import org.springframework.core.convert.converter.SuperConverter; +import org.springframework.core.convert.converter.SuperTwoWayConverter; + +@SuppressWarnings("unchecked") +class ReverseSuperConverter implements SuperConverter { + + private SuperTwoWayConverter converter; + + public ReverseSuperConverter(SuperTwoWayConverter converter) { + this.converter = converter; + } + + public Object convert(Object source, Class targetClass) throws Exception { + return converter.convertBack(source, targetClass); + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticConversionExecutor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticConversionExecutor.java index a4e41c5998b..ae94394e819 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticConversionExecutor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticConversionExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-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. @@ -21,16 +21,15 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; -@SuppressWarnings("unchecked") -class StaticConversionExecutor implements ConversionExecutor { +class StaticConversionExecutor implements ConversionExecutor { - private final Class sourceClass; + private final Class sourceClass; - private final Class targetClass; + private final Class targetClass; - private final Converter converter; + private final Converter converter; - public StaticConversionExecutor(Class sourceClass, Class targetClass, Converter converter) { + public StaticConversionExecutor(Class sourceClass, Class targetClass, Converter converter) { Assert.notNull(sourceClass, "The source class is required"); Assert.notNull(targetClass, "The target class is required"); Assert.notNull(converter, "The converter is required"); @@ -39,15 +38,15 @@ class StaticConversionExecutor implements ConversionExecutor { this.converter = converter; } - public Class getSourceClass() { + public Class getSourceClass() { return sourceClass; } - public Class getTargetClass() { + public Class getTargetClass() { return targetClass; } - public Object execute(Object source) throws ConversionExecutionException { + public T execute(S source) throws ConversionExecutionException { if (source == null) { return null; } @@ -66,7 +65,7 @@ class StaticConversionExecutor implements ConversionExecutor { if (!(o instanceof StaticConversionExecutor)) { return false; } - StaticConversionExecutor other = (StaticConversionExecutor) o; + StaticConversionExecutor other = (StaticConversionExecutor) o; return sourceClass.equals(other.sourceClass) && targetClass.equals(other.targetClass); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticSuperConversionExecutor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticSuperConversionExecutor.java index 8cb543c3c66..520ce9a48dc 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticSuperConversionExecutor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticSuperConversionExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-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. @@ -21,16 +21,15 @@ import org.springframework.core.convert.converter.SuperConverter; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; -@SuppressWarnings("unchecked") -class StaticSuperConversionExecutor implements ConversionExecutor { +class StaticSuperConversionExecutor implements ConversionExecutor { - private final Class sourceClass; + private final Class sourceClass; - private final Class targetClass; + private final Class targetClass; - private final SuperConverter converter; + private final SuperConverter converter; - public StaticSuperConversionExecutor(Class sourceClass, Class targetClass, SuperConverter converter) { + public StaticSuperConversionExecutor(Class sourceClass, Class targetClass, SuperConverter converter) { Assert.notNull(sourceClass, "The source class is required"); Assert.notNull(targetClass, "The target class is required"); Assert.notNull(converter, "The super converter is required"); @@ -39,15 +38,15 @@ class StaticSuperConversionExecutor implements ConversionExecutor { this.converter = converter; } - public Class getSourceClass() { + public Class getSourceClass() { return sourceClass; } - - public Class getTargetClass() { + + public Class getTargetClass() { return targetClass; } - public Object execute(Object source) throws ConversionExecutionException { + public T execute(S source) throws ConversionExecutionException { if (source == null) { return null; } @@ -66,7 +65,7 @@ class StaticSuperConversionExecutor implements ConversionExecutor { if (!(o instanceof StaticSuperConversionExecutor)) { return false; } - StaticSuperConversionExecutor other = (StaticSuperConversionExecutor) o; + StaticSuperConversionExecutor other = (StaticSuperConversionExecutor) o; return sourceClass.equals(other.sourceClass) && targetClass.equals(other.targetClass); } diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/service/DefaultConversionServiceTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/service/DefaultConversionServiceTests.java deleted file mode 100644 index 96d07d198ce..00000000000 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/service/DefaultConversionServiceTests.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2004-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.service; - -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; - -import junit.framework.TestCase; - -import org.springframework.core.convert.ConversionExecutor; -import org.springframework.core.convert.ConversionExecutorNotFoundException; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.converter.StringToBoolean; - -/** - * Test case for the default conversion service. - * - * @author Keith Donald - */ -public class DefaultConversionServiceTests extends TestCase { - - ConversionService service = new DefaultConversionService(); - - public void testConversionForwardIndex() { - ConversionExecutor executor = service.getConversionExecutor(String.class, Integer.class); - Integer three = executor.execute("3"); - assertEquals(3, three.intValue()); - } - - public void testConversionReverseIndex() { - ConversionExecutor executor = service.getConversionExecutor(Integer.class, String.class); - String threeString = executor.execute(new Integer(3)); - assertEquals("3", threeString); - } - - public void testConversionCompatibleTypes() { - ArrayList source = new ArrayList(); - assertSame(source, service.getConversionExecutor(ArrayList.class, List.class).execute(source)); - } - - public void testConversionOverrideDefaultConverter() { - Converter customConverter = new StringToBoolean("ja", "nee"); - ((GenericConversionService) service).addConverter(customConverter); - ConversionExecutor executor = service.getConversionExecutor(String.class, Boolean.class); - assertTrue(executor.execute("ja").booleanValue()); - } - - public void testConverterLookupTargetClassNotSupported() { - try { - service.getConversionExecutor(String.class, HashMap.class); - fail("Should have thrown an exception"); - } catch (ConversionExecutorNotFoundException e) { - } - } - - public void testConversionToPrimitive() { - DefaultConversionService service = new DefaultConversionService(); - ConversionExecutor executor = service.getConversionExecutor(String.class, int.class); - Integer three = (Integer) executor.execute("3"); - assertEquals(3, three.intValue()); - } - - public void testConversionArrayToArray() { - ConversionExecutor executor = service.getConversionExecutor(String[].class, Integer[].class); - Integer[] result = (Integer[]) executor.execute(new String[] { "1", "2", "3" }); - assertEquals(new Integer(1), result[0]); - assertEquals(new Integer(2), result[1]); - assertEquals(new Integer(3), result[2]); - } - - public void testConversionArrayToPrimitiveArray() { - ConversionExecutor executor = service.getConversionExecutor(String[].class, int[].class); - int[] result = (int[]) executor.execute(new String[] { "1", "2", "3" }); - assertEquals(1, result[0]); - assertEquals(2, result[1]); - assertEquals(3, result[2]); - } - - public void testConversionArrayToList() { - ConversionExecutor executor = service.getConversionExecutor(String[].class, List.class); - List result = (List) executor.execute(new String[] { "1", "2", "3" }); - assertEquals("1", result.get(0)); - assertEquals("2", result.get(1)); - assertEquals("3", result.get(2)); - } - - public void testConversionToArray() { - ConversionExecutor executor = service.getConversionExecutor(Collection.class, String[].class); - List list = new ArrayList(); - list.add("1"); - list.add("2"); - list.add("3"); - String[] result = (String[]) executor.execute(list); - assertEquals("1", result[0]); - assertEquals("2", result[1]); - assertEquals("3", result[2]); - } - - public void testConversionListToArrayWithComponentConversion() { - ConversionExecutor executor = service.getConversionExecutor(Collection.class, Integer[].class); - List list = new ArrayList(); - list.add("1"); - list.add("2"); - list.add("3"); - Integer[] result = (Integer[]) executor.execute(list); - assertEquals(new Integer(1), result[0]); - assertEquals(new Integer(2), result[1]); - assertEquals(new Integer(3), result[2]); - } - - public void testConversionArrayToConcreteList() { - ConversionExecutor executor = service.getConversionExecutor(String[].class, LinkedList.class); - LinkedList result = (LinkedList) executor.execute(new String[] { "1", "2", "3" }); - assertEquals("1", result.get(0)); - assertEquals("2", result.get(1)); - assertEquals("3", result.get(2)); - } - - public void testConversionArrayToAbstractList() { - try { - service.getConversionExecutor(String[].class, AbstractList.class); - } catch (IllegalArgumentException e) { - - } - } - - public void testConversionStringToArray() { - ConversionExecutor executor = service.getConversionExecutor(String.class, String[].class); - String[] result = (String[]) executor.execute("1,2,3"); - assertEquals(1, result.length); - assertEquals("1,2,3", result[0]); - } - - public void testConversionStringToArrayWithElementConversion() { - ConversionExecutor executor = service.getConversionExecutor(String.class, Integer[].class); - Integer[] result = (Integer[]) executor.execute("123"); - assertEquals(1, result.length); - assertEquals(new Integer(123), result[0]); - } - -} \ No newline at end of file diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java new file mode 100644 index 00000000000..e62ecc98a05 --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java @@ -0,0 +1,253 @@ +package org.springframework.core.convert.service; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import junit.framework.TestCase; + +import org.springframework.core.convert.ConversionExecutionException; +import org.springframework.core.convert.ConversionExecutor; +import org.springframework.core.convert.ConversionExecutorNotFoundException; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.NumberToNumber; +import org.springframework.core.convert.converter.StringToEnum; +import org.springframework.core.convert.converter.StringToInteger; +import org.springframework.core.convert.service.GenericConversionService; + +public class GenericConversionServiceTests extends TestCase { + + private GenericConversionService service = new GenericConversionService(); + + public void testExecuteConversion() { + service.addConverter(new StringToInteger()); + assertEquals(new Integer(3), service.executeConversion("3", Integer.class)); + } + + public void testConverterConversionForwardIndex() { + service.addConverter(new StringToInteger()); + ConversionExecutor executor = service.getConversionExecutor(String.class, Integer.class); + Integer three = (Integer) executor.execute("3"); + assertEquals(3, three.intValue()); + } + + public void testConverterConversionReverseIndex() { + service.addConverter(new StringToInteger()); + ConversionExecutor executor = service.getConversionExecutor(Integer.class, String.class); + String threeString = (String) executor.execute(new Integer(3)); + assertEquals("3", threeString); + } + + public void testConversionExecutorNotFound() { + try { + service.getConversionExecutor(String.class, Integer.class); + fail("Should have thrown an exception"); + } catch (ConversionExecutorNotFoundException e) { + } + } + + public void testConversionCompatibleTypes() { + ArrayList source = new ArrayList(); + assertSame(source, service.getConversionExecutor(ArrayList.class, List.class).execute(source)); + } + + public void testConversionExecutorNullArgument() { + service.addConverter(new StringToInteger()); + ConversionExecutor executor = service.getConversionExecutor(String.class, Integer.class); + assertNull(executor.execute(null)); + } + + public void testConversionExecutorWrongTypeArgument() { + service.addConverter(new StringToInteger()); + ConversionExecutor executor = service.getConversionExecutor(Integer.class, String.class); + try { + executor.execute("BOGUS"); + fail("Should have failed"); + } catch (ConversionExecutionException e) { + + } + } + + public void testConverterConversionSuperSourceType() { + service.addConverter(new Converter() { + public Integer convert(CharSequence source) throws Exception { + return Integer.valueOf(source.toString()); + } + + public CharSequence convertBack(Integer target) throws Exception { + return target.toString(); + } + }); + ConversionExecutor executor = service.getConversionExecutor(String.class, Integer.class); + Integer result = (Integer) executor.execute("3"); + assertEquals(new Integer(3), result); + } + + public void testConverterConversionNoSuperTargetType() { + service.addConverter(new Converter() { + public Integer convert(CharSequence source) throws Exception { + return Integer.valueOf(source.toString()); + } + + public CharSequence convertBack(Number target) throws Exception { + return target.toString(); + } + }); + try { + ConversionExecutor executor = service.getConversionExecutor(String.class, Integer.class); + fail("Should have failed"); + } catch (ConversionExecutorNotFoundException e) { + + } + } + + public void testConversionObjectToPrimitive() { + service.addConverter(new StringToInteger()); + ConversionExecutor executor = service.getConversionExecutor(String.class, int.class); + Integer three = (Integer) executor.execute("3"); + assertEquals(3, three.intValue()); + } + + public void testConversionArrayToArray() { + service.addConverter(new StringToInteger()); + ConversionExecutor executor = service.getConversionExecutor(String[].class, Integer[].class); + Integer[] result = (Integer[]) executor.execute(new String[] { "1", "2", "3" }); + assertEquals(new Integer(1), result[0]); + assertEquals(new Integer(2), result[1]); + assertEquals(new Integer(3), result[2]); + } + + public void testConversionArrayToPrimitiveArray() { + service.addConverter(new StringToInteger()); + ConversionExecutor executor = service.getConversionExecutor(String[].class, int[].class); + int[] result = (int[]) executor.execute(new String[] { "1", "2", "3" }); + assertEquals(1, result[0]); + assertEquals(2, result[1]); + assertEquals(3, result[2]); + } + + public void testConversionArrayToListInterface() { + ConversionExecutor executor = service.getConversionExecutor(String[].class, List.class); + List result = (List) executor.execute(new String[] { "1", "2", "3" }); + assertEquals("1", result.get(0)); + assertEquals("2", result.get(1)); + assertEquals("3", result.get(2)); + } + + public void testConversionArrayToListImpl() { + ConversionExecutor executor = service.getConversionExecutor(String[].class, LinkedList.class); + LinkedList result = (LinkedList) executor.execute(new String[] { "1", "2", "3" }); + assertEquals("1", result.get(0)); + assertEquals("2", result.get(1)); + assertEquals("3", result.get(2)); + } + + public void testConversionArrayToAbstractList() { + try { + service.getConversionExecutor(String[].class, AbstractList.class); + } catch (IllegalArgumentException e) { + + } + } + + public void testConversionListToArray() { + ConversionExecutor executor = service.getConversionExecutor(Collection.class, String[].class); + List list = new ArrayList(); + list.add("1"); + list.add("2"); + list.add("3"); + String[] result = (String[]) executor.execute(list); + assertEquals("1", result[0]); + assertEquals("2", result[1]); + assertEquals("3", result[2]); + } + + public void testConversionListToArrayWithComponentConversion() { + service.addConverter(new StringToInteger()); + ConversionExecutor executor = service.getConversionExecutor(Collection.class, Integer[].class); + List list = new ArrayList(); + list.add("1"); + list.add("2"); + list.add("3"); + Integer[] result = (Integer[]) executor.execute(list); + assertEquals(new Integer(1), result[0]); + assertEquals(new Integer(2), result[1]); + assertEquals(new Integer(3), result[2]); + } + + public void testConversionObjectToArray() { + ConversionExecutor executor = service.getConversionExecutor(String.class, String[].class); + String[] result = (String[]) executor.execute("1,2,3"); + assertEquals(1, result.length); + assertEquals("1,2,3", result[0]); + } + + public void testConversionObjectToArrayWithElementConversion() { + service.addConverter(new StringToInteger()); + ConversionExecutor executor = service.getConversionExecutor(String.class, Integer[].class); + Integer[] result = (Integer[]) executor.execute("123"); + assertEquals(1, result.length); + assertEquals(new Integer(123), result[0]); + } + + public static enum FooEnum { + BAR + } + + public void testSuperConverterConversionForwardIndex() { + service.addConverter(new StringToEnum()); + ConversionExecutor executor = service.getConversionExecutor(String.class, FooEnum.class); + assertEquals(FooEnum.BAR, executor.execute("BAR")); + } + + public void testSuperTwoWayConverterConversionReverseIndex() { + service.addConverter(new StringToEnum()); + ConversionExecutor executor = service.getConversionExecutor(FooEnum.class, String.class); + assertEquals("BAR", executor.execute(FooEnum.BAR)); + } + + public void testSuperConverterConversionNotConvertibleAbstractType() { + service.addConverter(new StringToEnum()); + ConversionExecutor executor = service.getConversionExecutor(String.class, Enum.class); + try { + executor.execute("WHATEV"); + fail("Should have failed"); + } catch (ConversionExecutionException e) { + + } + } + + public void testSuperConverterConversionNotConvertibleAbstractType2() { + service.addConverter(new NumberToNumber()); + Number customNumber = new Number() { + @Override + public double doubleValue() { + return 0; + } + + @Override + public float floatValue() { + return 0; + } + + @Override + public int intValue() { + return 0; + } + + @Override + public long longValue() { + return 0; + } + }; + ConversionExecutor executor = service.getConversionExecutor(Integer.class, customNumber.getClass()); + try { + executor.execute(3); + fail("Should have failed"); + } catch (ConversionExecutionException e) { + + } + } +} \ No newline at end of file