Skip to content

Commit d0d0d72

Browse files
committed
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.
1 parent 91fd1a4 commit d0d0d72

File tree

4 files changed

+73
-74
lines changed

4 files changed

+73
-74
lines changed

src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -52,35 +52,6 @@ public ParameterizedTypeInformation(ParameterizedType type, Class<?> resolvedTyp
5252
this.type = type;
5353
}
5454

55-
/**
56-
* Resolves the type variables to be used. Uses the parent's type variable map but overwrites variables locally
57-
* declared.
58-
*
59-
* @param type must not be {@literal null}.
60-
* @param resolvedType must not be {@literal null}.
61-
* @param parent must not be {@literal null}.
62-
* @return
63-
*/
64-
private static Map<TypeVariable<?>, Type> calculateTypeVariables(ParameterizedType type, Class<?> resolvedType,
65-
TypeDiscoverer<?> parent) {
66-
67-
TypeVariable<?>[] typeParameters = resolvedType.getTypeParameters();
68-
Type[] arguments = type.getActualTypeArguments();
69-
70-
Map<TypeVariable<?>, Type> localTypeVariables = new HashMap<TypeVariable<?>, Type>(parent.getTypeVariableMap());
71-
72-
for (int i = 0; i < typeParameters.length; i++) {
73-
74-
Type value = arguments[i];
75-
76-
if (!(value instanceof TypeVariable)) {
77-
localTypeVariables.put(typeParameters[i], value);
78-
}
79-
}
80-
81-
return localTypeVariables;
82-
}
83-
8455
/*
8556
* (non-Javadoc)
8657
* @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType()
@@ -268,4 +239,47 @@ private boolean cacheAndReturn(boolean resolved) {
268239
this.resolved = resolved;
269240
return resolved;
270241
}
242+
243+
/**
244+
* Resolves the type variables to be used. Uses the parent's type variable map but overwrites variables locally
245+
* declared.
246+
*
247+
* @param type must not be {@literal null}.
248+
* @param resolvedType must not be {@literal null}.
249+
* @param parent must not be {@literal null}.
250+
* @return will never be {@literal null}.
251+
*/
252+
private static Map<TypeVariable<?>, Type> calculateTypeVariables(ParameterizedType type, Class<?> resolvedType,
253+
TypeDiscoverer<?> parent) {
254+
255+
TypeVariable<?>[] typeParameters = resolvedType.getTypeParameters();
256+
Type[] arguments = type.getActualTypeArguments();
257+
258+
Map<TypeVariable<?>, Type> localTypeVariables = new HashMap<TypeVariable<?>, Type>(parent.getTypeVariableMap());
259+
260+
for (int i = 0; i < typeParameters.length; i++) {
261+
localTypeVariables.put(typeParameters[i], flattenTypeVariable(arguments[i], localTypeVariables));
262+
}
263+
264+
return localTypeVariables;
265+
}
266+
267+
/**
268+
* Recursively resolves the type bound to the given {@link Type} in case it's a {@link TypeVariable} and there's an
269+
* entry in the given type variables.
270+
*
271+
* @param source must not be {@literal null}.
272+
* @param variables must not be {@literal null}.
273+
* @return will never be {@literal null}.
274+
*/
275+
private static Type flattenTypeVariable(Type source, Map<TypeVariable<?>, Type> variables) {
276+
277+
if (!(source instanceof TypeVariable)) {
278+
return source;
279+
}
280+
281+
Type value = variables.get(source);
282+
283+
return value == null ? source : flattenTypeVariable(value, variables);
284+
}
271285
}

src/main/java/org/springframework/data/util/TypeDiscoverer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ protected TypeInformation<?> createInfo(Type fieldType) {
134134

135135
if (fieldType instanceof TypeVariable) {
136136
TypeVariable<?> variable = (TypeVariable<?>) fieldType;
137-
return new TypeVariableTypeInformation(variable, type, this);
137+
return new TypeVariableTypeInformation(variable, this);
138138
}
139139

140140
if (fieldType instanceof GenericArrayType) {

src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import static org.springframework.util.ObjectUtils.*;
1919

20-
import java.lang.reflect.ParameterizedType;
2120
import java.lang.reflect.Type;
2221
import java.lang.reflect.TypeVariable;
2322

@@ -32,61 +31,22 @@
3231
class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
3332

3433
private final TypeVariable<?> variable;
35-
private final Type owningType;
3634

3735
/**
38-
* Creates a bew {@link TypeVariableTypeInformation} for the given {@link TypeVariable} owning {@link Type} and parent
36+
* Creates a new {@link TypeVariableTypeInformation} for the given {@link TypeVariable} owning {@link Type} and parent
3937
* {@link TypeDiscoverer}.
4038
*
4139
* @param variable must not be {@literal null}
4240
* @param owningType must not be {@literal null}
4341
* @param parent must not be {@literal null}.
4442
*/
45-
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent) {
43+
public TypeVariableTypeInformation(TypeVariable<?> variable, TypeDiscoverer<?> parent) {
4644

4745
super(variable, parent);
4846

4947
Assert.notNull(variable, "TypeVariable must not be null!");
5048

5149
this.variable = variable;
52-
this.owningType = owningType;
53-
}
54-
55-
/*
56-
* (non-Javadoc)
57-
* @see org.springframework.data.util.TypeDiscoverer#getType()
58-
*/
59-
@Override
60-
public Class<T> getType() {
61-
62-
int index = getIndex(variable);
63-
64-
if (owningType instanceof ParameterizedType && index != -1) {
65-
Type fieldType = ((ParameterizedType) owningType).getActualTypeArguments()[index];
66-
return resolveType(fieldType);
67-
}
68-
69-
return resolveType(variable);
70-
}
71-
72-
/**
73-
* Returns the index of the type parameter binding the given {@link TypeVariable}.
74-
*
75-
* @param variable
76-
* @return
77-
*/
78-
private int getIndex(TypeVariable<?> variable) {
79-
80-
Class<?> rawType = resolveType(owningType);
81-
TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
82-
83-
for (int i = 0; i < typeParameters.length; i++) {
84-
if (variable.equals(typeParameters[i])) {
85-
return i;
86-
}
87-
}
88-
89-
return -1;
9050
}
9151

9252
/*

src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java renamed to src/test/java/org/springframework/data/util/ParameterizedTypeInformationUnitTests.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
* @author Mark Paluch
4444
*/
4545
@RunWith(MockitoJUnitRunner.class)
46-
public class ParameterizedTypeUnitTests {
46+
public class ParameterizedTypeInformationUnitTests {
4747

4848
static final Map<TypeVariable<?>, Type> EMPTY_MAP = Collections.emptyMap();
4949

@@ -99,7 +99,7 @@ public void resolvesMapValueTypeCorrectly() {
9999
public void createsToStringRepresentation() {
100100

101101
assertThat(from(Foo.class).getProperty("param").toString(),
102-
is("org.springframework.data.util.ParameterizedTypeUnitTests$Localized<java.lang.String>"));
102+
is("org.springframework.data.util.ParameterizedTypeInformationUnitTests$Localized<java.lang.String>"));
103103
}
104104

105105
@Test // DATACMNS-485
@@ -150,6 +150,14 @@ public void prefersLocalGenericsDeclarationOverParentBound() {
150150
assertThat(componentType.getType(), is(typeCompatibleWith(Responsibility.class)));
151151
}
152152

153+
@Test // DATACMNS-1196
154+
public void detectsNestedGenerics() {
155+
156+
TypeInformation<?> myList = ClassTypeInformation.from(EnumGeneric.class).getProperty("inner.myList");
157+
158+
assertThat(myList.getComponentType().getType(), is(typeCompatibleWith(MyEnum.class)));
159+
}
160+
153161
@SuppressWarnings("serial")
154162
class Localized<S> extends HashMap<Locale, S> {
155163
S value;
@@ -215,4 +223,21 @@ class CandidateInfoContainer<E extends CandidateInfo> {
215223
class Candidate {
216224
CandidateInfoContainer<Experience> experiences;
217225
}
226+
227+
// FOO
228+
229+
static abstract class Generic<T> {
230+
231+
Inner<T> inner;
232+
233+
static class Inner<T> {
234+
List<T> myList;
235+
}
236+
}
237+
238+
static class EnumGeneric extends Generic<MyEnum> {}
239+
240+
public enum MyEnum {
241+
E1, E2
242+
}
218243
}

0 commit comments

Comments
 (0)