From a2f00f205a0b54c870a2f93684fa5fb30e0d2935 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 20 Jul 2011 19:57:40 +0000 Subject: [PATCH] collection/array conversion returns original collection if possible (SPR-8538); backported smarter element conversion check from 3.1 --- .../support/ArrayToArrayConverter.java | 6 +-- .../support/ArrayToCollectionConverter.java | 2 +- .../support/ArrayToObjectConverter.java | 26 +++++---- .../support/CollectionToArrayConverter.java | 4 +- .../CollectionToCollectionConverter.java | 4 +- .../support/CollectionToObjectConverter.java | 9 ++-- .../support/CollectionToStringConverter.java | 15 +++--- .../core/convert/support/ConversionUtils.java | 42 ++++++++------- .../support/ObjectToArrayConverter.java | 6 +-- .../support/ObjectToCollectionConverter.java | 4 +- .../support/StringToBooleanConverter.java | 17 +++--- .../support/DefaultConversionTests.java | 53 +++++++++++++++++-- 12 files changed, 123 insertions(+), 65 deletions(-) diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java index 70eabb2a6e..e487afd035 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import java.util.Set; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.util.ObjectUtils; /** @@ -32,7 +32,7 @@ import org.springframework.util.ObjectUtils; * @author Keith Donald * @since 3.0 */ -final class ArrayToArrayConverter implements GenericConverter { +final class ArrayToArrayConverter implements ConditionalGenericConverter { private final CollectionToArrayConverter helperConverter; diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java index 5608a4ed57..27203af2e4 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java @@ -49,7 +49,7 @@ final class ArrayToCollectionConverter implements ConditionalGenericConverter { } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor()); + return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService); } @SuppressWarnings("unchecked") diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java index 404218ef54..6080db747e 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,40 +16,48 @@ package org.springframework.core.convert.support; -import java.util.Arrays; +import java.lang.reflect.Array; import java.util.Collections; import java.util.Set; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; -import org.springframework.util.ObjectUtils; /** * Converts an Array to an Object by returning the first array element after converting it to the desired targetType. - * This implementation first adapts the source Array to a List, then delegates to {@link CollectionToObjectConverter} to perform the target Object conversion. * * @author Keith Donald * @since 3.0 */ final class ArrayToObjectConverter implements ConditionalGenericConverter { - private final CollectionToObjectConverter helperConverter; + private final ConversionService conversionService; public ArrayToObjectConverter(ConversionService conversionService) { - this.helperConverter = new CollectionToObjectConverter(conversionService); + this.conversionService = conversionService; } public Set getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object[].class, Object.class)); } - + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.helperConverter.matches(sourceType, targetType); + return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService); } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType); + if (source == null) { + return null; + } + if (sourceType.isAssignableTo(targetType)) { + return source; + } + if (Array.getLength(source) == 0) { + return null; + } + Object firstElement = Array.get(source, 0); + return this.conversionService.convert(firstElement, sourceType.getElementTypeDescriptor(firstElement), targetType); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java index e4d70903e3..6b8ef7a24b 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,7 @@ final class CollectionToArrayConverter implements ConditionalGenericConverter { } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor()); + return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService); } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java index 3e1aeefb76..efc89e852a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java @@ -49,9 +49,9 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor()); + return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), conversionService); } - + @SuppressWarnings("unchecked") public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java index 9f1ac3b3ce..c5c2258bef 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,13 +43,16 @@ final class CollectionToObjectConverter implements ConditionalGenericConverter { } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType); + return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService); } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } + if (sourceType.isAssignableTo(targetType)) { + return source; + } Collection sourceCollection = (Collection) source; if (sourceCollection.size() == 0) { return null; @@ -58,4 +61,4 @@ final class CollectionToObjectConverter implements ConditionalGenericConverter { return this.conversionService.convert(firstElement, sourceType.getElementTypeDescriptor(firstElement), targetType); } -} \ No newline at end of file +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java index d5ca3c8da9..77cb6a0285 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ final class CollectionToStringConverter implements ConditionalGenericConverter { } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType); + return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService); } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { @@ -56,18 +56,17 @@ final class CollectionToStringConverter implements ConditionalGenericConverter { if (sourceCollection.size() == 0) { return ""; } - StringBuilder string = new StringBuilder(); + StringBuilder sb = new StringBuilder(); int i = 0; for (Object sourceElement : sourceCollection) { if (i > 0) { - string.append(DELIMITER); + sb.append(DELIMITER); } - Object targetElement = this.conversionService.convert( - sourceElement, sourceType.getElementTypeDescriptor(sourceElement), targetType); - string.append(targetElement); + Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(sourceElement), targetType); + sb.append(targetElement); i++; } - return string.toString(); + return sb.toString(); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java index 88731032e4..c4d41ec351 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,8 @@ package org.springframework.core.convert.support; -import java.util.Map; - import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.GenericConverter; @@ -43,24 +42,27 @@ abstract class ConversionUtils { } } - public static TypeDescriptor[] getMapEntryTypes(Map sourceMap) { - Class keyType = null; - Class valueType = null; - for (Object entry : sourceMap.entrySet()) { - Map.Entry mapEntry = (Map.Entry) entry; - Object key = mapEntry.getKey(); - if (keyType == null && key != null) { - keyType = key.getClass(); - } - Object value = mapEntry.getValue(); - if (valueType == null && value != null) { - valueType = value.getClass(); - } - if (keyType!= null && valueType != null) { - break; - } + public static boolean canConvertElements(TypeDescriptor sourceElementType, TypeDescriptor targetElementType, ConversionService conversionService) { + if (targetElementType == null) { + // yes + return true; + } + if (sourceElementType == null) { + // maybe + return true; + } + if (conversionService.canConvert(sourceElementType, targetElementType)) { + // yes + return true; + } + else if (sourceElementType.getType().isAssignableFrom(targetElementType.getType())) { + // maybe; + return true; + } + else { + // no; + return false; } - return new TypeDescriptor[] { TypeDescriptor.valueOf(keyType), TypeDescriptor.valueOf(valueType) }; } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java index 83428f504c..e462c6b6c7 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,7 @@ final class ObjectToArrayConverter implements ConditionalGenericConverter { } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()); + return ConversionUtils.canConvertElements(sourceType, targetType.getElementTypeDescriptor(), this.conversionService); } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { @@ -57,4 +57,4 @@ final class ObjectToArrayConverter implements ConditionalGenericConverter { return target; } -} \ No newline at end of file +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java index 6e9bdd94bc..db78b0660e 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ final class ObjectToCollectionConverter implements ConditionalGenericConverter { } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()); + return ConversionUtils.canConvertElements(sourceType, targetType.getElementTypeDescriptor(), this.conversionService); } @SuppressWarnings("unchecked") diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java index 36e3090f88..7708a91e52 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,24 +36,23 @@ final class StringToBooleanConverter implements Converter { static { trueValues.add("true"); - falseValues.add("false"); - trueValues.add("on"); - falseValues.add("off"); - trueValues.add("yes"); - falseValues.add("no"); - trueValues.add("1"); + + falseValues.add("false"); + falseValues.add("off"); + falseValues.add("no"); falseValues.add("0"); } public Boolean convert(String source) { String value = source.trim(); - if (value.length() == 0) { + if ("".equals(value)) { return null; } - else if (trueValues.contains(value)) { + value = value.toLowerCase(); + if (trueValues.contains(value)) { return Boolean.TRUE; } else if (falseValues.contains(value)) { diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java index 94bdb79b34..6e7189bd27 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.core.convert.support; +import java.awt.Color; import java.math.BigDecimal; import java.math.BigInteger; import java.util.AbstractList; @@ -33,13 +34,17 @@ import java.util.Map; import java.util.Properties; import java.util.Set; -import static org.junit.Assert.*; import org.junit.Test; +import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterRegistry; + +import static org.junit.Assert.*; /** * @author Keith Donald @@ -75,6 +80,9 @@ public class DefaultConversionTests { assertEquals(Boolean.valueOf(true), conversionService.convert("on", Boolean.class)); assertEquals(Boolean.valueOf(true), conversionService.convert("yes", Boolean.class)); assertEquals(Boolean.valueOf(true), conversionService.convert("1", Boolean.class)); + assertEquals(Boolean.valueOf(true), conversionService.convert("TRUE", Boolean.class)); + assertEquals(Boolean.valueOf(true), conversionService.convert("ON", Boolean.class)); + assertEquals(Boolean.valueOf(true), conversionService.convert("YES", Boolean.class)); } @Test @@ -83,6 +91,9 @@ public class DefaultConversionTests { assertEquals(Boolean.valueOf(false), conversionService.convert("off", Boolean.class)); assertEquals(Boolean.valueOf(false), conversionService.convert("no", Boolean.class)); assertEquals(Boolean.valueOf(false), conversionService.convert("0", Boolean.class)); + assertEquals(Boolean.valueOf(false), conversionService.convert("FALSE", Boolean.class)); + assertEquals(Boolean.valueOf(false), conversionService.convert("OFF", Boolean.class)); + assertEquals(Boolean.valueOf(false), conversionService.convert("NO", Boolean.class)); } @Test @@ -286,6 +297,24 @@ public class DefaultConversionTests { assertEquals(new Integer("3"), result.get(2)); } + @Test + public void testSpr7766() throws Exception { + ConverterRegistry registry = ((ConverterRegistry) conversionService); + registry.addConverter(new ColorConverter()); + List colors = (List) conversionService.convert(new String[] { "ffffff", "#000000" }, TypeDescriptor.valueOf(String[].class), new TypeDescriptor(new MethodParameter(getClass().getMethod("handlerMethod", List.class), 0))); + assertEquals(2, colors.size()); + assertEquals(Color.WHITE, colors.get(0)); + assertEquals(Color.BLACK, colors.get(1)); + } + + public class ColorConverter implements Converter { + public Color convert(String source) { if (!source.startsWith("#")) source = "#" + source; return Color.decode(source); } + } + + public void handlerMethod(List color) { + + } + @Test public void convertArrayToCollectionImpl() { LinkedList result = conversionService.convert(new String[] { "1", "2", "3" }, LinkedList.class); @@ -357,7 +386,7 @@ public class DefaultConversionTests { @Test public void convertArrayToObject() { Object[] array = new Object[] { 3L }; - Object result = conversionService.convert(array, Object.class); + Object result = conversionService.convert(array, Long.class); assertEquals(3L, result); } @@ -368,6 +397,13 @@ public class DefaultConversionTests { assertEquals(new Integer(3), result); } + @Test + public void convertArrayToObjectAssignableTargetType() { + Long[] array = new Long[] { 3L }; + Long[] result = (Long[]) conversionService.convert(array, Object.class); + assertEquals(array, result); + } + @Test public void convertObjectToArray() { Object[] result = conversionService.convert(3L, Object[].class); @@ -460,6 +496,14 @@ public class DefaultConversionTests { assertEquals(new Integer(3), result); } + @Test + public void convertCollectionToObjectAssignableTarget() throws Exception { + Collection source = new ArrayList(); + source.add("foo"); + Object result = conversionService.convert(source, TypeDescriptor.forObject(source), new TypeDescriptor(getClass().getField("assignableTarget"))); + assertEquals(source, result); + } + @Test public void convertObjectToCollection() { List result = (List) conversionService.convert(3L, List.class); @@ -620,6 +664,8 @@ public class DefaultConversionTests { conversionService.convert(new Long(3), SSN.class); } + public Object assignableTarget; + private static class SSN { private String value; @@ -708,4 +754,5 @@ public class DefaultConversionTests { return new TestEntity(id); } } + }