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
This commit is contained in:
Juergen Hoeller 2024-12-10 22:16:10 +01:00
parent 3e3ca74020
commit 7de1dc826a
3 changed files with 69 additions and 11 deletions

View File

@ -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) {

View File

@ -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<String, RecursiveMap> {
}
@SuppressWarnings("serial")
static class RecursiveMapWithInterface extends HashMap<String, RecursiveMapWithInterface>
implements Map<String, RecursiveMapWithInterface> {
}
private static class ResolvableTypeAssert extends AbstractAssert<ResolvableTypeAssert, ResolvableType>{
public ResolvableTypeAssert(ResolvableType actual) {

View File

@ -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<String, RecursiveMap> {
}
@SuppressWarnings("serial")
static class RecursiveMapWithInterface extends HashMap<String, RecursiveMapWithInterface>
implements Map<String, RecursiveMapWithInterface> {
}
// Annotations used on tested elements
@Target({ElementType.PARAMETER})