Browse Source

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.
pull/240/head
Oliver Gierke 9 years ago
parent
commit
e7434a0e77
  1. 8
      src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java
  2. 39
      src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java
  3. 24
      src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java
  4. 20
      src/main/java/org/springframework/data/util/TypeDiscoverer.java
  5. 10
      src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java
  6. 45
      src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java

8
src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java

@ -18,8 +18,6 @@ package org.springframework.data.util; @@ -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> { @@ -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;
}

39
src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java

@ -20,6 +20,7 @@ import java.lang.reflect.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> @@ -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> @@ -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();

24
src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java

@ -17,7 +17,6 @@ package org.springframework.data.util; @@ -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 @@ -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;
}
/*

20
src/main/java/org/springframework/data/util/TypeDiscoverer.java

@ -126,30 +126,19 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -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> { @@ -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();

10
src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java

@ -20,7 +20,6 @@ import static org.springframework.util.ObjectUtils.*; @@ -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> { @@ -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;
}

45
src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java

@ -34,6 +34,7 @@ import org.junit.Test; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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;
}
}

Loading…
Cancel
Save