Skip to content

Commit 0f448d5

Browse files
committed
DATACMNS-594 - Fixed creation of type variable map to extend into detected bounds.
The initial setup of the type variable map now unfolds the detected types in the map to make sure we detect all type variables present in the current scope. Added caching of component and map value TypeInformation instances to avoid repeated creation.
1 parent ca377dc commit 0f448d5

File tree

5 files changed

+103
-22
lines changed

5 files changed

+103
-22
lines changed

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

+18
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Collection;
2525
import java.util.Collections;
2626
import java.util.HashMap;
27+
import java.util.HashSet;
2728
import java.util.List;
2829
import java.util.Map;
2930
import java.util.Map.Entry;
@@ -111,12 +112,29 @@ public static <S> TypeInformation<S> fromReturnTypeOf(Method method) {
111112
* @return
112113
*/
113114
private static Map<TypeVariable<?>, Type> getTypeVariableMap(Class<?> type) {
115+
return getTypeVariableMap(type, new HashSet<Type>());
116+
}
117+
118+
@SuppressWarnings("deprecation")
119+
private static Map<TypeVariable<?>, Type> getTypeVariableMap(Class<?> type, Collection<Type> visited) {
120+
121+
if (visited.contains(type)) {
122+
return Collections.emptyMap();
123+
} else {
124+
visited.add(type);
125+
}
114126

115127
Map<TypeVariable, Type> source = GenericTypeResolver.getTypeVariableMap(type);
116128
Map<TypeVariable<?>, Type> map = new HashMap<TypeVariable<?>, Type>(source.size());
117129

118130
for (Entry<TypeVariable, Type> entry : source.entrySet()) {
131+
132+
Type value = entry.getValue();
119133
map.put(entry.getKey(), entry.getValue());
134+
135+
if (value instanceof Class) {
136+
map.putAll(getTypeVariableMap((Class<?>) value, visited));
137+
}
120138
}
121139

122140
return map;

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ public Class<S> getType() {
5757

5858
/*
5959
* (non-Javadoc)
60-
* @see org.springframework.data.util.TypeDiscoverer#getComponentType()
60+
* @see org.springframework.data.util.TypeDiscoverer#doGetComponentType()
6161
*/
6262
@Override
63-
public TypeInformation<?> getComponentType() {
63+
protected TypeInformation<?> doGetComponentType() {
6464

6565
Type componentType = type.getGenericComponentType();
6666
return createInfo(componentType);

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

+22-17
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
3737

3838
private final ParameterizedType type;
39-
private TypeInformation<?> componentType;
39+
private Boolean resolved;
4040

4141
/**
4242
* Creates a new {@link ParameterizedTypeInformation} for the given {@link Type} and parent {@link TypeDiscoverer}.
@@ -51,12 +51,12 @@ public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> pa
5151
this.type = type;
5252
}
5353

54-
/*
54+
/*
5555
* (non-Javadoc)
56-
* @see org.springframework.data.util.TypeDiscoverer#getMapValueType()
56+
* @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType()
5757
*/
5858
@Override
59-
public TypeInformation<?> getMapValueType() {
59+
protected TypeInformation<?> doGetMapValueType() {
6060

6161
if (Map.class.isAssignableFrom(getType())) {
6262

@@ -141,18 +141,13 @@ public boolean isAssignableFrom(TypeInformation<?> target) {
141141
return true;
142142
}
143143

144-
/*
144+
/*
145145
* (non-Javadoc)
146-
* @see org.springframework.data.util.TypeDiscoverer#getComponentType()
146+
* @see org.springframework.data.util.TypeDiscoverer#doGetComponentType()
147147
*/
148148
@Override
149-
public TypeInformation<?> getComponentType() {
150-
151-
if (componentType == null) {
152-
this.componentType = createInfo(type.getActualTypeArguments()[0]);
153-
}
154-
155-
return this.componentType;
149+
protected TypeInformation<?> doGetComponentType() {
150+
return createInfo(type.getActualTypeArguments()[0]);
156151
}
157152

158153
/*
@@ -201,10 +196,14 @@ public String toString() {
201196

202197
private boolean isResolvedCompletely() {
203198

199+
if (resolved != null) {
200+
return resolved;
201+
}
202+
204203
Type[] types = type.getActualTypeArguments();
205204

206205
if (types.length == 0) {
207-
return false;
206+
return cacheAndReturn(false);
208207
}
209208

210209
for (Type type : types) {
@@ -213,15 +212,21 @@ private boolean isResolvedCompletely() {
213212

214213
if (info instanceof ParameterizedTypeInformation) {
215214
if (!((ParameterizedTypeInformation<?>) info).isResolvedCompletely()) {
216-
return false;
215+
return cacheAndReturn(false);
217216
}
218217
}
219218

220219
if (!(info instanceof ClassTypeInformation)) {
221-
return false;
220+
return cacheAndReturn(false);
222221
}
223222
}
224223

225-
return true;
224+
return cacheAndReturn(true);
225+
}
226+
227+
private boolean cacheAndReturn(boolean resolved) {
228+
229+
this.resolved = resolved;
230+
return resolved;
226231
}
227232
}

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

+29-3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
5151
private final Map<TypeVariable<?>, Type> typeVariableMap;
5252
private final Map<String, TypeInformation<?>> fieldTypes = new ConcurrentHashMap<String, TypeInformation<?>>();
5353

54+
private boolean componentTypeResolved = false;
55+
private TypeInformation<?> componentType;
56+
57+
private boolean valueTypeResolved = false;
58+
private TypeInformation<?> valueType;
59+
5460
private Class<S> resolvedType;
5561

5662
/**
@@ -83,7 +89,7 @@ protected Map<TypeVariable<?>, Type> getTypeVariableMap() {
8389
* @param fieldType
8490
* @return
8591
*/
86-
@SuppressWarnings({ "rawtypes", "unchecked" })
92+
@SuppressWarnings({ "rawtypes", "unchecked", "deprecation" })
8793
protected TypeInformation<?> createInfo(Type fieldType) {
8894

8995
if (fieldType.equals(this.type)) {
@@ -135,7 +141,7 @@ protected TypeInformation<?> createInfo(Type fieldType) {
135141
* @param type
136142
* @return
137143
*/
138-
@SuppressWarnings({ "unchecked", "rawtypes" })
144+
@SuppressWarnings({ "unchecked", "rawtypes", "deprecation" })
139145
protected Class<S> resolveType(Type type) {
140146

141147
Map<TypeVariable, Type> map = new HashMap<TypeVariable, Type>();
@@ -313,6 +319,16 @@ public boolean isMap() {
313319
*/
314320
public TypeInformation<?> getMapValueType() {
315321

322+
if (!valueTypeResolved) {
323+
this.valueType = doGetMapValueType();
324+
this.valueTypeResolved = true;
325+
}
326+
327+
return this.valueType;
328+
}
329+
330+
protected TypeInformation<?> doGetMapValueType() {
331+
316332
if (isMap()) {
317333
return getTypeArgument(Map.class, 1);
318334
}
@@ -345,7 +361,17 @@ public boolean isCollectionLike() {
345361
* (non-Javadoc)
346362
* @see org.springframework.data.util.TypeInformation#getComponentType()
347363
*/
348-
public TypeInformation<?> getComponentType() {
364+
public final TypeInformation<?> getComponentType() {
365+
366+
if (!componentTypeResolved) {
367+
this.componentType = doGetComponentType();
368+
this.componentTypeResolved = true;
369+
}
370+
371+
return this.componentType;
372+
}
373+
374+
protected TypeInformation<?> doGetComponentType() {
349375

350376
Class<S> rawType = getType();
351377

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

+32
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,18 @@ public void resolvesNestedGenericsToConcreteType() {
341341
assertThat(subSubType.getType(), is((Object) ConcreteSubSub.class));
342342
}
343343

344+
/**
345+
* @see DATACMNS-594
346+
*/
347+
@Test
348+
public void considersGenericsOfTypeBounds() {
349+
350+
ClassTypeInformation<ConcreteRootIntermediate> customer = ClassTypeInformation.from(ConcreteRootIntermediate.class);
351+
TypeInformation<?> leafType = customer.getProperty("intermediate.content.intermediate.content");
352+
353+
assertThat(leafType.getType(), is((Object) Leaf.class));
354+
}
355+
344356
static class StringMapContainer extends MapContainer<String> {
345357

346358
}
@@ -495,4 +507,24 @@ static class ConcreteSub extends GenericSub<ConcreteSubSub> {}
495507
static class ConcreteSubSub extends GenericSubSub {
496508
String content;
497509
}
510+
511+
// DATACMNS-594
512+
513+
static class Intermediate<T> {
514+
T content;
515+
}
516+
517+
static abstract class GenericRootIntermediate<T> {
518+
Intermediate<T> intermediate;
519+
}
520+
521+
static abstract class GenericInnerIntermediate<T> {
522+
Intermediate<T> intermediate;
523+
}
524+
525+
static class ConcreteRootIntermediate extends GenericRootIntermediate<ConcreteInnerIntermediate> {}
526+
527+
static class ConcreteInnerIntermediate extends GenericInnerIntermediate<Leaf> {}
528+
529+
static class Leaf {}
498530
}

0 commit comments

Comments
 (0)