diff --git a/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java b/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java index b51ceb062..a7c271e7a 100644 --- a/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java +++ b/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java @@ -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 extends ParentTypeAwareTypeInformation { * * @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, Type> typeVariableMap) { + protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer parent) { - super(type, parent, typeVariableMap); + super(type, parent); this.type = type; } diff --git a/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java b/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java index 778a7cd89..d7075911c 100644 --- a/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java +++ b/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java @@ -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 extends ParentTypeAwareTypeInformation * @param type must not be {@literal null} * @param parent must not be {@literal null} */ - public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer parent, - Map, 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, Type> calculateTypeVariables(ParameterizedType type, Class resolvedType, + TypeDiscoverer parent) { + + TypeVariable[] typeParameters = resolvedType.getTypeParameters(); + Type[] arguments = type.getActualTypeArguments(); + + Map, Type> localTypeVariables = new HashMap, 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 extends ParentTypeAwareTypeInformation return false; } - TypeInformation otherTypeInformation = rawType.equals(rawTargetType) ? target : target - .getSuperTypeInformation(rawType); + TypeInformation otherTypeInformation = rawType.equals(rawTargetType) ? target + : target.getSuperTypeInformation(rawType); List> myParameters = getTypeArguments(); List> typeParameters = otherTypeInformation.getTypeArguments(); diff --git a/src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java b/src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java index 9072fe30a..a506aab6d 100644 --- a/src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java +++ b/src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java @@ -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 extends TypeDiscoverer parent, Map, 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, Type> mergeMaps(TypeDiscoverer parent, Map, Type> map) { + protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer parent, + Map, Type> typeVariables) { - Map, Type> typeVariableMap = new HashMap, Type>(); - typeVariableMap.putAll(map); - typeVariableMap.putAll(parent.getTypeVariableMap()); - - return typeVariableMap; + super(type, typeVariables); + this.parent = parent; } /* diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java index 8355db256..f131ce2ab 100644 --- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -126,30 +126,19 @@ class TypeDiscoverer implements TypeInformation { } Class resolveType = resolveType(fieldType); - Map variableMap = new HashMap(); - variableMap.putAll(GenericTypeResolver.getTypeVariableMap(resolveType)); if (fieldType instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) fieldType; - - TypeVariable>[] 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 implements TypeInformation { @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> arguments = getTypeArguments(); diff --git a/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java b/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java index b9dd5439b..1314cc4e3 100644 --- a/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java +++ b/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java @@ -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 extends ParentTypeAwareTypeInformation { * * @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, 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; } diff --git a/src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java b/src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java index 79528491b..24b37116d 100644 --- a/src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java +++ b/src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java @@ -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, Type> EMPTY_MAP = Collections.emptyMap(); @Mock ParameterizedType one; + Class resolvedOne = GenericTypeResolver.resolveType(one, Collections. emptyMap()); @Before public void setUp() { @@ -59,8 +61,10 @@ public class ParameterizedTypeUnitTests { TypeDiscoverer stringParent = new TypeDiscoverer(String.class, EMPTY_MAP); TypeDiscoverer objectParent = new TypeDiscoverer(Object.class, EMPTY_MAP); - ParameterizedTypeInformation first = new ParameterizedTypeInformation(one, stringParent, EMPTY_MAP); - ParameterizedTypeInformation second = new ParameterizedTypeInformation(one, objectParent, EMPTY_MAP); + ParameterizedTypeInformation first = new ParameterizedTypeInformation(one, resolvedOne, + stringParent); + ParameterizedTypeInformation second = new ParameterizedTypeInformation(one, resolvedOne, + objectParent); assertThat(first, is(not(second))); } @@ -70,8 +74,10 @@ public class ParameterizedTypeUnitTests { TypeDiscoverer stringParent = new TypeDiscoverer(String.class, EMPTY_MAP); - ParameterizedTypeInformation first = new ParameterizedTypeInformation(one, stringParent, EMPTY_MAP); - ParameterizedTypeInformation second = new ParameterizedTypeInformation(one, stringParent, EMPTY_MAP); + ParameterizedTypeInformation first = new ParameterizedTypeInformation(one, resolvedOne, + stringParent); + ParameterizedTypeInformation second = new ParameterizedTypeInformation(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 = 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 extends HashMap { 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 responsibilities; + } + + class CandidateInfoContainer { + List values; + } + + class Candidate { + CandidateInfoContainer experiences; + } }