Skip to content

Commit 1d6331c

Browse files
christophstroblodrotbohm
authored andcommitted
Use ResolvableType to back TypeInformation.
Reworked the implementation of the TypeInformation type hierarchy to be based on Spring's ResolvableType for most of the heavy-lifting. Original pull request: #2572. Related ticket: #2312.
1 parent 8721ab4 commit 1d6331c

16 files changed

+499
-1238
lines changed

Diff for: src/main/java/org/springframework/data/repository/query/Parameter.java

+9-14
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Collections;
2424
import java.util.List;
2525
import java.util.Optional;
26+
import java.util.stream.Stream;
2627

2728
import org.springframework.core.MethodParameter;
2829
import org.springframework.core.ResolvableType;
@@ -34,6 +35,7 @@
3435
import org.springframework.data.util.ClassTypeInformation;
3536
import org.springframework.data.util.Lazy;
3637
import org.springframework.data.util.TypeInformation;
38+
import org.springframework.data.util.TypeDiscoverer;
3739
import org.springframework.util.Assert;
3840

3941
/**
@@ -211,24 +213,17 @@ boolean isSort() {
211213
*/
212214
private static boolean isDynamicProjectionParameter(MethodParameter parameter) {
213215

214-
Method method = parameter.getMethod();
215-
216-
if (method == null) {
217-
throw new IllegalStateException(String.format("Method parameter %s is not backed by a method", parameter));
218-
}
219-
220-
ClassTypeInformation<?> ownerType = ClassTypeInformation.from(parameter.getDeclaringClass());
221-
TypeInformation<?> parameterTypes = ownerType.getParameterTypes(method).get(parameter.getParameterIndex());
222-
223-
if (!parameterTypes.getType().equals(Class.class)) {
216+
if (!parameter.getParameterType().equals(Class.class)) {
224217
return false;
225218
}
226219

227-
TypeInformation<?> bound = parameterTypes.getTypeArguments().get(0);
228-
TypeInformation<Object> returnType = ClassTypeInformation.fromReturnTypeOf(method);
220+
ResolvableType returnType = ResolvableType.forMethodReturnType(parameter.getMethod());
221+
if(new TypeDiscoverer(returnType).isCollectionLike() || org.springframework.util.ClassUtils.isAssignable(Stream.class, returnType.toClass())) {
222+
returnType = returnType.getGeneric(0);
223+
}
229224

230-
return bound
231-
.equals(QueryExecutionConverters.unwrapWrapperTypes(ReactiveWrapperConverters.unwrapWrapperTypes(returnType)));
225+
ResolvableType type = ResolvableType.forMethodParameter(parameter);
226+
return returnType.getType().equals(type.getGeneric(0).getType());
232227
}
233228

234229
/**

Diff for: src/main/java/org/springframework/data/repository/query/QueryMethod.java

+1
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ private static void assertReturnTypeAssignable(Method method, Set<Class<?>> type
300300
Assert.notNull(method, "Method must not be null");
301301
Assert.notEmpty(types, "Types must not be null or empty");
302302

303+
// TODO: to resolve generics fully we'd need the actual repository interface here
303304
TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);
304305

305306
returnType = QueryExecutionConverters.isSingleValue(returnType.getType()) //

Diff for: src/main/java/org/springframework/data/repository/util/ClassUtils.java

+1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ public static void unwrapReflectionException(Exception ex) throws Throwable {
192192
throw ex;
193193
}
194194

195+
// TODO: we should also consider having the owning type here so we can resolve generics better.
195196
private static TypeInformation<?> getEffectivelyReturnedTypeFrom(Method method) {
196197

197198
TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);

Diff for: src/main/java/org/springframework/data/util/ClassTypeInformation.java

+30-79
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,15 @@
1616
package org.springframework.data.util;
1717

1818
import java.lang.reflect.Method;
19-
import java.lang.reflect.Type;
20-
import java.lang.reflect.TypeVariable;
2119
import java.util.Arrays;
2220
import java.util.Collection;
23-
import java.util.Collections;
24-
import java.util.HashMap;
25-
import java.util.HashSet;
2621
import java.util.List;
2722
import java.util.Map;
2823
import java.util.Set;
2924

30-
import org.springframework.core.GenericTypeResolver;
3125
import org.springframework.core.convert.TypeDescriptor;
26+
import org.springframework.core.ResolvableType;
27+
import org.springframework.lang.Nullable;
3228
import org.springframework.util.Assert;
3329
import org.springframework.util.ConcurrentReferenceHashMap;
3430
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
@@ -39,7 +35,6 @@
3935
* @author Oliver Gierke
4036
* @author Christoph Strobl
4137
*/
42-
@SuppressWarnings({ "unchecked", "rawtypes" })
4338
public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
4439

4540
public static final ClassTypeInformation<Collection> COLLECTION = new ClassTypeInformation(Collection.class);
@@ -51,95 +46,51 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
5146
private static final Map<Class<?>, ClassTypeInformation<?>> cache = new ConcurrentReferenceHashMap<>(64,
5247
ReferenceType.WEAK);
5348

54-
static {
55-
Arrays.asList(COLLECTION, LIST, SET, MAP, OBJECT).forEach(it -> cache.put(it.getType(), it));
56-
}
57-
58-
private final Class<S> type;
59-
private final Lazy<TypeDescriptor> descriptor;
60-
6149
/**
62-
* Simple factory method to easily create new instances of {@link ClassTypeInformation}.
63-
*
64-
* @param <S>
65-
* @param type must not be {@literal null}.
50+
* Warning: Does not fully resolve generic arguments.
51+
* @param method
6652
* @return
53+
* @deprecated since 3.0 Use {@link #fromReturnTypeOf(Method, Class)} instead.
6754
*/
68-
public static <S> ClassTypeInformation<S> from(Class<S> type) {
69-
70-
Assert.notNull(type, "Type must not be null");
71-
72-
return (ClassTypeInformation<S>) cache.computeIfAbsent(type, ClassTypeInformation::new);
55+
@Deprecated
56+
public static TypeInformation<?> fromReturnTypeOf(Method method) {
57+
return new TypeDiscoverer<>(ResolvableType.forMethodReturnType(method));
7358
}
7459

7560
/**
76-
* Creates a {@link TypeInformation} from the given method's return type.
77-
*
78-
* @param method must not be {@literal null}.
61+
* @param method
62+
* @param actualType can be {@literal null}.
7963
* @return
8064
*/
81-
public static <S> TypeInformation<S> fromReturnTypeOf(Method method) {
65+
public static TypeInformation<?> fromReturnTypeOf(Method method, @Nullable Class<?> actualType) {
8266

83-
Assert.notNull(method, "Method must not be null");
84-
return (TypeInformation<S>) ClassTypeInformation.from(method.getDeclaringClass())
85-
.createInfo(method.getGenericReturnType());
67+
if(actualType == null) {
68+
return new TypeDiscoverer<>(ResolvableType.forMethodReturnType(method));
69+
}
70+
return new TypeDiscoverer<>(ResolvableType.forMethodReturnType(method, actualType));
8671
}
8772

88-
/**
89-
* Creates {@link ClassTypeInformation} for the given type.
90-
*
91-
* @param type
92-
*/
93-
ClassTypeInformation(Class<S> type) {
94-
95-
super(type, getTypeVariableMap(type));
73+
Class<?> type;
9674

97-
this.type = type;
98-
this.descriptor = Lazy.of(() -> TypeDescriptor.valueOf(type));
99-
}
100-
101-
/**
102-
* Little helper to allow us to create a generified map, actually just to satisfy the compiler.
103-
*
104-
* @param type must not be {@literal null}.
105-
* @return
106-
*/
107-
private static Map<TypeVariable<?>, Type> getTypeVariableMap(Class<?> type) {
108-
return getTypeVariableMap(type, new HashSet<>());
75+
static {
76+
Arrays.asList(COLLECTION, LIST, SET, MAP, OBJECT).forEach(it -> cache.put(it.getType(), it));
10977
}
11078

111-
private static Map<TypeVariable<?>, Type> getTypeVariableMap(Class<?> type, Collection<Type> visited) {
112-
113-
if (visited.contains(type)) {
114-
return Collections.emptyMap();
115-
} else {
116-
visited.add(type);
117-
}
118-
119-
Map<TypeVariable, Type> source = GenericTypeResolver.getTypeVariableMap(type);
120-
Map<TypeVariable<?>, Type> map = new HashMap<>(source.size());
121-
122-
for (Map.Entry<TypeVariable, Type> entry : source.entrySet()) {
123-
124-
Type value = entry.getValue();
125-
map.put(entry.getKey(), entry.getValue());
79+
public static <S> ClassTypeInformation<S> from(Class<S> type) {
12680

127-
if (value instanceof Class) {
81+
Assert.notNull(type, "Type must not be null");
12882

129-
for (Map.Entry<TypeVariable<?>, Type> nestedEntry : getTypeVariableMap((Class<?>) value, visited).entrySet()) {
130-
if (!map.containsKey(nestedEntry.getKey())) {
131-
map.put(nestedEntry.getKey(), nestedEntry.getValue());
132-
}
133-
}
134-
}
135-
}
83+
return (ClassTypeInformation<S>) cache.computeIfAbsent(type, ClassTypeInformation::new);
84+
}
13685

137-
return map;
86+
ClassTypeInformation(Class<S> type) {
87+
super(ResolvableType.forClass(type));
88+
this.type = type;
13889
}
13990

14091
@Override
14192
public Class<S> getType() {
142-
return type;
93+
return (Class<S>) type;
14394
}
14495

14596
@Override
@@ -158,12 +109,12 @@ public TypeInformation<? extends S> specialize(ClassTypeInformation<?> type) {
158109
}
159110

160111
@Override
161-
public TypeDescriptor toTypeDescriptor() {
162-
return descriptor.get();
112+
public String toString() {
113+
return type.getName();
163114
}
164115

165116
@Override
166-
public String toString() {
167-
return type.getName();
117+
public boolean equals(Object o) {
118+
return super.equals(o);
168119
}
169120
}

Diff for: src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java

-64
This file was deleted.

0 commit comments

Comments
 (0)