From 7de1dc826a26a15a17207248e1ea833fadeee40a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 10 Dec 2024 22:16:10 +0100 Subject: [PATCH] Consistently handle generics in TypeDescriptor.equals Properly processes recursive types through always comparing generics via the top-level ResolvableType (rather than through nested TypeDescriptors with custom ResolvableType instances). Closes gh-33932 --- .../core/convert/TypeDescriptor.java | 12 +------ .../core/ResolvableTypeTests.java | 34 +++++++++++++++++++ .../core/convert/TypeDescriptorTests.java | 34 +++++++++++++++++++ 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 522bd92b77..a29ae07516 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -34,7 +34,6 @@ import org.springframework.lang.Contract; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; /** * Contextual descriptor about a type to convert from or to. @@ -501,16 +500,7 @@ public class TypeDescriptor implements Serializable { if (!annotationsMatch(otherDesc)) { return false; } - if (isCollection() || isArray()) { - return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), otherDesc.getElementTypeDescriptor()); - } - else if (isMap()) { - return (ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), otherDesc.getMapKeyTypeDescriptor()) && - ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), otherDesc.getMapValueTypeDescriptor())); - } - else { - return Arrays.equals(getResolvableType().getGenerics(), otherDesc.getResolvableType().getGenerics()); - } + return Arrays.equals(getResolvableType().getGenerics(), otherDesc.getResolvableType().getGenerics()); } private boolean annotationsMatch(TypeDescriptor otherDesc) { diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 3af9b1fdee..c37e2962fb 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1387,6 +1387,30 @@ class ResolvableTypeTests { assertThat(type.hasUnresolvableGenerics()).isFalse(); } + @Test // gh-33932 + void recursiveType() { + assertThat(ResolvableType.forClass(RecursiveMap.class)).isEqualTo( + ResolvableType.forClass(RecursiveMap.class)); + + ResolvableType resolvableType1 = ResolvableType.forClassWithGenerics(Map.class, + String.class, RecursiveMap.class); + ResolvableType resolvableType2 = ResolvableType.forClassWithGenerics(Map.class, + String.class, RecursiveMap.class); + assertThat(resolvableType1).isEqualTo(resolvableType2); + } + + @Test // gh-33932 + void recursiveTypeWithInterface() { + assertThat(ResolvableType.forClass(RecursiveMapWithInterface.class)).isEqualTo( + ResolvableType.forClass(RecursiveMapWithInterface.class)); + + ResolvableType resolvableType1 = ResolvableType.forClassWithGenerics(Map.class, + String.class, RecursiveMapWithInterface.class); + ResolvableType resolvableType2 = ResolvableType.forClassWithGenerics(Map.class, + String.class, RecursiveMapWithInterface.class); + assertThat(resolvableType1).isEqualTo(resolvableType2); + } + @Test void spr11219() throws Exception { ResolvableType type = ResolvableType.forField(BaseProvider.class.getField("stuff"), BaseProvider.class); @@ -1836,6 +1860,16 @@ class ResolvableTypeTests { } + @SuppressWarnings("serial") + static class RecursiveMap extends HashMap { + } + + @SuppressWarnings("serial") + static class RecursiveMapWithInterface extends HashMap + implements Map { + } + + private static class ResolvableTypeAssert extends AbstractAssert{ public ResolvableTypeAssert(ResolvableType actual) { diff --git a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java index ac097a382f..4533b83ded 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java @@ -770,6 +770,30 @@ class TypeDescriptorTests { assertThat(td1).isNotEqualTo(td2); } + @Test // gh-33932 + void recursiveType() { + assertThat(TypeDescriptor.valueOf(RecursiveMap.class)).isEqualTo( + TypeDescriptor.valueOf(RecursiveMap.class)); + + TypeDescriptor typeDescriptor1 = TypeDescriptor.map(Map.class, + TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMap.class)); + TypeDescriptor typeDescriptor2 = TypeDescriptor.map(Map.class, + TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMap.class)); + assertThat(typeDescriptor1).isEqualTo(typeDescriptor2); + } + + @Test // gh-33932 + void recursiveTypeWithInterface() { + assertThat(TypeDescriptor.valueOf(RecursiveMapWithInterface.class)).isEqualTo( + TypeDescriptor.valueOf(RecursiveMapWithInterface.class)); + + TypeDescriptor typeDescriptor1 = TypeDescriptor.map(Map.class, + TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMapWithInterface.class)); + TypeDescriptor typeDescriptor2 = TypeDescriptor.map(Map.class, + TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMapWithInterface.class)); + assertThat(typeDescriptor1).isEqualTo(typeDescriptor2); + } + // Methods designed for test introspection @@ -987,6 +1011,16 @@ class TypeDescriptorTests { } + @SuppressWarnings("serial") + static class RecursiveMap extends HashMap { + } + + @SuppressWarnings("serial") + static class RecursiveMapWithInterface extends HashMap + implements Map { + } + + // Annotations used on tested elements @Target({ElementType.PARAMETER})