collection/array conversion returns original collection if possible (SPR-8538); backported smarter element conversion check from 3.1

This commit is contained in:
Juergen Hoeller
2011-07-20 19:57:40 +00:00
parent 340a8b28f9
commit a2f00f205a
12 changed files with 123 additions and 65 deletions

View File

@@ -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;

View File

@@ -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")

View File

@@ -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<ConvertiblePair> 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);
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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) };
}
}

View File

@@ -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;
}
}
}

View File

@@ -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")

View File

@@ -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<String, Boolean> {
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)) {

View File

@@ -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<Color> colors = (List<Color>) 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<String, Color> {
public Color convert(String source) { if (!source.startsWith("#")) source = "#" + source; return Color.decode(source); }
}
public void handlerMethod(List<Color> 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<String> source = new ArrayList<String>();
source.add("foo");
Object result = conversionService.convert(source, TypeDescriptor.forObject(source), new TypeDescriptor(getClass().getField("assignableTarget")));
assertEquals(source, result);
}
@Test
public void convertObjectToCollection() {
List<String> result = (List<String>) 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);
}
}
}