Browse Source

Consider nested generics in `TypeDiscoverer` equality comparison.

We now compare nested generics wrapped into TypeInformation to consider type equality for deeply parametrized types.

Previously, we resolved type parameters to Class so Foo<List<String>> was considered equal to Foo<List<Map>> as the type parameter of the first nesting level was erased.

Closes #3051
3.1.x
Mark Paluch 2 years ago
parent
commit
43caf4970c
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 16
      src/main/java/org/springframework/data/util/TypeDiscoverer.java
  2. 21
      src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java

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

@ -60,6 +60,8 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -60,6 +60,8 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
private final Map<Constructor<?>, List<TypeInformation<?>>> constructorParameters = new ConcurrentHashMap<>();
private final Lazy<List<TypeInformation<?>>> typeArguments;
private final Lazy<List<TypeInformation<?>>> resolvedGenerics;
protected TypeDiscoverer(ResolvableType type) {
Assert.notNull(type, "Type must not be null");
@ -68,6 +70,10 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -68,6 +70,10 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
this.componentType = Lazy.of(this::doGetComponentType);
this.valueType = Lazy.of(this::doGetMapValueType);
this.typeArguments = Lazy.of(this::doGetTypeArguments);
this.resolvedGenerics = Lazy.of(() -> Arrays.stream(resolvableType.getGenerics()) //
.map(TypeInformation::of) // use TypeInformation comparison to remove any attachments to variableResolver
// holding the type source
.collect(Collectors.toList()));
}
static TypeDiscoverer<?> td(ResolvableType type) {
@ -325,15 +331,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -325,15 +331,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
return false;
}
var collect1 = Arrays.stream(resolvableType.getGenerics()) //
.map(ResolvableType::toClass) //
.collect(Collectors.toList());
var collect2 = Arrays.stream(that.resolvableType.getGenerics()) //
.map(ResolvableType::toClass) //
.collect(Collectors.toList());
return ObjectUtils.nullSafeEquals(collect1, collect2);
return ObjectUtils.nullSafeEquals(resolvedGenerics.get(), that.resolvedGenerics.get());
}
@Override

21
src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java

@ -29,6 +29,7 @@ import java.util.Set; @@ -29,6 +29,7 @@ import java.util.Set;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.core.ResolvableType;
import org.springframework.data.geo.GeoResults;
@ -348,6 +349,15 @@ public class TypeDiscovererUnitTests { @@ -348,6 +349,15 @@ public class TypeDiscovererUnitTests {
assertThat(discoverer.hashCode()).isNotEqualTo(classTypeInformation.hashCode());
}
@Test // GH-3051
void considersNestedGenericsInEquality() throws ReflectiveOperationException {
ResolvableType containerList = ResolvableType.forField(WithContainer.class.getDeclaredField("containerList"));
ResolvableType containerMap = ResolvableType.forField(WithContainer.class.getDeclaredField("containerMap"));
assertThat(TypeInformation.of(containerList)).isNotEqualTo(TypeInformation.of(containerMap));
}
class Person {
Addresses addresses;
@ -441,4 +451,15 @@ public class TypeDiscovererUnitTests { @@ -441,4 +451,15 @@ public class TypeDiscovererUnitTests {
class GeoResultsWrapper {
GeoResults<Leaf> results;
}
static class WithContainer {
MyContainer<List<String>> containerList;
MyContainer<List<Map<Long, Double>>> containerMap;
}
static class MyContainer<T> {
T data;
}
}

Loading…
Cancel
Save