Skip to content

Commit ca377dc

Browse files
committed
DATACMNS-590 - Fixed calculation of nested generics in ParentTypeAwareTypeInformation.
So far, the lookup of the type variable map was preferring the type variable maps detected on parent types in nested structures. This caused the concrete type variables in nested types not being considered correctly which caused the type resolution to fall back to the generic bounds. We now accumulate the type variable maps to avoid having to lookup a certain map in the nesting hierarchy. The core fix is in ParentTypeAwareTypeInformation's constructor and mergeMaps(…) respectively. Simplified the handling of type variable maps and made proper use of generics throughout the class hierarchy.
1 parent e02cecd commit ca377dc

9 files changed

+152
-70
lines changed

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

+20-4
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import java.util.Arrays;
2424
import java.util.Collection;
2525
import java.util.Collections;
26+
import java.util.HashMap;
2627
import java.util.List;
2728
import java.util.Map;
29+
import java.util.Map.Entry;
2830
import java.util.Set;
2931
import java.util.WeakHashMap;
3032

@@ -98,12 +100,26 @@ public static <S> TypeInformation<S> fromReturnTypeOf(Method method) {
98100
* @param type
99101
*/
100102
ClassTypeInformation(Class<S> type) {
101-
this(type, GenericTypeResolver.getTypeVariableMap(type));
103+
super(ClassUtils.getUserClass(type), getTypeVariableMap(type));
104+
this.type = type;
102105
}
103106

104-
ClassTypeInformation(Class<S> type, Map<TypeVariable, Type> typeVariableMap) {
105-
super(ClassUtils.getUserClass(type), typeVariableMap);
106-
this.type = type;
107+
/**
108+
* Little helper to allow us to create a generified map, actually just to satisfy the compiler.
109+
*
110+
* @param type must not be {@literal null}.
111+
* @return
112+
*/
113+
private static Map<TypeVariable<?>, Type> getTypeVariableMap(Class<?> type) {
114+
115+
Map<TypeVariable, Type> source = GenericTypeResolver.getTypeVariableMap(type);
116+
Map<TypeVariable<?>, Type> map = new HashMap<TypeVariable<?>, Type>(source.size());
117+
118+
for (Entry<TypeVariable, Type> entry : source.entrySet()) {
119+
map.put(entry.getKey(), entry.getValue());
120+
}
121+
122+
return map;
107123
}
108124

109125
/*

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.lang.reflect.Array;
1919
import java.lang.reflect.GenericArrayType;
2020
import java.lang.reflect.Type;
21+
import java.lang.reflect.TypeVariable;
22+
import java.util.Map;
2123

2224
/**
2325
* Special {@link TypeDiscoverer} handling {@link GenericArrayType}s.
@@ -26,18 +28,20 @@
2628
*/
2729
class GenericArrayTypeInformation<S> extends ParentTypeAwareTypeInformation<S> {
2830

29-
private GenericArrayType type;
31+
private final GenericArrayType type;
3032

3133
/**
3234
* Creates a new {@link GenericArrayTypeInformation} for the given {@link GenericArrayTypeInformation} and
3335
* {@link TypeDiscoverer}.
3436
*
35-
* @param type
36-
* @param parent
37+
* @param type must not be {@literal null}.
38+
* @param parent must not be {@literal null}.
39+
* @param typeVariableMap must not be {@literal null}.
3740
*/
38-
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent) {
41+
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent,
42+
Map<TypeVariable<?>, Type> typeVariableMap) {
3943

40-
super(type, parent, parent.getTypeVariableMap());
44+
super(type, parent, typeVariableMap);
4145
this.type = type;
4246
}
4347

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717

1818
import java.lang.reflect.ParameterizedType;
1919
import java.lang.reflect.Type;
20+
import java.lang.reflect.TypeVariable;
2021
import java.util.ArrayList;
2122
import java.util.Arrays;
2223
import java.util.HashSet;
2324
import java.util.List;
2425
import java.util.Map;
2526
import java.util.Set;
2627

27-
import org.springframework.core.GenericTypeResolver;
2828
import org.springframework.util.StringUtils;
2929

3030
/**
@@ -44,8 +44,10 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T>
4444
* @param type must not be {@literal null}
4545
* @param parent must not be {@literal null}
4646
*/
47-
public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> parent) {
48-
super(type, parent, null);
47+
public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> parent,
48+
Map<TypeVariable<?>, Type> typeVariableMap) {
49+
50+
super(type, parent, typeVariableMap);
4951
this.type = type;
5052
}
5153

@@ -72,8 +74,11 @@ public TypeInformation<?> getMapValueType() {
7274
supertypes.addAll(Arrays.asList(rawType.getGenericInterfaces()));
7375

7476
for (Type supertype : supertypes) {
75-
Class<?> rawSuperType = GenericTypeResolver.resolveType(supertype, getTypeVariableMap());
77+
78+
Class<?> rawSuperType = resolveType(supertype);
79+
7680
if (Map.class.isAssignableFrom(rawSuperType)) {
81+
7782
ParameterizedType parameterizedSupertype = (ParameterizedType) supertype;
7883
Type[] arguments = parameterizedSupertype.getActualTypeArguments();
7984
return createInfo(arguments[1]);

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

+16-12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.lang.reflect.Type;
1919
import java.lang.reflect.TypeVariable;
20+
import java.util.HashMap;
2021
import java.util.Map;
2122

2223
import org.springframework.util.ObjectUtils;
@@ -33,27 +34,30 @@ public abstract class ParentTypeAwareTypeInformation<S> extends TypeDiscoverer<S
3334
/**
3435
* Creates a new {@link ParentTypeAwareTypeInformation}.
3536
*
36-
* @param type
37-
* @param parent
38-
* @param map
37+
* @param type must not be {@literal null}.
38+
* @param parent must not be {@literal null}.
39+
* @param map must not be {@literal null}.
3940
*/
40-
@SuppressWarnings("rawtypes")
41-
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent, Map<TypeVariable, Type> map) {
42-
43-
super(type, map);
41+
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
4442

43+
super(type, mergeMaps(parent, map));
4544
this.parent = parent;
4645
}
4746

4847
/**
49-
* Considers the parent's type variable map before invoking the super class method.
48+
* Merges the type variable maps of the given parent with the new map.
5049
*
50+
* @param parent must not be {@literal null}.
51+
* @param map must not be {@literal null}.
5152
* @return
5253
*/
53-
@Override
54-
@SuppressWarnings("rawtypes")
55-
protected Map<TypeVariable, Type> getTypeVariableMap() {
56-
return parent == null ? super.getTypeVariableMap() : parent.getTypeVariableMap();
54+
private static Map<TypeVariable<?>, Type> mergeMaps(TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
55+
56+
Map<TypeVariable<?>, Type> typeVariableMap = new HashMap<TypeVariable<?>, Type>();
57+
typeVariableMap.putAll(map);
58+
typeVariableMap.putAll(parent.getTypeVariableMap());
59+
60+
return typeVariableMap;
5761
}
5862

5963
/*

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

+17-13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Arrays;
3131
import java.util.Collection;
3232
import java.util.Collections;
33+
import java.util.HashMap;
3334
import java.util.List;
3435
import java.util.Map;
3536
import java.util.concurrent.ConcurrentHashMap;
@@ -47,33 +48,32 @@
4748
class TypeDiscoverer<S> implements TypeInformation<S> {
4849

4950
private final Type type;
50-
@SuppressWarnings("rawtypes") private final Map<TypeVariable, Type> typeVariableMap;
51+
private final Map<TypeVariable<?>, Type> typeVariableMap;
5152
private final Map<String, TypeInformation<?>> fieldTypes = new ConcurrentHashMap<String, TypeInformation<?>>();
5253

5354
private Class<S> resolvedType;
5455

5556
/**
5657
* Creates a ne {@link TypeDiscoverer} for the given type, type variable map and parent.
5758
*
58-
* @param type must not be null.
59-
* @param typeVariableMap
59+
* @param type must not be {@literal null}.
60+
* @param typeVariableMap must not be {@literal null}.
6061
*/
61-
@SuppressWarnings("rawtypes")
62-
protected TypeDiscoverer(Type type, Map<TypeVariable, Type> typeVariableMap) {
62+
protected TypeDiscoverer(Type type, Map<TypeVariable<?>, Type> typeVariableMap) {
6363

6464
Assert.notNull(type);
65+
Assert.notNull(typeVariableMap);
66+
6567
this.type = type;
6668
this.typeVariableMap = typeVariableMap;
6769
}
6870

6971
/**
70-
* Returns the type variable map. Will traverse the parents up to the root on and use it's map.
72+
* Returns the type variable map.
7173
*
7274
* @return
7375
*/
74-
@SuppressWarnings("rawtypes")
75-
protected Map<TypeVariable, Type> getTypeVariableMap() {
76-
76+
protected Map<TypeVariable<?>, Type> getTypeVariableMap() {
7777
return typeVariableMap;
7878
}
7979

@@ -98,7 +98,7 @@ protected TypeInformation<?> createInfo(Type fieldType) {
9898

9999
if (fieldType instanceof ParameterizedType) {
100100
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
101-
return new ParameterizedTypeInformation(parameterizedType, this);
101+
return new ParameterizedTypeInformation(parameterizedType, this, variableMap);
102102
}
103103

104104
if (fieldType instanceof TypeVariable) {
@@ -107,7 +107,7 @@ protected TypeInformation<?> createInfo(Type fieldType) {
107107
}
108108

109109
if (fieldType instanceof GenericArrayType) {
110-
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this);
110+
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this, variableMap);
111111
}
112112

113113
if (fieldType instanceof WildcardType) {
@@ -135,9 +135,13 @@ protected TypeInformation<?> createInfo(Type fieldType) {
135135
* @param type
136136
* @return
137137
*/
138-
@SuppressWarnings("unchecked")
138+
@SuppressWarnings({ "unchecked", "rawtypes" })
139139
protected Class<S> resolveType(Type type) {
140-
return (Class<S>) GenericTypeResolver.resolveType(type, getTypeVariableMap());
140+
141+
Map<TypeVariable, Type> map = new HashMap<TypeVariable, Type>();
142+
map.putAll(getTypeVariableMap());
143+
144+
return (Class<S>) GenericTypeResolver.resolveType(type, map);
141145
}
142146

143147
/*

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,10 @@ class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
4343
* @param owningType must not be {@literal null}
4444
* @param parent
4545
*/
46-
@SuppressWarnings("rawtypes")
4746
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent,
48-
Map<TypeVariable, Type> map) {
47+
Map<TypeVariable<?>, Type> typeVariableMap) {
4948

50-
super(variable, parent, map);
49+
super(variable, parent, typeVariableMap);
5150
Assert.notNull(variable);
5251
this.variable = variable;
5352
this.owningType = owningType;

src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java

+35-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public void discoversArraysAndCollections() {
9393
TypeInformation<StringCollectionContainer> information = ClassTypeInformation.from(StringCollectionContainer.class);
9494

9595
TypeInformation<?> property = information.getProperty("array");
96-
assertEquals(property.getComponentType().getType(), String.class);
96+
assertThat(property.getComponentType().getType(), is((Object) String.class));
9797

9898
Class<?> type = property.getType();
9999
assertEquals(String[].class, type);
@@ -327,6 +327,20 @@ public void createsToStringRepresentation() {
327327
is("org.springframework.data.util.ClassTypeInformationUnitTests$SpecialPerson"));
328328
}
329329

330+
/**
331+
* @see DATACMNS-590
332+
*/
333+
@Test
334+
public void resolvesNestedGenericsToConcreteType() {
335+
336+
ClassTypeInformation<ConcreteRoot> rootType = from(ConcreteRoot.class);
337+
TypeInformation<?> subsPropertyType = rootType.getProperty("subs");
338+
TypeInformation<?> subsElementType = subsPropertyType.getActualType();
339+
TypeInformation<?> subSubType = subsElementType.getProperty("subSub");
340+
341+
assertThat(subSubType.getType(), is((Object) ConcreteSubSub.class));
342+
}
343+
330344
static class StringMapContainer extends MapContainer<String> {
331345

332346
}
@@ -461,4 +475,24 @@ static class SuperGenerics {
461475

462476
SortedMap<String, ? extends SortedMap<String, List<Person>>> seriously;
463477
}
478+
479+
// DATACMNS-590
480+
481+
static abstract class GenericRoot<T extends GenericSub<?>> {
482+
List<T> subs;
483+
}
484+
485+
static abstract class GenericSub<T extends GenericSubSub> {
486+
T subSub;
487+
}
488+
489+
static abstract class GenericSubSub {}
490+
491+
static class ConcreteRoot extends GenericRoot<ConcreteSub> {}
492+
493+
static class ConcreteSub extends GenericSub<ConcreteSubSub> {}
494+
495+
static class ConcreteSubSub extends GenericSubSub {
496+
String content;
497+
}
464498
}

src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011 the original author or authors.
2+
* Copyright 2011-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,8 +22,11 @@
2222

2323
import java.lang.reflect.ParameterizedType;
2424
import java.lang.reflect.Type;
25+
import java.lang.reflect.TypeVariable;
26+
import java.util.Collections;
2527
import java.util.HashMap;
2628
import java.util.Locale;
29+
import java.util.Map;
2730

2831
import org.junit.Before;
2932
import org.junit.Test;
@@ -39,6 +42,8 @@
3942
@RunWith(MockitoJUnitRunner.class)
4043
public class ParameterizedTypeUnitTests {
4144

45+
static final Map<TypeVariable<?>, Type> EMPTY_MAP = Collections.emptyMap();
46+
4247
@Mock ParameterizedType one;
4348

4449
@Before
@@ -49,22 +54,22 @@ public void setUp() {
4954
@Test
5055
public void considersTypeInformationsWithDifferingParentsNotEqual() {
5156

52-
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null);
53-
TypeDiscoverer<Object> objectParent = new TypeDiscoverer<Object>(Object.class, null);
57+
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, EMPTY_MAP);
58+
TypeDiscoverer<Object> objectParent = new TypeDiscoverer<Object>(Object.class, EMPTY_MAP);
5459

55-
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent);
56-
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, objectParent);
60+
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent, EMPTY_MAP);
61+
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, objectParent, EMPTY_MAP);
5762

58-
assertFalse(first.equals(second));
63+
assertThat(first, is(not(second)));
5964
}
6065

6166
@Test
6267
public void considersTypeInformationsWithSameParentsNotEqual() {
6368

64-
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null);
69+
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, EMPTY_MAP);
6570

66-
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent);
67-
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, stringParent);
71+
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent, EMPTY_MAP);
72+
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, stringParent, EMPTY_MAP);
6873

6974
assertTrue(first.equals(second));
7075
}

0 commit comments

Comments
 (0)