DATACMNS-1135 - Fixed generics lookup for recursively nested container references.
Revert the merging of a parent type's type variable map and only apply the locally available declared generics in case of parameterized types. Moved that augmentation into ParameterizedTypeInformation.
This commit is contained in:
@@ -18,8 +18,6 @@ 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.
|
||||
@@ -36,12 +34,10 @@ class GenericArrayTypeInformation<S> extends ParentTypeAwareTypeInformation<S> {
|
||||
*
|
||||
* @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,
|
||||
Map<TypeVariable<?>, Type> typeVariableMap) {
|
||||
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent) {
|
||||
|
||||
super(type, parent, typeVariableMap);
|
||||
super(type, parent);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -45,13 +46,41 @@ 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,
|
||||
Map<TypeVariable<?>, Type> typeVariableMap) {
|
||||
public ParameterizedTypeInformation(ParameterizedType type, Class<?> resolvedType, TypeDiscoverer<?> parent) {
|
||||
|
||||
super(type, parent, typeVariableMap);
|
||||
super(type, parent, calculateTypeVariables(type, resolvedType, parent));
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the type variables to be used. Uses the parent's type variable map but overwrites variables locally
|
||||
* declared.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param resolvedType must not be {@literal null}.
|
||||
* @param parent must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static Map<TypeVariable<?>, Type> calculateTypeVariables(ParameterizedType type, Class<?> resolvedType,
|
||||
TypeDiscoverer<?> parent) {
|
||||
|
||||
TypeVariable<?>[] typeParameters = resolvedType.getTypeParameters();
|
||||
Type[] arguments = type.getActualTypeArguments();
|
||||
|
||||
Map<TypeVariable<?>, Type> localTypeVariables = new HashMap<TypeVariable<?>, Type>(parent.getTypeVariableMap());
|
||||
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
|
||||
Type value = arguments[i];
|
||||
|
||||
if (!(value instanceof TypeVariable)) {
|
||||
localTypeVariables.put(typeParameters[i], value);
|
||||
}
|
||||
}
|
||||
|
||||
return localTypeVariables;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType()
|
||||
@@ -123,8 +152,8 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T>
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeInformation<?> otherTypeInformation = rawType.equals(rawTargetType) ? target : target
|
||||
.getSuperTypeInformation(rawType);
|
||||
TypeInformation<?> otherTypeInformation = rawType.equals(rawTargetType) ? target
|
||||
: target.getSuperTypeInformation(rawType);
|
||||
|
||||
List<TypeInformation<?>> myParameters = getTypeArguments();
|
||||
List<TypeInformation<?>> typeParameters = otherTypeInformation.getTypeArguments();
|
||||
|
||||
@@ -17,7 +17,6 @@ package org.springframework.data.util;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -35,28 +34,23 @@ public abstract class ParentTypeAwareTypeInformation<S> extends TypeDiscoverer<S
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param parent must not be {@literal null}.
|
||||
* @param map must not be {@literal null}.
|
||||
*/
|
||||
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
|
||||
|
||||
super(type, mergeMaps(parent, map));
|
||||
this.parent = parent;
|
||||
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent) {
|
||||
this(type, parent, parent.getTypeVariableMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the type variable maps of the given parent with the new map.
|
||||
* Creates a new {@link ParentTypeAwareTypeInformation} with the given type variables.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param parent must not be {@literal null}.
|
||||
* @param map must not be {@literal null}.
|
||||
* @return
|
||||
* @param typeVariables must not be {@literal null}.
|
||||
*/
|
||||
private static Map<TypeVariable<?>, Type> mergeMaps(TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
|
||||
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent,
|
||||
Map<TypeVariable<?>, Type> typeVariables) {
|
||||
|
||||
Map<TypeVariable<?>, Type> typeVariableMap = new HashMap<TypeVariable<?>, Type>();
|
||||
typeVariableMap.putAll(map);
|
||||
typeVariableMap.putAll(parent.getTypeVariableMap());
|
||||
|
||||
return typeVariableMap;
|
||||
super(type, typeVariables);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -126,30 +126,19 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
}
|
||||
|
||||
Class<S> resolveType = resolveType(fieldType);
|
||||
Map<TypeVariable, Type> variableMap = new HashMap<TypeVariable, Type>();
|
||||
variableMap.putAll(GenericTypeResolver.getTypeVariableMap(resolveType));
|
||||
|
||||
if (fieldType instanceof ParameterizedType) {
|
||||
|
||||
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
|
||||
|
||||
TypeVariable<Class<S>>[] typeParameters = resolveType.getTypeParameters();
|
||||
Type[] arguments = parameterizedType.getActualTypeArguments();
|
||||
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
variableMap.put(typeParameters[i], arguments[i]);
|
||||
}
|
||||
|
||||
return new ParameterizedTypeInformation(parameterizedType, this, variableMap);
|
||||
return new ParameterizedTypeInformation(parameterizedType, resolveType, this);
|
||||
}
|
||||
|
||||
if (fieldType instanceof TypeVariable) {
|
||||
TypeVariable<?> variable = (TypeVariable<?>) fieldType;
|
||||
return new TypeVariableTypeInformation(variable, type, this, variableMap);
|
||||
return new TypeVariableTypeInformation(variable, type, this);
|
||||
}
|
||||
|
||||
if (fieldType instanceof GenericArrayType) {
|
||||
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this, variableMap);
|
||||
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this);
|
||||
}
|
||||
|
||||
if (fieldType instanceof WildcardType) {
|
||||
@@ -532,7 +521,8 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
@Override
|
||||
public TypeInformation<?> specialize(ClassTypeInformation<?> type) {
|
||||
|
||||
Assert.isTrue(getType().isAssignableFrom(type.getType()), String.format("%s must be assignable from %s", getType(), type.getType()));
|
||||
Assert.isTrue(getType().isAssignableFrom(type.getType()),
|
||||
String.format("%s must be assignable from %s", getType(), type.getType()));
|
||||
|
||||
List<TypeInformation<?>> arguments = getTypeArguments();
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import static org.springframework.util.ObjectUtils.*;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -41,13 +40,14 @@ class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
|
||||
*
|
||||
* @param variable must not be {@literal null}
|
||||
* @param owningType must not be {@literal null}
|
||||
* @param parent
|
||||
* @param parent must not be {@literal null}.
|
||||
*/
|
||||
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent,
|
||||
Map<TypeVariable<?>, Type> typeVariableMap) {
|
||||
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent) {
|
||||
|
||||
super(variable, parent);
|
||||
|
||||
super(variable, parent, typeVariableMap);
|
||||
Assert.notNull(variable, "TypeVariable must not be null!");
|
||||
|
||||
this.variable = variable;
|
||||
this.owningType = owningType;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ParameterizedTypeInformation}.
|
||||
@@ -47,6 +48,7 @@ public class ParameterizedTypeUnitTests {
|
||||
static final Map<TypeVariable<?>, Type> EMPTY_MAP = Collections.emptyMap();
|
||||
|
||||
@Mock ParameterizedType one;
|
||||
Class<?> resolvedOne = GenericTypeResolver.resolveType(one, Collections.<TypeVariable, Type> emptyMap());
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -59,8 +61,10 @@ public class ParameterizedTypeUnitTests {
|
||||
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, EMPTY_MAP);
|
||||
TypeDiscoverer<Object> objectParent = new TypeDiscoverer<Object>(Object.class, EMPTY_MAP);
|
||||
|
||||
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent, EMPTY_MAP);
|
||||
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, objectParent, EMPTY_MAP);
|
||||
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, resolvedOne,
|
||||
stringParent);
|
||||
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, resolvedOne,
|
||||
objectParent);
|
||||
|
||||
assertThat(first, is(not(second)));
|
||||
}
|
||||
@@ -70,8 +74,10 @@ public class ParameterizedTypeUnitTests {
|
||||
|
||||
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, EMPTY_MAP);
|
||||
|
||||
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent, EMPTY_MAP);
|
||||
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, stringParent, EMPTY_MAP);
|
||||
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, resolvedOne,
|
||||
stringParent);
|
||||
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, resolvedOne,
|
||||
stringParent);
|
||||
|
||||
assertTrue(first.equals(second));
|
||||
}
|
||||
@@ -124,7 +130,7 @@ public class ParameterizedTypeUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATACMNS-899
|
||||
public void returnsNullMapValueTypeForNonMapProperties(){
|
||||
public void returnsNullMapValueTypeForNonMapProperties() {
|
||||
|
||||
TypeInformation<?> valueType = ClassTypeInformation.from(Bar.class).getProperty("param");
|
||||
TypeInformation<?> mapValueType = valueType.getMapValueType();
|
||||
@@ -133,6 +139,17 @@ public class ParameterizedTypeUnitTests {
|
||||
assertThat(mapValueType, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1135
|
||||
public void prefersLocalGenericsDeclarationOverParentBound() {
|
||||
|
||||
ClassTypeInformation<Candidate> candidate = ClassTypeInformation.from(Candidate.class);
|
||||
|
||||
TypeInformation<?> componentType = candidate.getProperty("experiences.values").getComponentType();
|
||||
componentType = componentType.getProperty("responsibilities.values").getComponentType();
|
||||
|
||||
assertThat(componentType.getType(), is(typeCompatibleWith(Responsibility.class)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
class Localized<S> extends HashMap<Locale, S> {
|
||||
S value;
|
||||
@@ -180,4 +197,22 @@ public class ParameterizedTypeUnitTests {
|
||||
}
|
||||
|
||||
class Education {}
|
||||
|
||||
// DATACMNS-1135
|
||||
|
||||
abstract class CandidateInfo {}
|
||||
|
||||
class Responsibility extends CandidateInfo {}
|
||||
|
||||
class Experience extends CandidateInfo {
|
||||
CandidateInfoContainer<Responsibility> responsibilities;
|
||||
}
|
||||
|
||||
class CandidateInfoContainer<E extends CandidateInfo> {
|
||||
List<E> values;
|
||||
}
|
||||
|
||||
class Candidate {
|
||||
CandidateInfoContainer<Experience> experiences;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user