string to collection and array converters now are conditional and apply target element type match

This commit is contained in:
Keith Donald 2011-06-06 22:48:00 +00:00
parent 38041acc87
commit a1a7c32052
5 changed files with 50 additions and 12 deletions

View File

@ -27,7 +27,12 @@ public interface ConversionService {
/** /**
* Returns true if objects of sourceType can be converted to targetType. * 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) * @param targetType the target type to convert to (required)
* @return true if a conversion can be performed, false if not * @return true if a conversion can be performed, false if not
* @throws IllegalArgumentException if targetType is null * @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. * 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. * The TypeDescriptors provide additional context about the source and target locations where conversion would occur, often object fields or property locations.
* @param sourceType context about the source type to convert from (required) * 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) * @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 * @return true if a conversion can be performed between the source and target types, false if not
* @throws IllegalArgumentException if targetType is null * @throws IllegalArgumentException if targetType is null
* @see TypeDescriptor#forObject(Object)
*/ */
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
@ -57,7 +66,7 @@ public interface ConversionService {
/** /**
* Convert the source to targetType. * 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 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 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) * @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 ConversionException if a conversion exception occurred
* @throws IllegalArgumentException if targetType is null * @throws IllegalArgumentException if targetType is null
* @throws IllegalArgumentException if sourceType is null but source is not null * @throws IllegalArgumentException if sourceType is null but source is not null
* @see TypeDescriptor#forObject(Object)
*/ */
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

View File

@ -22,16 +22,17 @@ import java.util.Set;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor; 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; import org.springframework.util.StringUtils;
/** /**
* Converts a comma-delimited String to an Array. * 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 * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
final class StringToArrayConverter implements GenericConverter { final class StringToArrayConverter implements ConditionalGenericConverter {
private final ConversionService conversionService; private final ConversionService conversionService;
@ -43,6 +44,10 @@ final class StringToArrayConverter implements GenericConverter {
return Collections.singleton(new ConvertiblePair(String.class, Object[].class)); 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) { public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) { if (source == null) {
return null; return null;

View File

@ -28,6 +28,7 @@ import org.springframework.util.StringUtils;
/** /**
* Converts a comma-delimited String to a Collection. * 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 * @author Keith Donald
* @since 3.0 * @since 3.0
@ -45,10 +46,11 @@ final class StringToCollectionConverter implements ConditionalGenericConverter {
} }
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { 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 true;
} }
return this.conversionService.canConvert(sourceType, targetType.getElementType());
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -759,4 +759,5 @@ public class DefaultConversionTests {
return new TestEntity(id); return new TestEntity(id);
} }
} }
} }

View File

@ -16,16 +16,17 @@
package org.springframework.core.convert.support; package org.springframework.core.convert.support;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
@ -528,4 +529,25 @@ public class GenericConversionServiceTests {
public static Map<String, ?> wildcardMap; public static Map<String, ?> 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<Integer> stringToCollection;
} }