fixed detection of element type in case of nested collections (SPR-7569)

This commit is contained in:
Juergen Hoeller 2010-10-10 21:09:43 +00:00
parent 6b3c299a50
commit ebe8052d55
2 changed files with 88 additions and 41 deletions

View File

@ -76,6 +76,8 @@ public class TypeDescriptor {
private Field field; private Field field;
private int fieldNestingLevel = 1;
private Object value; private Object value;
private TypeDescriptor elementType; private TypeDescriptor elementType;
@ -133,6 +135,19 @@ public class TypeDescriptor {
this.type = type; this.type = type;
} }
/**
* Create a new type descriptor for a field.
* Use this constructor when a target conversion point originates from a field.
* @param field the field to wrap
* @param type the specific type to expose (may be an array/collection element)
*/
private TypeDescriptor(Field field, int nestingLevel, Class<?> type) {
Assert.notNull(field, "Field must not be null");
this.field = field;
this.fieldNestingLevel = nestingLevel;
this.type = type;
}
/** /**
* Internal constructor for a NULL descriptor. * Internal constructor for a NULL descriptor.
*/ */
@ -397,10 +412,12 @@ public class TypeDescriptor {
return TypeDescriptor.UNKNOWN; return TypeDescriptor.UNKNOWN;
} }
else if (this.methodParameter != null) { else if (this.methodParameter != null) {
return new TypeDescriptor(this.methodParameter, elementType); MethodParameter nested = new MethodParameter(this.methodParameter);
nested.increaseNestingLevel();
return new TypeDescriptor(nested, elementType);
} }
else if (this.field != null) { else if (this.field != null) {
return new TypeDescriptor(this.field, elementType); return new TypeDescriptor(this.field, this.fieldNestingLevel + 1, elementType);
} }
else { else {
return TypeDescriptor.valueOf(elementType); return TypeDescriptor.valueOf(elementType);
@ -434,7 +451,7 @@ public class TypeDescriptor {
} }
/** /**
* A textual representation of the type descriptor (eg. Map<String,Foo>) for use in messages * A textual representation of the type descriptor (eg. Map<String,Foo>) for use in messages.
*/ */
public String asString() { public String asString() {
return toString(); return toString();
@ -442,28 +459,22 @@ public class TypeDescriptor {
public String toString() { public String toString() {
if (this == TypeDescriptor.NULL) { if (this == TypeDescriptor.NULL) {
return "[TypeDescriptor.NULL]"; return "null";
} }
else { else {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("[TypeDescriptor ");
Annotation[] anns = getAnnotations(); Annotation[] anns = getAnnotations();
for (Annotation ann : anns) { for (Annotation ann : anns) {
builder.append("@").append(ann.annotationType().getName()).append(' '); builder.append("@").append(ann.annotationType().getName()).append(' ');
} }
builder.append(ClassUtils.getQualifiedName(getType())); builder.append(ClassUtils.getQualifiedName(getType()));
if (isMap()) { if (isMap()) {
Class<?> mapKeyType = getMapKeyType(); builder.append("<").append(getMapKeyTypeDescriptor());
Class<?> valueKeyType = getMapValueType(); builder.append(", ").append(getMapValueTypeDescriptor()).append(">");
builder.append("<").append(mapKeyType != null ? ClassUtils.getQualifiedName(mapKeyType) : "?");
builder.append(", ").append(valueKeyType != null ? ClassUtils.getQualifiedName(valueKeyType) : "?");
builder.append(">");
} }
else if (isCollection()) { else if (isCollection()) {
Class<?> elementType = getElementType(); builder.append("<").append(getElementTypeDescriptor()).append(">");
builder.append("<").append(elementType != null ? ClassUtils.getQualifiedName(elementType) : "?").append(">");
} }
builder.append("]");
return builder.toString(); return builder.toString();
} }
} }
@ -486,7 +497,7 @@ public class TypeDescriptor {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Class<?> resolveCollectionElementType() { private Class<?> resolveCollectionElementType() {
if (this.field != null) { if (this.field != null) {
return GenericCollectionTypeResolver.getCollectionFieldType(this.field); return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.fieldNestingLevel);
} }
else if (this.methodParameter != null) { else if (this.methodParameter != null) {
return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter); return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter);
@ -497,7 +508,10 @@ public class TypeDescriptor {
return elementType; return elementType;
} }
} }
return (this.type != null ? GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection>) this.type) : null); else if (this.type != null) {
return GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection>) this.type);
}
return null;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -514,7 +528,10 @@ public class TypeDescriptor {
return keyType; return keyType;
} }
} }
return (this.type != null && isMap() ? GenericCollectionTypeResolver.getMapKeyType((Class<? extends Map>) this.type) : null); else if (this.type != null && isMap()) {
return GenericCollectionTypeResolver.getMapKeyType((Class<? extends Map>) this.type);
}
return null;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -531,7 +548,10 @@ public class TypeDescriptor {
return valueType; return valueType;
} }
} }
return (isMap() && this.type != null ? GenericCollectionTypeResolver.getMapValueType((Class<? extends Map>) this.type) : null); else if (this.type != null && isMap()) {
return GenericCollectionTypeResolver.getMapValueType((Class<? extends Map>) this.type);
}
return null;
} }
private Annotation[] resolveAnnotations() { private Annotation[] resolveAnnotations() {

View File

@ -16,16 +16,16 @@
package org.springframework.core.convert; package org.springframework.core.convert;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import org.junit.Test; import org.junit.Test;
/** /**
@ -33,42 +33,73 @@ import org.junit.Test;
*/ */
public class TypeDescriptorTests { public class TypeDescriptorTests {
List<String> listOfString; public List<String> listOfString;
int[] intArray;
List<String>[] arrayOfListOfString; public List<List<String>> listOfListOfString = new ArrayList<List<String>>();
public List<List> listOfListOfUnknown = new ArrayList<List>();
public int[] intArray;
public List<String>[] arrayOfListOfString;
public List<Integer> listField = new ArrayList<Integer>();
public Map<String, Integer> mapField = new HashMap<String, Integer>();
@Test @Test
public void listDescriptors() throws Exception { public void listDescriptor() throws Exception {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfString")); TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfString"));
assertFalse(typeDescriptor.isArray()); assertFalse(typeDescriptor.isArray());
assertEquals(List.class,typeDescriptor.getType()); assertEquals(List.class, typeDescriptor.getType());
assertEquals(String.class,typeDescriptor.getElementType()); assertEquals(String.class, typeDescriptor.getElementType());
// TODO caught shorten these names but it is OK that they are fully qualified for now // TODO caught shorten these names but it is OK that they are fully qualified for now
assertEquals("[TypeDescriptor java.util.List<java.lang.String>]",typeDescriptor.asString()); assertEquals("java.util.List<java.lang.String>", typeDescriptor.asString());
} }
@Test @Test
public void arrayTypeDescriptors() throws Exception { public void listOfListOfStringDescriptor() throws Exception {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfListOfString"));
assertFalse(typeDescriptor.isArray());
assertEquals(List.class, typeDescriptor.getType());
assertEquals(List.class, typeDescriptor.getElementType());
assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType());
assertEquals("java.util.List<java.util.List<java.lang.String>>", typeDescriptor.asString());
}
@Test
public void listOfListOfUnknownDescriptor() throws Exception {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfListOfUnknown"));
assertFalse(typeDescriptor.isArray());
assertEquals(List.class, typeDescriptor.getType());
assertEquals(List.class, typeDescriptor.getElementType());
assertEquals(Object.class, typeDescriptor.getElementTypeDescriptor().getElementType());
assertEquals("java.util.List<java.util.List<java.lang.Object>>", typeDescriptor.asString());
}
@Test
public void arrayTypeDescriptor() throws Exception {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("intArray")); TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("intArray"));
assertTrue(typeDescriptor.isArray()); assertTrue(typeDescriptor.isArray());
assertEquals(Integer.TYPE,typeDescriptor.getElementType()); assertEquals(Integer.TYPE,typeDescriptor.getElementType());
assertEquals("[TypeDescriptor int[]]",typeDescriptor.asString()); assertEquals("int[]",typeDescriptor.asString());
} }
@Test @Test
public void buildingArrayTypeDescriptors() throws Exception { public void buildingArrayTypeDescriptor() throws Exception {
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(int[].class); TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(int[].class);
assertTrue(typeDescriptor.isArray()); assertTrue(typeDescriptor.isArray());
assertEquals(Integer.TYPE,typeDescriptor.getElementType()); assertEquals(Integer.TYPE ,typeDescriptor.getElementType());
} }
@Test @Test
public void complexTypeDescriptors() throws Exception { public void complexTypeDescriptor() throws Exception {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("arrayOfListOfString")); TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("arrayOfListOfString"));
assertTrue(typeDescriptor.isArray()); assertTrue(typeDescriptor.isArray());
assertEquals(List.class,typeDescriptor.getElementType()); assertEquals(List.class,typeDescriptor.getElementType());
// TODO asc notice that the type of the list elements is lost: typeDescriptor.getElementType() should return a TypeDescriptor // TODO asc notice that the type of the list elements is lost: typeDescriptor.getElementType() should return a TypeDescriptor
assertEquals("[TypeDescriptor java.util.List[]]",typeDescriptor.asString()); assertEquals("java.util.List[]",typeDescriptor.asString());
} }
@Test @Test
@ -95,8 +126,4 @@ public class TypeDescriptorTests {
assertEquals(t11, t12); assertEquals(t11, t12);
} }
public List<Integer> listField = new ArrayList<Integer>();
public Map<String, Integer> mapField = new HashMap<String, Integer>();
} }