DATACMNS-1196 - Fixed generics lookup for nested generics in ParameterizedTypeInformation.

We now eagerly resolve a generics declaration chain, which we previously - erroneously - expected GenericTypeResolver to do for us. Simplified TypeVariableTypeInformation implementation. Renamed ParameterizedTypeUnitTests to ParameterizedTypeInformationUnitTests.
This commit is contained in:
Oliver Gierke
2017-10-12 18:04:32 +02:00
parent 91fd1a4caf
commit d0d0d72c98
4 changed files with 73 additions and 74 deletions

View File

@@ -52,35 +52,6 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T>
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()
@@ -268,4 +239,47 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T>
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<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++) {
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<TypeVariable<?>, Type> variables) {
if (!(source instanceof TypeVariable)) {
return source;
}
Type value = variables.get(source);
return value == null ? source : flattenTypeVariable(value, variables);
}
}

View File

@@ -134,7 +134,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
if (fieldType instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) fieldType;
return new TypeVariableTypeInformation(variable, type, this);
return new TypeVariableTypeInformation(variable, this);
}
if (fieldType instanceof GenericArrayType) {

View File

@@ -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<T> extends ParentTypeAwareTypeInformation<T> {
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<T> 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;
}
/*

View File

@@ -43,7 +43,7 @@ import org.springframework.core.GenericTypeResolver;
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class ParameterizedTypeUnitTests {
public class ParameterizedTypeInformationUnitTests {
static final Map<TypeVariable<?>, 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<java.lang.String>"));
is("org.springframework.data.util.ParameterizedTypeInformationUnitTests$Localized<java.lang.String>"));
}
@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<S> extends HashMap<Locale, S> {
S value;
@@ -215,4 +223,21 @@ public class ParameterizedTypeUnitTests {
class Candidate {
CandidateInfoContainer<Experience> experiences;
}
// FOO
static abstract class Generic<T> {
Inner<T> inner;
static class Inner<T> {
List<T> myList;
}
}
static class EnumGeneric extends Generic<MyEnum> {}
public enum MyEnum {
E1, E2
}
}