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:
parent
3e3ca74020
commit
7de1dc826a
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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})
|
||||
|
|
Loading…
Reference in New Issue