From 43caf4970c56eb0620199d3632d2faf4e431a390 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 27 Feb 2024 11:18:29 +0100 Subject: [PATCH] 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> was considered equal to Foo> as the type parameter of the first nesting level was erased. Closes #3051 --- .../data/util/TypeDiscoverer.java | 16 +++++++------- .../data/util/TypeDiscovererUnitTests.java | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java index 76a292897..bd48634af 100644 --- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -60,6 +60,8 @@ class TypeDiscoverer implements TypeInformation { private final Map, List>> constructorParameters = new ConcurrentHashMap<>(); private final Lazy>> typeArguments; + private final Lazy>> resolvedGenerics; + protected TypeDiscoverer(ResolvableType type) { Assert.notNull(type, "Type must not be null"); @@ -68,6 +70,10 @@ class TypeDiscoverer implements TypeInformation { 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 implements TypeInformation { 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 diff --git a/src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java b/src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java index e0b95eb2c..8d08c9e0e 100755 --- a/src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java +++ b/src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java @@ -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 { 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 { class GeoResultsWrapper { GeoResults results; } + + static class WithContainer { + MyContainer> containerList; + + MyContainer>> containerMap; + } + + static class MyContainer { + T data; + } + }