Skip to content

Commit 8ce79c1

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 9c8aaac commit 8ce79c1

File tree

4 files changed

+73
-70
lines changed

4 files changed

+73
-70
lines changed

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

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -59,31 +59,6 @@ public ParameterizedTypeInformation(ParameterizedType type, Class<?> resolvedTyp
5959
this.resolved = Lazy.of(() -> isResolvedCompletely());
6060
}
6161

62-
/**
63-
* Resolves the type variables to be used. Uses the parent's type variable map but overwrites variables locally
64-
* declared.
65-
*
66-
* @param type must not be {@literal null}.
67-
* @param resolvedType must not be {@literal null}.
68-
* @param parent must not be {@literal null}.
69-
* @return
70-
*/
71-
private static Map<TypeVariable<?>, Type> calculateTypeVariables(ParameterizedType type, Class<?> resolvedType,
72-
TypeDiscoverer<?> parent) {
73-
74-
TypeVariable<?>[] typeParameters = resolvedType.getTypeParameters();
75-
Type[] arguments = type.getActualTypeArguments();
76-
77-
Map<TypeVariable<?>, Type> localTypeVariables = new HashMap<>(parent.getTypeVariableMap());
78-
79-
IntStream.range(0, typeParameters.length) //
80-
.mapToObj(it -> Pair.of(typeParameters[it], arguments[it])) //
81-
.filter(it -> !(it.getSecond() instanceof TypeVariable)) //
82-
.forEach(it -> localTypeVariables.put(it.getFirst(), it.getSecond()));
83-
84-
return localTypeVariables;
85-
}
86-
8762
/*
8863
* (non-Javadoc)
8964
* @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType()
@@ -267,4 +242,47 @@ private boolean isResolvedCompletely() {
267242

268243
return true;
269244
}
245+
246+
/**
247+
* Resolves the type variables to be used. Uses the parent's type variable map but overwrites variables locally
248+
* declared.
249+
*
250+
* @param type must not be {@literal null}.
251+
* @param resolvedType must not be {@literal null}.
252+
* @param parent must not be {@literal null}.
253+
* @return will never be {@literal null}.
254+
*/
255+
private static Map<TypeVariable<?>, Type> calculateTypeVariables(ParameterizedType type, Class<?> resolvedType,
256+
TypeDiscoverer<?> parent) {
257+
258+
TypeVariable<?>[] typeParameters = resolvedType.getTypeParameters();
259+
Type[] arguments = type.getActualTypeArguments();
260+
261+
Map<TypeVariable<?>, Type> localTypeVariables = new HashMap<>(parent.getTypeVariableMap());
262+
263+
IntStream.range(0, typeParameters.length) //
264+
.mapToObj(it -> Pair.of(typeParameters[it], flattenTypeVariable(arguments[it], localTypeVariables))) //
265+
.forEach(it -> localTypeVariables.put(it.getFirst(), it.getSecond()));
266+
267+
return localTypeVariables;
268+
}
269+
270+
/**
271+
* Recursively resolves the type bound to the given {@link Type} in case it's a {@link TypeVariable} and there's an
272+
* entry in the given type variables.
273+
*
274+
* @param source must not be {@literal null}.
275+
* @param variables must not be {@literal null}.
276+
* @return will never be {@literal null}.
277+
*/
278+
private static Type flattenTypeVariable(Type source, Map<TypeVariable<?>, Type> variables) {
279+
280+
if (!(source instanceof TypeVariable)) {
281+
return source;
282+
}
283+
284+
Type value = variables.get(source);
285+
286+
return value == null ? source : flattenTypeVariable(value, variables);
287+
}
270288
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ protected TypeInformation<?> createInfo(Type fieldType) {
140140
if (fieldType instanceof TypeVariable) {
141141

142142
TypeVariable<?> variable = (TypeVariable<?>) fieldType;
143-
return new TypeVariableTypeInformation(variable, type, this);
143+
return new TypeVariableTypeInformation(variable, this);
144144
}
145145

146146
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

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

3534
private final TypeVariable<?> variable;
36-
private final Type owningType;
3735

3836
/**
39-
* Creates a bew {@link TypeVariableTypeInformation} for the given {@link TypeVariable} owning {@link Type} and parent
37+
* Creates a new {@link TypeVariableTypeInformation} for the given {@link TypeVariable} owning {@link Type} and parent
4038
* {@link TypeDiscoverer}.
4139
*
4240
* @param variable must not be {@literal null}
4341
* @param owningType must not be {@literal null}
4442
* @param parent
4543
*/
46-
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent) {
44+
public TypeVariableTypeInformation(TypeVariable<?> variable, TypeDiscoverer<?> parent) {
4745

4846
super(variable, parent);
4947

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

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

9353
/*

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
@@ -41,7 +41,7 @@
4141
* @author Mark Paluch
4242
*/
4343
@RunWith(MockitoJUnitRunner.class)
44-
public class ParameterizedTypeUnitTests {
44+
public class ParameterizedTypeInformationUnitTests {
4545

4646
@Mock ParameterizedType one;
4747
Class<?> resolvedOne;
@@ -96,7 +96,7 @@ public void resolvesMapValueTypeCorrectly() {
9696
public void createsToStringRepresentation() {
9797

9898
assertThat(from(Foo.class).getProperty("param").toString())
99-
.isEqualTo("org.springframework.data.util.ParameterizedTypeUnitTests$Localized<java.lang.String>");
99+
.isEqualTo("org.springframework.data.util.ParameterizedTypeInformationUnitTests$Localized<java.lang.String>");
100100
}
101101

102102
@Test // DATACMNS-485
@@ -147,6 +147,14 @@ public void prefersLocalGenericsDeclarationOverParentBound() {
147147
assertThat(componentType.getType()).isEqualTo(Responsibility.class);
148148
}
149149

150+
@Test // DATACMNS-1196
151+
public void detectsNestedGenerics() {
152+
153+
TypeInformation<?> myList = ClassTypeInformation.from(EnumGeneric.class).getRequiredProperty("inner.myList");
154+
155+
assertThat(myList.getRequiredComponentType().getType()).isEqualTo(MyEnum.class);
156+
}
157+
150158
@SuppressWarnings("serial")
151159
class Localized<S> extends HashMap<Locale, S> {
152160
S value;
@@ -212,4 +220,21 @@ class CandidateInfoContainer<E extends CandidateInfo> {
212220
class Candidate {
213221
CandidateInfoContainer<Experience> experiences;
214222
}
223+
224+
// FOO
225+
226+
static abstract class Generic<T> {
227+
228+
Inner<T> inner;
229+
230+
static class Inner<T> {
231+
List<T> myList;
232+
}
233+
}
234+
235+
static class EnumGeneric extends Generic<MyEnum> {}
236+
237+
public enum MyEnum {
238+
E1, E2
239+
}
215240
}

0 commit comments

Comments
 (0)