DATACMNS-590 - Fixed calculation of nested generics in ParentTypeAwareTypeInformation.

So far, the lookup of the type variable map was preferring the type variable maps detected on parent types in nested structures. This caused the concrete type variables in nested types not being considered correctly which caused the type resolution to fall back to the generic bounds.

We now accumulate the type variable maps to avoid having to lookup a certain map in the nesting hierarchy. The core fix is in ParentTypeAwareTypeInformation's constructor and mergeMaps(…) respectively. Simplified the handling of type variable maps and made proper use of generics throughout the class hierarchy.
This commit is contained in:
Oliver Gierke
2014-11-12 14:30:34 +01:00
parent dec5cadce9
commit ac73720668
9 changed files with 152 additions and 70 deletions

View File

@@ -23,8 +23,10 @@ import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
@@ -98,12 +100,26 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
* @param type
*/
ClassTypeInformation(Class<S> type) {
this(type, GenericTypeResolver.getTypeVariableMap(type));
super(ClassUtils.getUserClass(type), getTypeVariableMap(type));
this.type = type;
}
ClassTypeInformation(Class<S> type, Map<TypeVariable, Type> typeVariableMap) {
super(ClassUtils.getUserClass(type), typeVariableMap);
this.type = type;
/**
* Little helper to allow us to create a generified map, actually just to satisfy the compiler.
*
* @param type must not be {@literal null}.
* @return
*/
private static Map<TypeVariable<?>, Type> getTypeVariableMap(Class<?> type) {
Map<TypeVariable, Type> source = GenericTypeResolver.getTypeVariableMap(type);
Map<TypeVariable<?>, Type> map = new HashMap<TypeVariable<?>, Type>(source.size());
for (Entry<TypeVariable, Type> entry : source.entrySet()) {
map.put(entry.getKey(), entry.getValue());
}
return map;
}
/*

View File

@@ -18,6 +18,8 @@ package org.springframework.data.util;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Map;
/**
* Special {@link TypeDiscoverer} handling {@link GenericArrayType}s.
@@ -26,18 +28,20 @@ import java.lang.reflect.Type;
*/
class GenericArrayTypeInformation<S> extends ParentTypeAwareTypeInformation<S> {
private GenericArrayType type;
private final GenericArrayType type;
/**
* Creates a new {@link GenericArrayTypeInformation} for the given {@link GenericArrayTypeInformation} and
* {@link TypeDiscoverer}.
*
* @param type
* @param parent
* @param type must not be {@literal null}.
* @param parent must not be {@literal null}.
* @param typeVariableMap must not be {@literal null}.
*/
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent) {
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent,
Map<TypeVariable<?>, Type> typeVariableMap) {
super(type, parent, parent.getTypeVariableMap());
super(type, parent, typeVariableMap);
this.type = type;
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -24,7 +25,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.core.GenericTypeResolver;
import org.springframework.util.StringUtils;
/**
@@ -44,8 +44,10 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T>
* @param type must not be {@literal null}
* @param parent must not be {@literal null}
*/
public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> parent) {
super(type, parent, null);
public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> parent,
Map<TypeVariable<?>, Type> typeVariableMap) {
super(type, parent, typeVariableMap);
this.type = type;
}
@@ -72,8 +74,11 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T>
supertypes.addAll(Arrays.asList(rawType.getGenericInterfaces()));
for (Type supertype : supertypes) {
Class<?> rawSuperType = GenericTypeResolver.resolveType(supertype, getTypeVariableMap());
Class<?> rawSuperType = resolveType(supertype);
if (Map.class.isAssignableFrom(rawSuperType)) {
ParameterizedType parameterizedSupertype = (ParameterizedType) supertype;
Type[] arguments = parameterizedSupertype.getActualTypeArguments();
return createInfo(arguments[1]);

View File

@@ -17,6 +17,7 @@ package org.springframework.data.util;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.ObjectUtils;
@@ -33,27 +34,30 @@ public abstract class ParentTypeAwareTypeInformation<S> extends TypeDiscoverer<S
/**
* Creates a new {@link ParentTypeAwareTypeInformation}.
*
* @param type
* @param parent
* @param map
* @param type must not be {@literal null}.
* @param parent must not be {@literal null}.
* @param map must not be {@literal null}.
*/
@SuppressWarnings("rawtypes")
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent, Map<TypeVariable, Type> map) {
super(type, map);
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
super(type, mergeMaps(parent, map));
this.parent = parent;
}
/**
* Considers the parent's type variable map before invoking the super class method.
* Merges the type variable maps of the given parent with the new map.
*
* @param parent must not be {@literal null}.
* @param map must not be {@literal null}.
* @return
*/
@Override
@SuppressWarnings("rawtypes")
protected Map<TypeVariable, Type> getTypeVariableMap() {
return parent == null ? super.getTypeVariableMap() : parent.getTypeVariableMap();
private static Map<TypeVariable<?>, Type> mergeMaps(TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
Map<TypeVariable<?>, Type> typeVariableMap = new HashMap<TypeVariable<?>, Type>();
typeVariableMap.putAll(map);
typeVariableMap.putAll(parent.getTypeVariableMap());
return typeVariableMap;
}
/*

View File

@@ -30,6 +30,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -47,7 +48,7 @@ import org.springframework.util.ReflectionUtils;
class TypeDiscoverer<S> implements TypeInformation<S> {
private final Type type;
@SuppressWarnings("rawtypes") private final Map<TypeVariable, Type> typeVariableMap;
private final Map<TypeVariable<?>, Type> typeVariableMap;
private final Map<String, TypeInformation<?>> fieldTypes = new ConcurrentHashMap<String, TypeInformation<?>>();
private Class<S> resolvedType;
@@ -55,25 +56,24 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
/**
* Creates a ne {@link TypeDiscoverer} for the given type, type variable map and parent.
*
* @param type must not be null.
* @param typeVariableMap
* @param type must not be {@literal null}.
* @param typeVariableMap must not be {@literal null}.
*/
@SuppressWarnings("rawtypes")
protected TypeDiscoverer(Type type, Map<TypeVariable, Type> typeVariableMap) {
protected TypeDiscoverer(Type type, Map<TypeVariable<?>, Type> typeVariableMap) {
Assert.notNull(type);
Assert.notNull(typeVariableMap);
this.type = type;
this.typeVariableMap = typeVariableMap;
}
/**
* Returns the type variable map. Will traverse the parents up to the root on and use it's map.
* Returns the type variable map.
*
* @return
*/
@SuppressWarnings("rawtypes")
protected Map<TypeVariable, Type> getTypeVariableMap() {
protected Map<TypeVariable<?>, Type> getTypeVariableMap() {
return typeVariableMap;
}
@@ -98,7 +98,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
if (fieldType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
return new ParameterizedTypeInformation(parameterizedType, this);
return new ParameterizedTypeInformation(parameterizedType, this, variableMap);
}
if (fieldType instanceof TypeVariable) {
@@ -107,7 +107,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
}
if (fieldType instanceof GenericArrayType) {
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this);
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this, variableMap);
}
if (fieldType instanceof WildcardType) {
@@ -135,9 +135,13 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
* @param type
* @return
*/
@SuppressWarnings("unchecked")
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Class<S> resolveType(Type type) {
return (Class<S>) GenericTypeResolver.resolveType(type, getTypeVariableMap());
Map<TypeVariable, Type> map = new HashMap<TypeVariable, Type>();
map.putAll(getTypeVariableMap());
return (Class<S>) GenericTypeResolver.resolveType(type, map);
}
/*

View File

@@ -43,11 +43,10 @@ class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
* @param owningType must not be {@literal null}
* @param parent
*/
@SuppressWarnings("rawtypes")
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent,
Map<TypeVariable, Type> map) {
Map<TypeVariable<?>, Type> typeVariableMap) {
super(variable, parent, map);
super(variable, parent, typeVariableMap);
Assert.notNull(variable);
this.variable = variable;
this.owningType = owningType;