Browse Source

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
pull/34398/head
Juergen Hoeller 1 year ago
parent
commit
7de1dc826a
  1. 12
      spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
  2. 34
      spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
  3. 34
      spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java

12
spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

@ -34,7 +34,6 @@ import org.springframework.lang.Contract; @@ -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 { @@ -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) {

34
spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java

@ -1387,6 +1387,30 @@ class ResolvableTypeTests { @@ -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 { @@ -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) {

34
spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java

@ -770,6 +770,30 @@ class TypeDescriptorTests { @@ -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 { @@ -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})

Loading…
Cancel
Save