Skip to content

Commit c08d207

Browse files
committed
Additional refinements in TypeInformation reimplementation.
Slight refinements in TypeDiscoverer.equals(…) / hashCode() that are still not completely valid, are different enough to work for differentiating use cases but not 100% efficient for cache cases. Captured outstanding work in #2643. Reimplemented ….repository.query.Parameter.isDynamicProjectParameter(…) to bild on TypeInformation completely and properly unwrapp *all* wrapper types for type comparison. Related ticket #2312.
1 parent 18a9f3a commit c08d207

File tree

5 files changed

+68
-23
lines changed

5 files changed

+68
-23
lines changed

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

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

1818
import static java.lang.String.*;
1919

20+
import java.lang.reflect.Method;
2021
import java.util.ArrayList;
2122
import java.util.Arrays;
2223
import java.util.Collections;
2324
import java.util.List;
2425
import java.util.Optional;
25-
import java.util.stream.Stream;
2626

2727
import org.springframework.core.MethodParameter;
2828
import org.springframework.core.ResolvableType;
@@ -214,15 +214,16 @@ private static boolean isDynamicProjectionParameter(MethodParameter parameter) {
214214
return false;
215215
}
216216

217-
ResolvableType returnType = ResolvableType.forMethodReturnType(parameter.getMethod());
217+
Method method = parameter.getMethod();
218218

219-
if (TypeInformation.of(returnType).isCollectionLike()
220-
|| org.springframework.util.ClassUtils.isAssignable(Stream.class, returnType.toClass())) {
221-
returnType = returnType.getGeneric(0);
219+
if (method == null) {
220+
throw new IllegalArgumentException("Parameter is not associated with any method");
222221
}
223222

224-
ResolvableType type = ResolvableType.forMethodParameter(parameter);
225-
return returnType.getType().equals(type.getGeneric(0).getType());
223+
TypeInformation<?> returnType = TypeInformation.fromReturnTypeOf(method);
224+
TypeInformation<?> unwrapped = QueryExecutionConverters.unwrapWrapperTypes(returnType);
225+
226+
return unwrapped.equals(TypeInformation.fromMethodParameter(parameter).getComponentType());
226227
}
227228

228229
/**

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import org.springframework.core.convert.TypeDescriptor;
3535
import org.springframework.lang.Nullable;
3636
import org.springframework.util.Assert;
37-
import org.springframework.util.ClassUtils;
3837
import org.springframework.util.ConcurrentLruCache;
3938
import org.springframework.util.ObjectUtils;
4039
import org.springframework.util.ReflectionUtils;
@@ -197,7 +196,7 @@ public TypeDescriptor toTypeDescriptor() {
197196

198197
@Override
199198
public ClassTypeInformation<?> getRawTypeInformation() {
200-
return new ClassTypeInformation<>(ResolvableType.forRawClass(resolvableType.getRawClass()));
199+
return new ClassTypeInformation<>(ResolvableType.forRawClass(resolvableType.toClass()));
201200
}
202201

203202
@Nullable
@@ -323,7 +322,7 @@ public boolean equals(@Nullable Object o) {
323322
return true;
324323
}
325324

326-
if ((o == null) || !ClassUtils.isAssignable(getClass(), o.getClass())) {
325+
if ((o == null) || !ObjectUtils.nullSafeEquals(getClass(), o.getClass())) {
327326
return false;
328327
}
329328

@@ -346,7 +345,11 @@ public boolean equals(@Nullable Object o) {
346345

347346
@Override
348347
public int hashCode() {
349-
return ObjectUtils.nullSafeHashCode(resolvableType.toClass());
348+
349+
int result = 31 * getClass().hashCode();
350+
result += 31 * getType().hashCode();
351+
352+
return result;
350353
}
351354

352355
@Override

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

+24-4
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717

1818
import java.lang.reflect.Constructor;
1919
import java.lang.reflect.Method;
20+
import java.lang.reflect.TypeVariable;
2021
import java.util.Collection;
2122
import java.util.List;
2223
import java.util.Map;
2324
import java.util.Set;
2425

26+
import org.springframework.core.MethodParameter;
2527
import org.springframework.core.ResolvableType;
2628
import org.springframework.core.convert.TypeDescriptor;
2729
import org.springframework.lang.Nullable;
@@ -61,9 +63,11 @@ public static TypeInformation<?> of(ResolvableType type) {
6163

6264
Assert.notNull(type, "Type must not be null");
6365

64-
return type.hasGenerics() || (type.isArray() && type.getComponentType().hasGenerics()) //
65-
? TypeDiscoverer.td(type)
66-
: ClassTypeInformation.cti(type);
66+
return type.hasGenerics()
67+
|| (type.isArray() && type.getComponentType().hasGenerics()) //
68+
|| (type.getType() instanceof TypeVariable)
69+
? TypeDiscoverer.td(type)
70+
: ClassTypeInformation.cti(type);
6771
}
6872

6973
/**
@@ -103,7 +107,23 @@ public static TypeInformation<?> fromReturnTypeOf(Method method) {
103107
* @since 3.0
104108
*/
105109
public static TypeInformation<?> fromReturnTypeOf(Method method, @Nullable Class<?> type) {
106-
return ClassTypeInformation.fromReturnTypeOf(method, type);
110+
111+
ResolvableType intermediate = type == null
112+
? ResolvableType.forMethodReturnType(method)
113+
: ResolvableType.forMethodReturnType(method, type);
114+
115+
return TypeInformation.of(intermediate);
116+
}
117+
118+
/**
119+
* Returns a new {@link TypeInformation} for the given {@link MethodParameter}.
120+
*
121+
* @param parameter must not be {@literal null}.
122+
* @return will never be {@literal null}.
123+
* @since 3.0
124+
*/
125+
public static TypeInformation<?> fromMethodParameter(MethodParameter parameter) {
126+
return TypeInformation.of(ResolvableType.forMethodParameter(parameter));
107127
}
108128

109129
/**

Diff for: src/test/java/org/springframework/data/repository/query/ParameterUnitTests.java

+14-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.util.Collections;
2121
import java.util.List;
22+
import java.util.Optional;
2223
import java.util.stream.Stream;
2324

2425
import org.jetbrains.annotations.NotNull;
@@ -48,14 +49,17 @@ void classParameterWithSameTypeParameterAsReturnedStreamIsDynamicProjectionParam
4849
assertThat(parameter.isDynamicProjectionParameter()).isTrue();
4950
}
5051

52+
@Test
53+
void classParameterWithSameTypeParameterAsReturnedOptionalIsDynamicProjectionParameter() throws Exception {
54+
55+
var parameter = new Parameter(getMethodParameter("dynamicProjectionWithOptional"));
56+
57+
assertThat(parameter.isDynamicProjectionParameter()).isTrue();
58+
}
59+
5160
@NotNull
5261
private MethodParameter getMethodParameter(String methodName) throws NoSuchMethodException {
53-
return new MethodParameter( //
54-
this.getClass().getDeclaredMethod( //
55-
methodName, //
56-
Class.class //
57-
), //
58-
0);
62+
return new MethodParameter(this.getClass().getDeclaredMethod(methodName, Class.class), 0);
5963
}
6064

6165
<T> List<T> dynamicProjectionWithList(Class<T> type) {
@@ -65,4 +69,8 @@ <T> List<T> dynamicProjectionWithList(Class<T> type) {
6569
<T> Stream<T> dynamicProjectionWithStream(Class<T> type) {
6670
return Stream.empty();
6771
}
72+
73+
<T> Optional<T> dynamicProjectionWithOptional(Class<T> type) {
74+
return Optional.empty();
75+
}
6876
}

Diff for: src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,7 @@ void genericFieldOfType() {
274274
var field = ReflectionUtils.findField(GenericPerson.class, "value");
275275
var discoverer = TypeInformation.of(ResolvableType.forField(field, TypeExtendingGenericPersonWithAddress.class));
276276

277-
assertThat(discoverer).isEqualTo(TypeInformation.of(Address.class));
278-
assertThat(discoverer.hashCode()).isEqualTo(TypeInformation.of(Address.class).hashCode());
277+
assertThat(discoverer.getType()).isEqualTo(Address.class);
279278
}
280279

281280
@Test // #2511
@@ -335,6 +334,20 @@ void detectsComponentTypeOfTypedClass() {
335334
assertThat(property.getComponentType().getType()).isEqualTo(Leaf.class);
336335
}
337336

337+
@Test
338+
void differentEqualsAndHashCodeForTypeDiscovererAndClassTypeInformation() {
339+
340+
ResolvableType type = ResolvableType.forClass(Object.class);
341+
342+
var discoverer = new TypeDiscoverer<>(type);
343+
var classTypeInformation = new ClassTypeInformation<>(type);
344+
345+
assertThat(discoverer).isNotEqualTo(classTypeInformation);
346+
assertThat(classTypeInformation).isNotEqualTo(type);
347+
348+
assertThat(discoverer.hashCode()).isNotEqualTo(classTypeInformation.hashCode());
349+
}
350+
338351
class Person {
339352

340353
Addresses addresses;

0 commit comments

Comments
 (0)