From 7a2f25190a793cc87535b3413665adce64a791c4 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 17 Mar 2011 09:05:34 +0100 Subject: [PATCH] Extended TypeInformation classes to support generic arrays as well. Additionally added support for resolving component types (for collections and map keys) as well as map value types. --- .../data/util/ArrayTypeDiscoverer.java | 44 ++++++ .../data/util/ClassTypeInformation.java | 129 ++++++++++-------- .../data/util/TypeDiscoverer.java | 26 +++- .../data/util/TypeInformation.java | 9 +- .../util/ClassTypeInformationUnitTests.java | 39 ++++-- 5 files changed, 176 insertions(+), 71 deletions(-) create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/util/ArrayTypeDiscoverer.java diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/util/ArrayTypeDiscoverer.java b/spring-data-commons-core/src/main/java/org/springframework/data/util/ArrayTypeDiscoverer.java new file mode 100644 index 000000000..c715aff29 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/util/ArrayTypeDiscoverer.java @@ -0,0 +1,44 @@ +package org.springframework.data.util; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Type; + +/** + * Special {@link TypeDiscoverer} handling {@link GenericArrayType}s. + * + * @author Oliver Gierke + */ +public class ArrayTypeDiscoverer extends TypeDiscoverer { + + private GenericArrayType type; + + /** + * @param type + * @param typeVariableMap + * @param parent + */ + protected ArrayTypeDiscoverer(GenericArrayType type, TypeDiscoverer parent) { + super(type, null, parent); + this.type = type; + } + + /* (non-Javadoc) + * @see org.springframework.data.util.TypeDiscoverer#getType() + */ + @Override + public Class getType() { + + return Array.newInstance(resolveType(type.getGenericComponentType()), 0).getClass(); + } + + /* (non-Javadoc) + * @see org.springframework.data.util.TypeDiscoverer#getComponentType() + */ + @Override + public TypeInformation getComponentType() { + + Type componentType = type.getGenericComponentType(); + return createInfo(componentType); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java index d4cd4835d..e7d0aa96d 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java @@ -12,68 +12,79 @@ import java.util.Set; */ public class ClassTypeInformation extends TypeDiscoverer { - private final Class type; + private final Class type; - /** - * Creates {@link ClassTypeInformation} for the given type. - * @param type - */ - public ClassTypeInformation(Class type) { - this(type, GenericTypeResolver.getTypeVariableMap(type), null, null); - } + /** + * Creates {@link ClassTypeInformation} for the given type. + * + * @param type + */ + public ClassTypeInformation(Class type) { + this(type, GenericTypeResolver.getTypeVariableMap(type), null, null); + } - /** - * Creates {@link ClassTypeInformation} for the given type and the given basic types. Handing over a basic type - * will prevent it's nested fields to be traversed for further {@link TypeInformation}. - * - * @param type - * @param basicTypes - */ - public ClassTypeInformation(Class type, Set> basicTypes) { - this(type, GenericTypeResolver.getTypeVariableMap(type), basicTypes, null); - } + /** + * Creates {@link ClassTypeInformation} for the given type and the given basic types. Handing over a basic type will + * prevent it's nested fields to be traversed for further {@link TypeInformation}. + * + * @param type + * @param basicTypes + */ + public ClassTypeInformation(Class type, Set> basicTypes) { + this(type, GenericTypeResolver.getTypeVariableMap(type), basicTypes, null); + } - ClassTypeInformation(Class type, TypeDiscoverer parent) { - this(type, null, null, parent); - } + ClassTypeInformation(Class type, TypeDiscoverer parent) { + this(type, null, null, parent); + } - @SuppressWarnings("rawtypes") - ClassTypeInformation(Class type, Map typeVariableMap, Set> basicTypes, - TypeDiscoverer parent) { - super(type, typeVariableMap, parent); - this.type = type; - } + @SuppressWarnings("rawtypes") + ClassTypeInformation(Class type, Map typeVariableMap, Set> basicTypes, + TypeDiscoverer parent) { + super(type, typeVariableMap, parent); + this.type = type; + } - /* - * (non-Javadoc) - * - * @see org.springframework.data.document.mongodb.TypeDiscovererTest.FieldInformation#getType() - */ - @Override - public Class getType() { - return type; - } - - /* (non-Javadoc) - * @see org.springframework.data.util.TypeDiscoverer#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - - if (!super.equals(obj)) { - return false; - } - - ClassTypeInformation that = (ClassTypeInformation) obj; - return this.type.equals(that.type); - } - - /* (non-Javadoc) - * @see org.springframework.data.util.TypeDiscoverer#hashCode() - */ - @Override - public int hashCode() { - int result = super.hashCode(); - return result += 31 * type.hashCode(); - } + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.TypeDiscovererTest.FieldInformation#getType() + */ + @Override + public Class getType() { + return type; + } + + /* (non-Javadoc) + * @see org.springframework.data.util.TypeDiscoverer#getComponentType() + */ + @Override + public TypeInformation getComponentType() { + + TypeVariable[] typeParameters = type.getTypeParameters(); + return typeParameters.length > 0 ? new TypeVariableTypeInformation(typeParameters[0], this.getType(), this) : null; + } + + /* (non-Javadoc) + * @see org.springframework.data.util.TypeDiscoverer#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + + if (!super.equals(obj)) { + return false; + } + + ClassTypeInformation that = (ClassTypeInformation) obj; + return this.type.equals(that.type); + } + + /* (non-Javadoc) + * @see org.springframework.data.util.TypeDiscoverer#hashCode() + */ + @Override + public int hashCode() { + int result = super.hashCode(); + return result += 31 * type.hashCode(); + } } \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java index 83ddc1aae..cbaaf35f4 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -2,9 +2,11 @@ package org.springframework.data.util; import static org.springframework.util.ObjectUtils.*; import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; +import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -60,7 +62,7 @@ class TypeDiscoverer implements TypeInformation { * @param fieldType * @return */ - private TypeInformation createInfo(Type fieldType) { + protected TypeInformation createInfo(Type fieldType) { if (fieldType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) fieldType; @@ -75,6 +77,10 @@ class TypeDiscoverer implements TypeInformation { if (fieldType instanceof Class) { return new ClassTypeInformation((Class) fieldType, this); } + + if (fieldType instanceof GenericArrayType) { + return new ArrayTypeDiscoverer((GenericArrayType) fieldType, this); + } throw new IllegalArgumentException(); } @@ -142,14 +148,28 @@ class TypeDiscoverer implements TypeInformation { * @see org.springframework.data.util.TypeInformation#getMapValueType() */ @Override - public Class getMapValueType() { + public TypeInformation getMapValueType() { if (!Map.class.isAssignableFrom(getType())) { return null; } ParameterizedType parameterizedType = (ParameterizedType) type; - return createInfo(parameterizedType.getActualTypeArguments()[1]).getType(); + return createInfo(parameterizedType.getActualTypeArguments()[1]); + } + + /* (non-Javadoc) + * @see org.springframework.data.util.TypeInformation#getComponentType() + */ + @Override + public TypeInformation getComponentType() { + + if (!(Map.class.isAssignableFrom(getType()) || Collection.class.isAssignableFrom(getType()))) { + return null; + } + + ParameterizedType parameterizedType = (ParameterizedType) type; + return createInfo(parameterizedType.getActualTypeArguments()[0]); } /* diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeInformation.java index abdd593cc..1ad1a8d85 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeInformation.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeInformation.java @@ -1,5 +1,6 @@ package org.springframework.data.util; +import java.util.Collection; import java.util.Map; /** @@ -20,13 +21,19 @@ public interface TypeInformation { */ TypeInformation getProperty(String fieldname); + /** + * Returns the component type for {@link Collection}s or the key type for {@link Map}s. + * + * @return + */ + TypeInformation getComponentType(); /** * Will return the type of the value in case the underlying type is a {@link Map}. * * @return */ - Class getMapValueType(); + TypeInformation getMapValueType(); /** * Returns the type of the property. Will resolve generics and the generic diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java index 13f3cf905..d1dee4c09 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java @@ -4,7 +4,9 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import java.util.Calendar; +import java.util.Collection; import java.util.Map; +import java.util.Set; import org.junit.Test; import org.springframework.data.mapping.Person; @@ -69,11 +71,25 @@ public class ClassTypeInformationUnitTests { } @Test - public void discoversArrays() { - ClassTypeInformation information = new ClassTypeInformation(CollectionContainer.class); - Class type = information.getProperty("array").getType(); + public void discoversArraysAndCollections() { + ClassTypeInformation information = new ClassTypeInformation(StringCollectionContainer.class); + + TypeInformation property = information.getProperty("array"); + assertEquals(property.getComponentType().getType(), String.class); + + Class type = property.getType(); assertEquals(String[].class, type); assertThat(type.isArray(), is(true)); + + property = information.getProperty("foo"); + assertEquals(Collection[].class, property.getType()); + assertEquals(Collection.class, property.getComponentType().getType()); + assertEquals(String.class, property.getComponentType().getComponentType().getType()); + + property = information.getProperty("rawSet"); + assertEquals(Set.class, property.getType()); + assertEquals(Object.class, property.getComponentType().getType()); + assertNull(property.getMapValueType()); } @Test @@ -82,11 +98,11 @@ public class ClassTypeInformationUnitTests { ClassTypeInformation information = new ClassTypeInformation(StringMapContainer.class); TypeInformation genericMap = information.getProperty("genericMap"); assertEquals(Map.class, genericMap.getType()); - assertEquals(String.class, genericMap.getMapValueType()); + assertEquals(String.class, genericMap.getMapValueType().getType()); TypeInformation map = information.getProperty("map"); assertEquals(Map.class, map.getType()); - assertEquals(Calendar.class, map.getMapValueType()); + assertEquals(Calendar.class, map.getMapValueType().getType()); } private class StringMapContainer extends MapContainer { @@ -97,10 +113,17 @@ public class ClassTypeInformationUnitTests { Map genericMap; Map map; } - - private class CollectionContainer { + + private class StringCollectionContainer extends CollectionContainer { - String[] array; + } + + private class CollectionContainer { + + T[] array; + Collection[] foo; + Set set; + Set rawSet; } private class GenericTypeWithBound {