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 5767534a133..abd323cdd92 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 @@ -27,7 +27,12 @@ public interface ConversionService { /** * Returns true if objects of sourceType can be converted to targetType. - * @param sourceType the source type to convert from (required) + * If this method returns true, it means {@link #convert(Object, Class)} is capable of converting an instance of sourceType to targetType. + * Special note on collections, arrays, and maps types: + * For conversion between collection, array, and map types, this method will return 'true' + * even though a convert invocation may still generate a {@link ConversionException} if the underlying elements are not convertible. + * Callers are expected to handle this exceptional case when working with collections and maps. + * @param sourceType the source type to convert from (may be null if source is null) * @param targetType the target type to convert to (required) * @return true if a conversion can be performed, false if not * @throws IllegalArgumentException if targetType is null @@ -36,12 +41,16 @@ public interface ConversionService { /** * Returns true if objects of sourceType can be converted to the targetType. - * The TypeDescriptors provide additional context about the source and target locations where conversion would occur, often object property locations. - * @param sourceType context about the source type to convert from (required) + * The TypeDescriptors provide additional context about the source and target locations where conversion would occur, often object fields or property locations. + * If this method returns true, it means {@link #convert(Object, TypeDescriptor, TypeDescriptor)} is capable of converting an instance of sourceType to targetType. + * Special note on collections, arrays, and maps types: + * For conversion between collection, array, and map types, this method will return 'true' + * even though a convert invocation may still generate a {@link ConversionException} if the underlying elements are not convertible. + * Callers are expected to handle this exceptional case when working with collections and maps. + * @param sourceType context about the source type to convert from (may be null if source is null) * @param targetType context about the target type to convert to (required) * @return true if a conversion can be performed between the source and target types, false if not * @throws IllegalArgumentException if targetType is null - * @see TypeDescriptor#forObject(Object) */ boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); @@ -57,7 +66,7 @@ public interface ConversionService { /** * Convert the source to targetType. - * The TypeDescriptors provide additional context about the source and target locations where conversion will occur, often object property locations. + * The TypeDescriptors provide additional context about the source and target locations where conversion will occur, often object fields or property locations. * @param source the source object to convert (may be null) * @param sourceType context about the source type converting from (may be null if source is null) * @param targetType context about the target type to convert to (required) @@ -65,7 +74,6 @@ public interface ConversionService { * @throws ConversionException if a conversion exception occurred * @throws IllegalArgumentException if targetType is null * @throws IllegalArgumentException if sourceType is null but source is not null - * @see TypeDescriptor#forObject(Object) */ Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java index c2323901588..3ee76bf1b83 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java @@ -22,16 +22,17 @@ 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.StringUtils; /** * Converts a comma-delimited String to an Array. - * + * Only matches if String.class can be converted to the target array element type. + * * @author Keith Donald * @since 3.0 */ -final class StringToArrayConverter implements GenericConverter { +final class StringToArrayConverter implements ConditionalGenericConverter { private final ConversionService conversionService; @@ -43,6 +44,10 @@ final class StringToArrayConverter implements GenericConverter { return Collections.singleton(new ConvertiblePair(String.class, Object[].class)); } + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + return this.conversionService.canConvert(sourceType, targetType.getElementType()); + } + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java index fb1cf034e21..1fceba080d5 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java @@ -28,6 +28,7 @@ import org.springframework.util.StringUtils; /** * Converts a comma-delimited String to a Collection. + * If the target collection element type is declared, only matches if String.class can be converted to it. * * @author Keith Donald * @since 3.0 @@ -45,10 +46,11 @@ final class StringToCollectionConverter implements ConditionalGenericConverter { } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - if (targetType.getElementType() == null) { + if (targetType.getElementType() != null) { + return this.conversionService.canConvert(sourceType, targetType.getElementType()); + } else { return true; } - return this.conversionService.canConvert(sourceType, targetType.getElementType()); } @SuppressWarnings("unchecked") 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 db9ce5a47f1..45016b5d003 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 @@ -759,4 +759,5 @@ public class DefaultConversionTests { return new TestEntity(id); } } + } diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index 4eb186f93fe..9cc6cea23b9 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java @@ -16,16 +16,17 @@ package org.springframework.core.convert.support; +import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; @@ -528,4 +529,25 @@ public class GenericConversionServiceTests { public static Map wildcardMap; + @Test + public void stringToArrayCanConvert() { + conversionService.addConverter(new StringToArrayConverter(conversionService)); + assertFalse(conversionService.canConvert(String.class, Integer[].class)); + conversionService.addConverterFactory(new StringToNumberConverterFactory()); + assertTrue(conversionService.canConvert(String.class, Integer[].class)); + } + + @Test + public void stringToCollectionCanConvert() throws Exception { + conversionService.addConverter(new StringToCollectionConverter(conversionService)); + assertTrue(conversionService.canConvert(String.class, Collection.class)); + TypeDescriptor targetType = new TypeDescriptor(getClass().getField("stringToCollection")); + assertFalse(conversionService.canConvert(TypeDescriptor.valueOf(String.class), targetType)); + conversionService.addConverterFactory(new StringToNumberConverterFactory()); + assertTrue(conversionService.canConvert(TypeDescriptor.valueOf(String.class), targetType)); + } + + public Collection stringToCollection; + + }