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.
This commit is contained in:
Oliver Gierke
2011-03-17 09:05:34 +01:00
parent 8ab42a266c
commit 7a2f25190a
5 changed files with 176 additions and 71 deletions

View File

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

View File

@@ -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<Class<?>> 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<Class<?>> 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<TypeVariable, Type> typeVariableMap, Set<Class<?>> basicTypes,
TypeDiscoverer parent) {
super(type, typeVariableMap, parent);
this.type = type;
}
@SuppressWarnings("rawtypes")
ClassTypeInformation(Class<?> type, Map<TypeVariable, Type> typeVariableMap, Set<Class<?>> 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();
}
}

View File

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

View File

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

View File

@@ -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<String> {
@@ -97,10 +113,17 @@ public class ClassTypeInformationUnitTests {
Map<String, T> genericMap;
Map<String, Calendar> map;
}
private class CollectionContainer {
private class StringCollectionContainer extends CollectionContainer<String> {
String[] array;
}
private class CollectionContainer<T> {
T[] array;
Collection<T>[] foo;
Set<String> set;
Set rawSet;
}
private class GenericTypeWithBound<T extends Person> {