diff --git a/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java b/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java index 7eb2db7fb..1bb6d514f 100644 --- a/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java +++ b/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java @@ -59,31 +59,6 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation this.resolved = Lazy.of(() -> isResolvedCompletely()); } - /** - * 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<>(parent.getTypeVariableMap()); - - IntStream.range(0, typeParameters.length) // - .mapToObj(it -> Pair.of(typeParameters[it], arguments[it])) // - .filter(it -> !(it.getSecond() instanceof TypeVariable)) // - .forEach(it -> localTypeVariables.put(it.getFirst(), it.getSecond())); - - return localTypeVariables; - } - /* * (non-Javadoc) * @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType() @@ -267,4 +242,47 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation return true; } + + /** + * 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<>(parent.getTypeVariableMap()); + + IntStream.range(0, typeParameters.length) // + .mapToObj(it -> Pair.of(typeParameters[it], flattenTypeVariable(arguments[it], localTypeVariables))) // + .forEach(it -> localTypeVariables.put(it.getFirst(), it.getSecond())); + + 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 bb193f8f2..77ed83334 100644 --- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -140,7 +140,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 043487641..265e74586 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; @@ -33,61 +32,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 */ - 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 cd19b4b6e..b6f5a04ed 100755 --- a/src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java +++ b/src/test/java/org/springframework/data/util/ParameterizedTypeInformationUnitTests.java @@ -41,7 +41,7 @@ import org.springframework.core.GenericTypeResolver; * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) -public class ParameterizedTypeUnitTests { +public class ParameterizedTypeInformationUnitTests { @Mock ParameterizedType one; Class resolvedOne; @@ -96,7 +96,7 @@ public class ParameterizedTypeUnitTests { public void createsToStringRepresentation() { assertThat(from(Foo.class).getProperty("param").toString()) - .isEqualTo("org.springframework.data.util.ParameterizedTypeUnitTests$Localized"); + .isEqualTo("org.springframework.data.util.ParameterizedTypeInformationUnitTests$Localized"); } @Test // DATACMNS-485 @@ -147,6 +147,14 @@ public class ParameterizedTypeUnitTests { assertThat(componentType.getType()).isEqualTo(Responsibility.class); } + @Test // DATACMNS-1196 + public void detectsNestedGenerics() { + + TypeInformation myList = ClassTypeInformation.from(EnumGeneric.class).getRequiredProperty("inner.myList"); + + assertThat(myList.getRequiredComponentType().getType()).isEqualTo(MyEnum.class); + } + @SuppressWarnings("serial") class Localized extends HashMap { S value; @@ -212,4 +220,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 + } }