Skip to content

Commit 7de1dc8

Browse files
committed
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
1 parent 3e3ca74 commit 7de1dc8

File tree

3 files changed

+69
-11
lines changed

3 files changed

+69
-11
lines changed

Diff for: spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

+1-11
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import org.springframework.lang.Nullable;
3535
import org.springframework.util.Assert;
3636
import org.springframework.util.ClassUtils;
37-
import org.springframework.util.ObjectUtils;
3837

3938
/**
4039
* Contextual descriptor about a type to convert from or to.
@@ -501,16 +500,7 @@ public boolean equals(@Nullable Object other) {
501500
if (!annotationsMatch(otherDesc)) {
502501
return false;
503502
}
504-
if (isCollection() || isArray()) {
505-
return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), otherDesc.getElementTypeDescriptor());
506-
}
507-
else if (isMap()) {
508-
return (ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), otherDesc.getMapKeyTypeDescriptor()) &&
509-
ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), otherDesc.getMapValueTypeDescriptor()));
510-
}
511-
else {
512-
return Arrays.equals(getResolvableType().getGenerics(), otherDesc.getResolvableType().getGenerics());
513-
}
503+
return Arrays.equals(getResolvableType().getGenerics(), otherDesc.getResolvableType().getGenerics());
514504
}
515505

516506
private boolean annotationsMatch(TypeDescriptor otherDesc) {

Diff for: spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java

+34
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,30 @@ void hasUnresolvableGenericsWithEnum() {
13871387
assertThat(type.hasUnresolvableGenerics()).isFalse();
13881388
}
13891389

1390+
@Test // gh-33932
1391+
void recursiveType() {
1392+
assertThat(ResolvableType.forClass(RecursiveMap.class)).isEqualTo(
1393+
ResolvableType.forClass(RecursiveMap.class));
1394+
1395+
ResolvableType resolvableType1 = ResolvableType.forClassWithGenerics(Map.class,
1396+
String.class, RecursiveMap.class);
1397+
ResolvableType resolvableType2 = ResolvableType.forClassWithGenerics(Map.class,
1398+
String.class, RecursiveMap.class);
1399+
assertThat(resolvableType1).isEqualTo(resolvableType2);
1400+
}
1401+
1402+
@Test // gh-33932
1403+
void recursiveTypeWithInterface() {
1404+
assertThat(ResolvableType.forClass(RecursiveMapWithInterface.class)).isEqualTo(
1405+
ResolvableType.forClass(RecursiveMapWithInterface.class));
1406+
1407+
ResolvableType resolvableType1 = ResolvableType.forClassWithGenerics(Map.class,
1408+
String.class, RecursiveMapWithInterface.class);
1409+
ResolvableType resolvableType2 = ResolvableType.forClassWithGenerics(Map.class,
1410+
String.class, RecursiveMapWithInterface.class);
1411+
assertThat(resolvableType1).isEqualTo(resolvableType2);
1412+
}
1413+
13901414
@Test
13911415
void spr11219() throws Exception {
13921416
ResolvableType type = ResolvableType.forField(BaseProvider.class.getField("stuff"), BaseProvider.class);
@@ -1836,6 +1860,16 @@ public void doA() {
18361860
}
18371861

18381862

1863+
@SuppressWarnings("serial")
1864+
static class RecursiveMap extends HashMap<String, RecursiveMap> {
1865+
}
1866+
1867+
@SuppressWarnings("serial")
1868+
static class RecursiveMapWithInterface extends HashMap<String, RecursiveMapWithInterface>
1869+
implements Map<String, RecursiveMapWithInterface> {
1870+
}
1871+
1872+
18391873
private static class ResolvableTypeAssert extends AbstractAssert<ResolvableTypeAssert, ResolvableType>{
18401874

18411875
public ResolvableTypeAssert(ResolvableType actual) {

Diff for: spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java

+34
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,30 @@ void equalityWithGenerics() {
770770
assertThat(td1).isNotEqualTo(td2);
771771
}
772772

773+
@Test // gh-33932
774+
void recursiveType() {
775+
assertThat(TypeDescriptor.valueOf(RecursiveMap.class)).isEqualTo(
776+
TypeDescriptor.valueOf(RecursiveMap.class));
777+
778+
TypeDescriptor typeDescriptor1 = TypeDescriptor.map(Map.class,
779+
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMap.class));
780+
TypeDescriptor typeDescriptor2 = TypeDescriptor.map(Map.class,
781+
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMap.class));
782+
assertThat(typeDescriptor1).isEqualTo(typeDescriptor2);
783+
}
784+
785+
@Test // gh-33932
786+
void recursiveTypeWithInterface() {
787+
assertThat(TypeDescriptor.valueOf(RecursiveMapWithInterface.class)).isEqualTo(
788+
TypeDescriptor.valueOf(RecursiveMapWithInterface.class));
789+
790+
TypeDescriptor typeDescriptor1 = TypeDescriptor.map(Map.class,
791+
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMapWithInterface.class));
792+
TypeDescriptor typeDescriptor2 = TypeDescriptor.map(Map.class,
793+
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMapWithInterface.class));
794+
assertThat(typeDescriptor1).isEqualTo(typeDescriptor2);
795+
}
796+
773797

774798
// Methods designed for test introspection
775799

@@ -987,6 +1011,16 @@ public void setListProperty(List<Number> t) {
9871011
}
9881012

9891013

1014+
@SuppressWarnings("serial")
1015+
static class RecursiveMap extends HashMap<String, RecursiveMap> {
1016+
}
1017+
1018+
@SuppressWarnings("serial")
1019+
static class RecursiveMapWithInterface extends HashMap<String, RecursiveMapWithInterface>
1020+
implements Map<String, RecursiveMapWithInterface> {
1021+
}
1022+
1023+
9901024
// Annotations used on tested elements
9911025

9921026
@Target({ElementType.PARAMETER})

0 commit comments

Comments
 (0)