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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user