diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToArray.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToArray.java index 91219df5..256097c0 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToArray.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToArray.java @@ -36,11 +36,17 @@ public class ArrayToArray implements Converter { /** * Creates a new array-to-array converter. * @param conversionService the service to use to lookup conversion executors for individual array elements + * dynamically */ public ArrayToArray(ConversionService conversionService) { this.conversionService = conversionService; } + /** + * Creates a new array-to-array converter. + * @param elementConverter a specific conversion executor to use to convert elements in the source array to elements + * in the target array. + */ public ArrayToArray(ConversionExecutor elementConverter) { this.elementConverter = elementConverter; } diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToCollection.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToCollection.java index fe0ad93f..4a854f86 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToCollection.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToCollection.java @@ -50,10 +50,19 @@ public class ArrayToCollection implements TwoWayConverter { private ConversionExecutor elementConverter; + /** + * Creates a new array to collection converter. + * @param conversionService the conversion service to use to lookup the converter to apply to array elements added + * to the target collection + */ public ArrayToCollection(ConversionService conversionService) { this.conversionService = conversionService; } + /** + * Creates a new array to collection converter. + * @param elementConverter A specific converter to use on array elements when adding them to the target collection + */ public ArrayToCollection(ConversionExecutor elementConverter) { this.elementConverter = elementConverter; } diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/CollectionToCollection.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/CollectionToCollection.java index 9e0dee0e..ef79f67a 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/converters/CollectionToCollection.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/converters/CollectionToCollection.java @@ -14,16 +14,31 @@ import org.springframework.binding.convert.ConversionService; import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.JdkVersion; +/** + * A converter that can convert from one collection type to another. + * + * @author Keith Donald + */ public class CollectionToCollection implements Converter { private ConversionService conversionService; private ConversionExecutor elementConverter; + /** + * Creates a new collection-to-collection converter + * @param conversionService the conversion service to use to convert collection elements to add to the target + * collection + */ public CollectionToCollection(ConversionService conversionService) { this.conversionService = conversionService; } + /** + * Creates a new collection-to-collection converter + * @param conversionService a specific converter to use to convert collection elements added to the target + * collection + */ public CollectionToCollection(ConversionExecutor elementConverter) { this.elementConverter = elementConverter; } @@ -55,6 +70,7 @@ public class CollectionToCollection implements Converter { return targetCollection; } + // this code is duplicated in ArrayToCollection.java and ObjectToCollection too private Class getCollectionImplClass(Class targetClass) { if (targetClass.isInterface()) { if (List.class.equals(targetClass)) { diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToArray.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToArray.java index 3136f0d4..84bdb8f4 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToArray.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToArray.java @@ -32,10 +32,19 @@ public class ObjectToArray implements Converter { private ConversionExecutor elementConverter; + /** + * Creates a new object to array converter. + * @param conversionService the conversion service to resolve the converter to use to convert the object added to + * the target array. + */ public ObjectToArray(ConversionService conversionService) { this.conversionService = conversionService; } + /** + * Creates a new object to array converter. + * @param elementConverter a specific converter to use to convert the object added to the target array. + */ public ObjectToArray(ConversionExecutor elementConverter) { this.elementConverter = elementConverter; } diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToCollection.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToCollection.java index bb8b7870..26fb183a 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToCollection.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToCollection.java @@ -39,10 +39,16 @@ public class ObjectToCollection implements Converter { private ConversionService conversionService; + private ConversionExecutor elementConverter; + public ObjectToCollection(ConversionService conversionService) { this.conversionService = conversionService; } + public ObjectToCollection(ConversionExecutor elementConverter) { + this.elementConverter = elementConverter; + } + public Class getSourceClass() { return Object.class; } @@ -69,6 +75,7 @@ public class ObjectToCollection implements Converter { return collection; } + // this code is duplicated in ArrayToCollection and CollectionToCollection private Class getCollectionImplClass(Class targetClass) { if (targetClass.isInterface()) { if (List.class.equals(targetClass)) { @@ -86,13 +93,17 @@ public class ObjectToCollection implements Converter { } private ConversionExecutor getElementConverter(Object source, Class targetClass) { - if (JdkVersion.isAtLeastJava15()) { - Class elementType = GenericCollectionTypeResolver.getCollectionType(targetClass); - if (elementType != null) { - Class componentType = source.getClass().getComponentType(); - return conversionService.getConversionExecutor(componentType, elementType); + if (elementConverter != null) { + return elementConverter; + } else { + if (JdkVersion.isAtLeastJava15()) { + Class elementType = GenericCollectionTypeResolver.getCollectionType(targetClass); + if (elementType != null) { + Class componentType = source.getClass().getComponentType(); + return conversionService.getConversionExecutor(componentType, elementType); + } } + return null; } - return null; } } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java index eb4e337e..d220000c 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java @@ -26,7 +26,6 @@ import java.util.Map; import java.util.Set; import org.springframework.binding.convert.ConversionException; -import org.springframework.binding.convert.ConversionExecutionException; import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.ConversionExecutorNotFoundException; import org.springframework.binding.convert.ConversionService; @@ -35,6 +34,7 @@ import org.springframework.binding.convert.converters.ArrayToCollection; import org.springframework.binding.convert.converters.CollectionToCollection; import org.springframework.binding.convert.converters.Converter; import org.springframework.binding.convert.converters.ObjectToArray; +import org.springframework.binding.convert.converters.ObjectToCollection; import org.springframework.binding.convert.converters.ReverseConverter; import org.springframework.binding.convert.converters.TwoWayConverter; import org.springframework.util.Assert; @@ -194,9 +194,8 @@ public class GenericConversionService implements ConversionService { if (!converter.getTargetClass().isAssignableFrom(targetComponentType)) { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id - + "' cannot convert from array storing elements of type [" - + sourceComponentType.getName() - + "]; to an array of storing elements of type [" + + "' cannot convert from an array storing elements of type [" + + sourceComponentType.getName() + "] to an array of storing elements of type [" + targetComponentType.getName() + "]"); } ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType, @@ -210,8 +209,9 @@ public class GenericConversionService implements ConversionService { return new StaticConversionExecutor(sourceClass, targetClass, new ArrayToArray(elementConverter)); } else { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, - "Custom ConversionExecutor with id '" + id + "' cannot convert from array of type [" - + sourceComponentType.getName() + "]; to an array of type [" + "Custom ConversionExecutor with id '" + id + + "' cannot convert from an array storing elements of type [" + + sourceComponentType.getName() + "] to an array storing elements of type [" + targetComponentType.getName() + "]"); } } else if (Collection.class.isAssignableFrom(targetClass)) { @@ -236,8 +236,9 @@ public class GenericConversionService implements ConversionService { } else { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id - + "' cannot convert from array storing elements type [" - + sourceComponentType.getName() + "]; to collection"); + + "' cannot convert from array an storing elements type [" + + sourceComponentType.getName() + "] to a collection of type [" + + targetClass.getName() + "]"); } } } @@ -259,8 +260,8 @@ public class GenericConversionService implements ConversionService { return new StaticConversionExecutor(sourceClass, targetClass, collectionToArray); } else { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, - "Custom ConversionExecutor with id '" + id - + "' cannot convert from collection to to an array holding elements of type [" + "Custom ConversionExecutor with id '" + id + "' cannot convert from collection of type [" + + sourceClass.getName() + "] to an array storing elements of type [" + targetComponentType.getName() + "]"); } } else { @@ -289,17 +290,30 @@ public class GenericConversionService implements ConversionService { } } } - if (Collection.class.isAssignableFrom(sourceClass) && Collection.class.isAssignableFrom(targetClass)) { - ConversionExecutor elementConverter; - // type erasure forces us to do runtime checks of list elements - if (converter instanceof TwoWayConverter) { - elementConverter = new TwoWayCapableConversionExecutor(converter.getSourceClass(), converter - .getTargetClass(), (TwoWayConverter) converter); + if (Collection.class.isAssignableFrom(targetClass)) { + if (Collection.class.isAssignableFrom(sourceClass)) { + ConversionExecutor elementConverter; + // type erasure forces us to do runtime checks of list elements + if (converter instanceof TwoWayConverter) { + elementConverter = new TwoWayCapableConversionExecutor(converter.getSourceClass(), converter + .getTargetClass(), (TwoWayConverter) converter); + } else { + elementConverter = new StaticConversionExecutor(converter.getSourceClass(), converter + .getTargetClass(), converter); + } + return new StaticConversionExecutor(sourceClass, targetClass, new CollectionToCollection( + elementConverter)); } else { - elementConverter = new StaticConversionExecutor(converter.getSourceClass(), converter.getTargetClass(), - converter); + ConversionExecutor elementConverter; + // type erasure forces us to do runtime checks of list elements + if (converter instanceof TwoWayConverter) { + elementConverter = new TwoWayCapableConversionExecutor(sourceClass, converter.getTargetClass(), + (TwoWayConverter) converter); + } else { + elementConverter = new StaticConversionExecutor(sourceClass, converter.getTargetClass(), converter); + } + return new StaticConversionExecutor(sourceClass, targetClass, new ObjectToCollection(elementConverter)); } - return new StaticConversionExecutor(sourceClass, targetClass, new CollectionToCollection(elementConverter)); } if (converter.getSourceClass().isAssignableFrom(sourceClass)) { if (!converter.getTargetClass().isAssignableFrom(targetClass)) { @@ -501,47 +515,4 @@ public class GenericConversionService implements ConversionService { return targetType; } } - - private static class TwoWayCapableConversionExecutor implements ConversionExecutor { - - private Class sourceClass; - - private Class targetClass; - - private TwoWayConverter converter; - - public TwoWayCapableConversionExecutor(Class sourceClass, Class targetClass, TwoWayConverter converter) { - this.sourceClass = sourceClass; - this.targetClass = targetClass; - this.converter = converter; - } - - public Class getSourceClass() { - return sourceClass; - } - - public Class getTargetClass() { - return targetClass; - } - - public Object execute(Object source) throws ConversionExecutionException { - if (source == null || getSourceClass().isInstance(source)) { - try { - return converter.convertSourceToTargetClass(source, targetClass); - } catch (Exception e) { - throw new ConversionExecutionException(source, getSourceClass(), getTargetClass(), e); - } - } else if (getTargetClass().isInstance(source)) { - try { - return converter.convertTargetToSourceClass(source, sourceClass); - } catch (Exception e) { - throw new ConversionExecutionException(source, getTargetClass(), getSourceClass(), e); - } - } else { - throw new ConversionExecutionException(source, getSourceClass(), getTargetClass(), "Source object " - + source + " to convert is expected to be an instance of [" + getSourceClass().getName() - + "] or [" + getTargetClass().getName() + "]"); - } - } - } } \ No newline at end of file diff --git a/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java b/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java index 019076e6..52db6d71 100644 --- a/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java @@ -197,6 +197,16 @@ public class DefaultConversionServiceTests extends TestCase { assertEquals("princy2", p[1]); } + public void testRegisterCustomConverterArrayToArrayBogus() { + DefaultConversionService service = new DefaultConversionService(); + service.addConverter("princy", new CustomTwoWayConverter()); + try { + service.getConversionExecutor("princy", Integer[].class, Principal[].class); + fail("Should have failed"); + } catch (ConversionExecutorNotFoundException e) { + } + } + public void testRegisterCustomConverterArrayToList() { DefaultConversionService service = new DefaultConversionService(); service.addConverter("princy", new CustomTwoWayConverter()); @@ -225,6 +235,17 @@ public class DefaultConversionServiceTests extends TestCase { assertEquals("princy2", p.get(1)); } + public void testRegisterCustomConverterArrayToListBogus() { + DefaultConversionService service = new DefaultConversionService(); + service.addConverter("princy", new CustomTwoWayConverter()); + try { + service.getConversionExecutor("princy", Integer[].class, List.class); + fail("Should have failed"); + } catch (ConversionExecutorNotFoundException e) { + + } + } + public void testRegisterCustomConverterListToArray() { DefaultConversionService service = new DefaultConversionService(); service.addConverter("princy", new CustomTwoWayConverter()); @@ -259,6 +280,17 @@ public class DefaultConversionServiceTests extends TestCase { assertEquals("princy2", p[1]); } + public void testRegisterCustomConverterListToArrayBogus() { + DefaultConversionService service = new DefaultConversionService(); + service.addConverter("princy", new CustomTwoWayConverter()); + try { + service.getConversionExecutor("princy", List.class, Integer[].class); + fail("Should have failed"); + } catch (ConversionExecutorNotFoundException e) { + + } + } + public void testRegisterCustomConverterObjectToArray() { DefaultConversionService service = new DefaultConversionService(); service.addConverter("princy", new CustomTwoWayConverter()); @@ -280,6 +312,50 @@ public class DefaultConversionServiceTests extends TestCase { assertEquals("princy1", p[0]); } + public void testRegisterCustomConverterObjectToArrayBogus() { + DefaultConversionService service = new DefaultConversionService(); + service.addConverter("princy", new CustomTwoWayConverter()); + try { + ConversionExecutor executor = service.getConversionExecutor("princy", Integer.class, Principal[].class); + fail("Should have failed"); + } catch (ConversionExecutorNotFoundException e) { + + } + } + + public void testRegisterCustomConverterObjectToList() { + DefaultConversionService service = new DefaultConversionService(); + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", String.class, List.class); + List list = (List) executor.execute("princy1"); + assertEquals("princy1", ((Principal) list.get(0)).getName()); + } + + public void testRegisterCustomConverterObjectToListBogus() { + DefaultConversionService service = new DefaultConversionService(); + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", Integer.class, List.class); + try { + List list = (List) executor.execute(new Integer(1)); + fail("Should have failed"); + } catch (ConversionExecutionException e) { + + } + } + + public void testRegisterCustomConverterObjectToListReverse() { + DefaultConversionService service = new DefaultConversionService(); + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", Principal.class, List.class); + final Principal princy1 = new Principal() { + public String getName() { + return "princy1"; + } + }; + List list = (List) executor.execute(princy1); + assertEquals("princy1", list.get(0)); + } + public void testRegisterCustomConverterListToList() { DefaultConversionService service = new DefaultConversionService(); service.addConverter("princy", new CustomTwoWayConverter()); @@ -314,6 +390,20 @@ public class DefaultConversionServiceTests extends TestCase { assertEquals("princy2", list.get(1)); } + public void testRegisterCustomConverterListToListBogus() { + DefaultConversionService service = new DefaultConversionService(); + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", List.class, List.class); + List princyList = new ArrayList(); + princyList.add(new Integer(1)); + try { + List list = (List) executor.execute(princyList); + fail("Should have failed"); + } catch (ConversionExecutionException e) { + + } + } + public void testConversionPrimitive() { DefaultConversionService service = new DefaultConversionService(); ConversionExecutor executor = service.getConversionExecutor(String.class, int.class);