diff --git a/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java b/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java index 88a8a2fc4..798de32e0 100644 --- a/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java +++ b/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java @@ -52,35 +52,6 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation 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() @@ -268,4 +239,47 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation this.resolved = resolved; return resolved; } + + /** + * 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 will never be {@literal null}. + */ + 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++) { + localTypeVariables.put(typeParameters[i], flattenTypeVariable(arguments[i], localTypeVariables)); + } + + return localTypeVariables; + } + + /** + * Recursively resolves the type bound to the given {@link Type} in case it's a {@link TypeVariable} and there's an + * entry in the given type variables. + * + * @param source must not be {@literal null}. + * @param variables must not be {@literal null}. + * @return will never be {@literal null}. + */ + private static Type flattenTypeVariable(Type source, Map, Type> variables) { + + if (!(source instanceof TypeVariable)) { + return source; + } + + Type value = variables.get(source); + + return value == null ? source : flattenTypeVariable(value, variables); + } } diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java index f131ce2ab..b096ff842 100644 --- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -134,7 +134,7 @@ class TypeDiscoverer implements TypeInformation { if (fieldType instanceof TypeVariable) { TypeVariable variable = (TypeVariable) fieldType; - return new TypeVariableTypeInformation(variable, type, this); + return new TypeVariableTypeInformation(variable, this); } if (fieldType instanceof GenericArrayType) { diff --git a/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java b/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java index 1314cc4e3..cc3db7a04 100644 --- a/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java +++ b/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java @@ -17,7 +17,6 @@ package org.springframework.data.util; import static org.springframework.util.ObjectUtils.*; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -32,61 +31,22 @@ import org.springframework.util.Assert; class TypeVariableTypeInformation extends ParentTypeAwareTypeInformation { private final TypeVariable variable; - private final Type owningType; /** - * Creates a bew {@link TypeVariableTypeInformation} for the given {@link TypeVariable} owning {@link Type} and parent + * Creates a new {@link TypeVariableTypeInformation} for the given {@link TypeVariable} owning {@link Type} and parent * {@link TypeDiscoverer}. * * @param variable must not be {@literal null} * @param owningType must not be {@literal null} * @param parent must not be {@literal null}. */ - public TypeVariableTypeInformation(TypeVariable variable, Type owningType, TypeDiscoverer parent) { + public TypeVariableTypeInformation(TypeVariable variable, TypeDiscoverer parent) { super(variable, parent); Assert.notNull(variable, "TypeVariable must not be null!"); this.variable = variable; - this.owningType = owningType; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.util.TypeDiscoverer#getType() - */ - @Override - public Class getType() { - - int index = getIndex(variable); - - if (owningType instanceof ParameterizedType && index != -1) { - Type fieldType = ((ParameterizedType) owningType).getActualTypeArguments()[index]; - return resolveType(fieldType); - } - - return resolveType(variable); - } - - /** - * Returns the index of the type parameter binding the given {@link TypeVariable}. - * - * @param variable - * @return - */ - private int getIndex(TypeVariable variable) { - - Class rawType = resolveType(owningType); - TypeVariable[] typeParameters = rawType.getTypeParameters(); - - for (int i = 0; i < typeParameters.length; i++) { - if (variable.equals(typeParameters[i])) { - return i; - } - } - - return -1; } /* diff --git a/src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java b/src/test/java/org/springframework/data/util/ParameterizedTypeInformationUnitTests.java similarity index 90% rename from src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java rename to src/test/java/org/springframework/data/util/ParameterizedTypeInformationUnitTests.java index 24b37116d..406b4d64f 100644 --- a/src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java +++ b/src/test/java/org/springframework/data/util/ParameterizedTypeInformationUnitTests.java @@ -43,7 +43,7 @@ import org.springframework.core.GenericTypeResolver; * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) -public class ParameterizedTypeUnitTests { +public class ParameterizedTypeInformationUnitTests { static final Map, Type> EMPTY_MAP = Collections.emptyMap(); @@ -99,7 +99,7 @@ public class ParameterizedTypeUnitTests { public void createsToStringRepresentation() { assertThat(from(Foo.class).getProperty("param").toString(), - is("org.springframework.data.util.ParameterizedTypeUnitTests$Localized")); + is("org.springframework.data.util.ParameterizedTypeInformationUnitTests$Localized")); } @Test // DATACMNS-485 @@ -150,6 +150,14 @@ public class ParameterizedTypeUnitTests { assertThat(componentType.getType(), is(typeCompatibleWith(Responsibility.class))); } + @Test // DATACMNS-1196 + public void detectsNestedGenerics() { + + TypeInformation myList = ClassTypeInformation.from(EnumGeneric.class).getProperty("inner.myList"); + + assertThat(myList.getComponentType().getType(), is(typeCompatibleWith(MyEnum.class))); + } + @SuppressWarnings("serial") class Localized extends HashMap { S value; @@ -215,4 +223,21 @@ public class ParameterizedTypeUnitTests { class Candidate { CandidateInfoContainer experiences; } + + // FOO + + static abstract class Generic { + + Inner inner; + + static class Inner { + List myList; + } + } + + static class EnumGeneric extends Generic {} + + public enum MyEnum { + E1, E2 + } }