Browse Source

ConversionService is able to deal with empty collections and nested collections (fixed regression; SPR-7289, SPR-7293); ConversionService properly handles nested Resource arrays in Map values (fixed regression; SPR-7295); ConversionService does not accidentally use copy constructor for same type (SPR-7304)

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@3447 50f2f4bb-b051-0410-bef5-90022cba6387
pull/1/head
Juergen Hoeller 16 years ago
parent
commit
7fb245167e
  1. 40
      org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
  2. 9
      org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java
  3. 5
      org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java
  4. 12
      org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java
  5. 3
      org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
  6. 13
      org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java
  7. 6
      org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java
  8. 63
      org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java

40
org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

@ -39,9 +39,12 @@ import org.springframework.util.ObjectUtils;
*/ */
public class TypeDescriptor { public class TypeDescriptor {
/** Constant defining an 'unknown type' TypeDescriptor */ /** Constant defining a TypeDescriptor for a <code>null</code> value */
public static final TypeDescriptor NULL = new TypeDescriptor(); public static final TypeDescriptor NULL = new TypeDescriptor();
/** Constant defining a TypeDescriptor for 'unknown type' */
public static final TypeDescriptor UNKNOWN = new TypeDescriptor(Object.class);
private static final Map<Class<?>, TypeDescriptor> typeDescriptorCache = new HashMap<Class<?>, TypeDescriptor>(); private static final Map<Class<?>, TypeDescriptor> typeDescriptorCache = new HashMap<Class<?>, TypeDescriptor>();
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
@ -140,7 +143,7 @@ public class TypeDescriptor {
* Create a new descriptor for the type of the given value. * Create a new descriptor for the type of the given value.
* <p>Use this constructor when a conversion point comes from a source such as a Map or * <p>Use this constructor when a conversion point comes from a source such as a Map or
* Collection, where no additional context is available but elements can be introspected. * Collection, where no additional context is available but elements can be introspected.
* @param type the actual type to wrap * @param value the value to determine the actual type from
*/ */
private TypeDescriptor(Object value) { private TypeDescriptor(Object value) {
Assert.notNull(value, "Value must not be null"); Assert.notNull(value, "Value must not be null");
@ -263,7 +266,7 @@ public class TypeDescriptor {
*/ */
public TypeDescriptor getElementTypeDescriptor(Object element) { public TypeDescriptor getElementTypeDescriptor(Object element) {
TypeDescriptor elementType = getElementTypeDescriptor(); TypeDescriptor elementType = getElementTypeDescriptor();
return (elementType != TypeDescriptor.NULL ? elementType : forObject(element)); return (elementType != TypeDescriptor.UNKNOWN ? elementType : forObject(element));
} }
/** /**
@ -306,7 +309,7 @@ public class TypeDescriptor {
*/ */
public TypeDescriptor getMapKeyTypeDescriptor(Object key) { public TypeDescriptor getMapKeyTypeDescriptor(Object key) {
TypeDescriptor keyType = getMapKeyTypeDescriptor(); TypeDescriptor keyType = getMapKeyTypeDescriptor();
return keyType != TypeDescriptor.NULL ? keyType : TypeDescriptor.forObject(key); return (keyType != TypeDescriptor.UNKNOWN ? keyType : TypeDescriptor.forObject(key));
} }
/** /**
@ -335,7 +338,7 @@ public class TypeDescriptor {
*/ */
public TypeDescriptor getMapValueTypeDescriptor(Object value) { public TypeDescriptor getMapValueTypeDescriptor(Object value) {
TypeDescriptor valueType = getMapValueTypeDescriptor(); TypeDescriptor valueType = getMapValueTypeDescriptor();
return (valueType != TypeDescriptor.NULL ? valueType : TypeDescriptor.forObject(value)); return (valueType != TypeDescriptor.UNKNOWN ? valueType : TypeDescriptor.forObject(value));
} }
/** /**
@ -390,11 +393,8 @@ public class TypeDescriptor {
* @return the type descriptor * @return the type descriptor
*/ */
public TypeDescriptor forElementType(Class<?> elementType) { public TypeDescriptor forElementType(Class<?> elementType) {
if (getType().equals(elementType)) { if (elementType == null) {
return this; return TypeDescriptor.UNKNOWN;
}
else if (elementType == null) {
return TypeDescriptor.NULL;
} }
else if (this.methodParameter != null) { else if (this.methodParameter != null) {
return new TypeDescriptor(this.methodParameter, elementType); return new TypeDescriptor(this.methodParameter, elementType);
@ -408,21 +408,21 @@ public class TypeDescriptor {
} }
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (!(obj instanceof TypeDescriptor)) { if (this == obj) {
return false;
}
TypeDescriptor td = (TypeDescriptor) obj;
if (this == td) {
return true; return true;
} }
if (!(obj instanceof TypeDescriptor) || obj == TypeDescriptor.NULL) {
return false;
}
TypeDescriptor other = (TypeDescriptor) obj;
boolean annotatedTypeEquals = boolean annotatedTypeEquals =
getType().equals(td.getType()) && ObjectUtils.nullSafeEquals(getAnnotations(), td.getAnnotations()); getType().equals(other.getType()) && ObjectUtils.nullSafeEquals(getAnnotations(), other.getAnnotations());
if (isCollection()) { if (isCollection()) {
return annotatedTypeEquals && ObjectUtils.nullSafeEquals(getElementType(), td.getElementType()); return annotatedTypeEquals && ObjectUtils.nullSafeEquals(getElementType(), other.getElementType());
} }
else if (isMap()) { else if (isMap()) {
return annotatedTypeEquals && ObjectUtils.nullSafeEquals(getMapKeyType(), td.getMapKeyType()) && return annotatedTypeEquals && ObjectUtils.nullSafeEquals(getMapKeyType(), other.getMapKeyType()) &&
ObjectUtils.nullSafeEquals(getMapValueType(), td.getMapValueType()); ObjectUtils.nullSafeEquals(getMapValueType(), other.getMapValueType());
} }
else { else {
return annotatedTypeEquals; return annotatedTypeEquals;
@ -430,7 +430,7 @@ public class TypeDescriptor {
} }
public int hashCode() { public int hashCode() {
return getType().hashCode(); return (this == TypeDescriptor.NULL ? 0 : getType().hashCode());
} }
/** /**

9
org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2010 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -58,9 +58,14 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert
return null; return null;
} }
Collection<?> sourceCollection = (Collection<?>) source; Collection<?> sourceCollection = (Collection<?>) source;
if (sourceCollection.isEmpty()) {
return sourceCollection;
}
Collection target = CollectionFactory.createCollection(targetType.getType(), sourceCollection.size()); Collection target = CollectionFactory.createCollection(targetType.getType(), sourceCollection.size());
for (Object sourceElement : sourceCollection) { for (Object sourceElement : sourceCollection) {
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(sourceElement), targetType.getElementTypeDescriptor(sourceElement)); Object targetElement = this.conversionService.convert(sourceElement,
sourceType.getElementTypeDescriptor(sourceElement),
targetType.getElementTypeDescriptor(sourceElement));
target.add(targetElement); target.add(targetElement);
} }
return target; return target;

5
org.springframework.core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2010 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -62,7 +62,8 @@ final class CollectionToStringConverter implements ConditionalGenericConverter {
if (i > 0) { if (i > 0) {
string.append(DELIMITER); string.append(DELIMITER);
} }
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(sourceElement), targetType); Object targetElement = this.conversionService.convert(
sourceElement, sourceType.getElementTypeDescriptor(sourceElement), targetType);
string.append(targetElement); string.append(targetElement);
i++; i++;
} }

12
org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2010 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,7 +16,6 @@
package org.springframework.core.convert.support; package org.springframework.core.convert.support;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionFailedException;
@ -41,15 +40,6 @@ abstract class ConversionUtils {
} }
} }
public static TypeDescriptor getElementType(Collection<?> collection) {
for (Object element : collection) {
if (element != null) {
return TypeDescriptor.valueOf(element.getClass());
}
}
return TypeDescriptor.NULL;
}
public static TypeDescriptor[] getMapEntryTypes(Map<?, ?> sourceMap) { public static TypeDescriptor[] getMapEntryTypes(Map<?, ?> sourceMap) {
Class<?> keyType = null; Class<?> keyType = null;
Class<?> valueType = null; Class<?> valueType = null;

3
org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java

@ -244,7 +244,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Matched cached converter " + converter); logger.trace("Matched cached converter " + converter);
} }
return converter != NO_MATCH ? converter : null; return (converter != NO_MATCH ? converter : null);
} }
else { else {
converter = findConverterForClassPair(sourceType, targetType); converter = findConverterForClassPair(sourceType, targetType);
@ -394,6 +394,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
private GenericConverter getMatchingConverterForTarget(TypeDescriptor sourceType, TypeDescriptor targetType, private GenericConverter getMatchingConverterForTarget(TypeDescriptor sourceType, TypeDescriptor targetType,
Map<Class<?>, MatchableConverters> converters) { Map<Class<?>, MatchableConverters> converters) {
Class<?> targetObjectType = targetType.getObjectType(); Class<?> targetObjectType = targetType.getObjectType();
if (targetObjectType.isInterface()) { if (targetObjectType.isInterface()) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>(); LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();

13
org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2010 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -59,13 +59,20 @@ final class MapToMapConverter implements ConditionalGenericConverter {
return null; return null;
} }
Map<?, ?> sourceMap = (Map<?, ?>) source; Map<?, ?> sourceMap = (Map<?, ?>) source;
if (sourceMap.isEmpty()) {
return sourceMap;
}
Map targetMap = CollectionFactory.createMap(targetType.getType(), sourceMap.size()); Map targetMap = CollectionFactory.createMap(targetType.getType(), sourceMap.size());
for (Object entry : sourceMap.entrySet()) { for (Object entry : sourceMap.entrySet()) {
Map.Entry sourceMapEntry = (Map.Entry) entry; Map.Entry sourceMapEntry = (Map.Entry) entry;
Object sourceKey = sourceMapEntry.getKey(); Object sourceKey = sourceMapEntry.getKey();
Object sourceValue = sourceMapEntry.getValue(); Object sourceValue = sourceMapEntry.getValue();
Object targetKey = this.conversionService.convert(sourceKey, sourceType.getMapKeyTypeDescriptor(sourceKey), targetType.getMapKeyTypeDescriptor(sourceKey)); Object targetKey = this.conversionService.convert(sourceKey,
Object targetValue = this.conversionService.convert(sourceValue, sourceType.getMapValueTypeDescriptor(sourceValue), targetType.getMapValueTypeDescriptor(sourceValue)); sourceType.getMapKeyTypeDescriptor(sourceKey),
targetType.getMapKeyTypeDescriptor(sourceKey));
Object targetValue = this.conversionService.convert(sourceValue,
sourceType.getMapValueTypeDescriptor(sourceValue),
targetType.getMapValueTypeDescriptor(sourceValue));
targetMap.put(targetKey, targetValue); targetMap.put(targetKey, targetValue);
} }
return targetMap; return targetMap;

6
org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java

@ -42,9 +42,11 @@ import org.springframework.util.ReflectionUtils;
* @since 3.0 * @since 3.0
*/ */
final class ObjectToObjectConverter implements ConditionalGenericConverter { final class ObjectToObjectConverter implements ConditionalGenericConverter {
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return hasValueOfMethodOrConstructor(targetType.getObjectType(), sourceType.getObjectType()); Class source = sourceType.getObjectType();
Class target = targetType.getObjectType();
return (!source.equals(target) && hasValueOfMethodOrConstructor(target, source));
} }
public Set<ConvertiblePair> getConvertibleTypes() { public Set<ConvertiblePair> getConvertibleTypes() {

63
org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java

@ -17,6 +17,7 @@
package org.springframework.core.convert.support; package org.springframework.core.convert.support;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
@ -225,6 +226,57 @@ public class GenericConversionServiceTests {
assertEquals(input, converted); assertEquals(input, converted);
} }
@Test
public void testListOfList() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
List<List<String>> list = Collections.singletonList(Collections.singletonList("Foo"));
assertNotNull(service.convert(list, String.class));
}
@Test
public void testEmptyList() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
List list = Collections.emptyList();
List result = service.convert(list, List.class);
assertSame(list, result);
result = service.convert(list, list.getClass());
assertSame(list, result);
}
@Test
public void testEmptyMap() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
Map map = Collections.emptyMap();
Map result = service.convert(map, Map.class);
assertSame(map, result);
result = service.convert(map, map.getClass());
assertSame(map, result);
}
@Test
public void testStringToString() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
String value = "myValue";
String result = service.convert(value, String.class);
assertSame(value, result);
}
@Test
public void testStringToObject() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
String value = "myValue";
Object result = service.convert(value, Object.class);
assertSame(value, result);
}
@Test
public void testIgnoreCopyConstructor() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
WithCopyConstructor value = new WithCopyConstructor();
Object result = service.convert(value, WithCopyConstructor.class);
assertSame(value, result);
}
@Test @Test
public void testPerformance1() { public void testPerformance1() {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
@ -296,6 +348,7 @@ public class GenericConversionServiceTests {
public static Map<String, Integer> map; public static Map<String, Integer> map;
private interface MyBaseInterface { private interface MyBaseInterface {
} }
@ -319,6 +372,16 @@ public class GenericConversionServiceTests {
} }
public static class WithCopyConstructor {
public WithCopyConstructor() {
}
public WithCopyConstructor(WithCopyConstructor value) {
}
}
public static Map<String, ?> wildcardMap; public static Map<String, ?> wildcardMap;
} }

Loading…
Cancel
Save