提交 4d6a5849 编写于 作者: K Keith Donald

SPR-8364

上级 4a6101a6
......@@ -343,24 +343,33 @@ public class TypeDescriptor {
}
/**
* Create a copy of this nested type descriptor and apply the specific type information from the indexed object.
* Create a copy of this nested type descriptor and apply the specific type information from the indexed value, if necessary.
* Used to support collection and map indexing scenarios, where the indexer has a reference to the indexed type descriptor but needs to ensure its type actually represents the indexed object type.
* This is necessary to support type conversion during index object binding operations.
* This is necessary to support type conversion during collection and map binding operations where generic information may not be available.
*/
public TypeDescriptor applyIndexedObject(Object object) {
if (object == null) {
return this;
}
// TODO preserve binding context with returned copy
// TODO fall back to generic info if collection is empty
if (object instanceof Collection<?>) {
return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType((Collection<?>) object));
if (isCollection() && Object.class.equals(getElementType())) {
Collection<?> collection = (Collection<?>) object;
if (collection.size() > 0) {
return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType((Collection<?>) object), methodParameter, field, fieldNestingLevel, annotations);
} else {
return this;
}
}
else if (object instanceof Map<?, ?>) {
return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType(((Map<?, ?>) object).keySet()), CollectionUtils.findCommonElementType(((Map<?, ?>) object).values()));
else if (isMap() && Object.class.equals(getMapKeyType()) && Object.class.equals(getMapValueType())) {
Map<?, ?> map = (Map<?, ?>) object;
if (map.size() > 0) {
return new TypeDescriptor(object.getClass(), CollectionUtils.findCommonElementType(((Map<?, ?>) object).keySet()),
CollectionUtils.findCommonElementType(((Map<?, ?>) object).values()), methodParameter, field, fieldNestingLevel, annotations);
} else {
return this;
}
}
else {
return valueOf(object.getClass());
return this;
}
}
......@@ -487,6 +496,7 @@ public class TypeDescriptor {
}
}
@SuppressWarnings("unchecked")
private Class<?> resolveCollectionElementType() {
if (this.methodParameter != null) {
return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter);
......@@ -495,10 +505,11 @@ public class TypeDescriptor {
return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.fieldNestingLevel);
}
else {
return GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection>) this.type);
return GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection<?>>) this.type);
}
}
@SuppressWarnings("unchecked")
private Class<?> resolveMapKeyType() {
if (this.methodParameter != null) {
return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter);
......@@ -507,10 +518,11 @@ public class TypeDescriptor {
return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.fieldNestingLevel);
}
else {
return GenericCollectionTypeResolver.getMapKeyType((Class<? extends Map>) this.type);
return GenericCollectionTypeResolver.getMapKeyType((Class<? extends Map<?, ?>>) this.type);
}
}
@SuppressWarnings("unchecked")
private Class<?> resolveMapValueType() {
if (this.methodParameter != null) {
return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter);
......@@ -519,7 +531,7 @@ public class TypeDescriptor {
return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.fieldNestingLevel);
}
else {
return GenericCollectionTypeResolver.getMapValueType((Class<? extends Map>) this.type);
return GenericCollectionTypeResolver.getMapValueType((Class<? extends Map<?, ?>>) this.type);
}
}
......@@ -550,7 +562,7 @@ public class TypeDescriptor {
}
this.elementType = TypeDescriptor.valueOf(elementType);
}
private TypeDescriptor(Class<?> mapType, Class<?> keyType, Class<?> valueType) {
this.type = mapType;
if (keyType == null) {
......@@ -563,6 +575,22 @@ public class TypeDescriptor {
this.mapValueType = TypeDescriptor.valueOf(valueType);
}
private TypeDescriptor(Class<?> collectionType, Class<?> elementType, MethodParameter methodParameter, Field field, int fieldNestingLevel, Annotation[] annotations) {
this(collectionType, elementType);
this.methodParameter = methodParameter;
this.field = field;
this.fieldNestingLevel = fieldNestingLevel;
this.annotations = annotations;
}
private TypeDescriptor(Class<?> mapType, Class<?> keyType, Class<?> valueType, MethodParameter methodParameter, Field field, int fieldNestingLevel, Annotation[] annotations) {
this(mapType, keyType, valueType);
this.methodParameter = methodParameter;
this.field = field;
this.fieldNestingLevel = fieldNestingLevel;
this.annotations = annotations;
}
private TypeDescriptor(Class<?> nestedType, Field field, int nestingLevel) {
this.type = nestedType;
this.field = field;
......
......@@ -66,17 +66,24 @@ final class MapToMapConverter implements ConditionalGenericConverter {
if (source == null) {
return null;
}
Map<?, ?> sourceMap = (Map<?, ?>) source;
Map targetMap = CollectionFactory.createMap(targetType.getType(), sourceMap.size());
for (Object entry : sourceMap.entrySet()) {
Map.Entry sourceMapEntry = (Map.Entry) entry;
Object sourceKey = sourceMapEntry.getKey();
Object sourceValue = sourceMapEntry.getValue();
Object targetKey = this.conversionService.convert(sourceKey, sourceType.getMapKeyTypeDescriptor().applyIndexedObject(sourceKey), targetType.getMapKeyTypeDescriptor());
Object targetValue = this.conversionService.convert(sourceValue, sourceType.getMapValueTypeDescriptor().applyIndexedObject(sourceValue), targetType.getMapValueTypeDescriptor());
targetMap.put(targetKey, targetValue);
Map<Object, Object> sourceMap = (Map<Object, Object>) source;
Map<Object, Object> targetMap = CollectionFactory.createMap(targetType.getType(), sourceMap.size());
TypeDescriptor targetKeyType = targetType.getMapKeyTypeDescriptor();
TypeDescriptor targetValueType = targetType.getMapValueTypeDescriptor();
if (Object.class.equals(targetKeyType.getType()) && Object.class.equals(targetValueType.getType())) {
for (Map.Entry<Object, Object> entry : sourceMap.entrySet()) {
targetMap.put(entry.getKey(), entry.getValue());
}
} else {
for (Map.Entry<Object, Object> entry : sourceMap.entrySet()) {
Object sourceKey = entry.getKey();
Object sourceValue = entry.getValue();
Object targetKey = this.conversionService.convert(sourceKey, sourceType.getMapKeyTypeDescriptor().applyIndexedObject(sourceKey), targetType.getMapKeyTypeDescriptor());
Object targetValue = this.conversionService.convert(sourceValue, sourceType.getMapValueTypeDescriptor().applyIndexedObject(sourceValue), targetType.getMapValueTypeDescriptor());
targetMap.put(targetKey, targetValue);
}
}
return targetMap;
}
}
}
\ No newline at end of file
......@@ -412,35 +412,6 @@ public class GenericConversionServiceTests {
assertNull(conversionService.convert(list, sourceType, targetType));
}
@Test
public void emptyMapToMap() throws Exception {
conversionService.addConverter(new MapToMapConverter(conversionService));
conversionService.addConverterFactory(new StringToNumberConverterFactory());
Map<String, String> map = new HashMap<String, String>();
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyMapTarget"));
assertTrue(conversionService.canConvert(sourceType, targetType));
assertEquals(map, conversionService.convert(map, sourceType, targetType));
}
public Map<String, String> emptyMapTarget;
@Test
public void emptyMapToMapDifferentTargetType() throws Exception {
conversionService.addConverter(new MapToMapConverter(conversionService));
conversionService.addConverterFactory(new StringToNumberConverterFactory());
Map<String, String> map = new HashMap<String, String>();
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyMapDifferentTarget"));
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
LinkedHashMap<String, String> result = (LinkedHashMap<String, String>) conversionService.convert(map, sourceType, targetType);
assertEquals(map, result);
assertEquals(LinkedHashMap.class, result.getClass());
}
public LinkedHashMap<String, String> emptyMapDifferentTarget;
private interface MyBaseInterface {
}
......
package org.springframework.core.convert.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.TypeDescriptor;
public class MapToMapConverterTests {
private GenericConversionService conversionService = new GenericConversionService();
@Before
public void setUp() {
conversionService.addConverter(new MapToMapConverter(conversionService));
}
@Test
public void scalarMap() throws Exception {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "9");
map.put("2", "37");
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("scalarMapTarget"));
assertFalse(conversionService.canConvert(sourceType, targetType));
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
Map<Integer, Integer> result = (Map<Integer, Integer>) conversionService.convert(map, sourceType, targetType);
assertFalse(map.equals(result));
assertEquals((Integer) 9, result.get(1));
assertEquals((Integer) 37, result.get(2));
}
public Map<Integer, Integer> scalarMapTarget;
@Test
public void scalarMapNotGenericTarget() throws Exception {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "9");
map.put("2", "37");
assertTrue(conversionService.canConvert(Map.class, Map.class));
assertEquals(map, conversionService.convert(map, Map.class));
}
@Test
public void collectionMap() throws Exception {
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("1", Arrays.asList("9", "12"));
map.put("2", Arrays.asList("37", "23"));
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("collectionMapTarget"));
assertFalse(conversionService.canConvert(sourceType, targetType));
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
Map<Integer, List<Integer>> result = (Map<Integer, List<Integer>>) conversionService.convert(map, sourceType, targetType);
assertFalse(map.equals(result));
assertEquals(Arrays.asList(9, 12), result.get(1));
assertEquals(Arrays.asList(37, 23), result.get(2));
}
public Map<Integer, List<Integer>> collectionMapTarget;
public Map<String, List<String>> sourceCollectionMapTarget;
@Test
public void collectionMapSourceTarget() throws Exception {
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("1", Arrays.asList("9", "12"));
map.put("2", Arrays.asList("37", "23"));
TypeDescriptor sourceType = new TypeDescriptor(getClass().getField("sourceCollectionMapTarget"));
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("collectionMapTarget"));
assertFalse(conversionService.canConvert(sourceType, targetType));
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
Map<Integer, List<Integer>> result = (Map<Integer, List<Integer>>) conversionService.convert(map, sourceType, targetType);
assertFalse(map.equals(result));
assertEquals(Arrays.asList(9, 12), result.get(1));
assertEquals(Arrays.asList(37, 23), result.get(2));
}
@Test
public void collectionMapNotGenericTarget() throws Exception {
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("1", Arrays.asList("9", "12"));
map.put("2", Arrays.asList("37", "23"));
assertTrue(conversionService.canConvert(Map.class, Map.class));
assertEquals(map, conversionService.convert(map, Map.class));
}
@Test
public void collectionMapNotGenericTargetCollectionToObjectInteraction() throws Exception {
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("1", Arrays.asList("9", "12"));
map.put("2", Arrays.asList("37", "23"));
conversionService.addConverter(new CollectionToObjectConverter(conversionService));
assertTrue(conversionService.canConvert(Map.class, Map.class));
assertEquals(map, conversionService.convert(map, Map.class));
}
@Test
public void emptyMap() throws Exception {
Map<String, String> map = new HashMap<String, String>();
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyMapTarget"));
assertTrue(conversionService.canConvert(sourceType, targetType));
assertEquals(map, conversionService.convert(map, sourceType, targetType));
}
public Map<String, String> emptyMapTarget;
@Test
public void emptyMapNoTargetGenericInfo() throws Exception {
Map<String, String> map = new HashMap<String, String>();
assertTrue(conversionService.canConvert(Map.class, Map.class));
assertEquals(map, conversionService.convert(map, Map.class));
}
@Test
public void emptyMapDifferentTargetImplType() throws Exception {
Map<String, String> map = new HashMap<String, String>();
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyMapDifferentTarget"));
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
LinkedHashMap<String, String> result = (LinkedHashMap<String, String>) conversionService.convert(map, sourceType, targetType);
assertEquals(map, result);
assertEquals(LinkedHashMap.class, result.getClass());
}
public LinkedHashMap<String, String> emptyMapDifferentTarget;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册