Browse Source

Consistent Class and array matching with Class comparison shortcut

Closes gh-31487
pull/30079/head
Juergen Hoeller 11 months ago
parent
commit
795ce1658c
  1. 48
      spring-core/src/main/java/org/springframework/core/ResolvableType.java
  2. 31
      spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java

48
spring-core/src/main/java/org/springframework/core/ResolvableType.java

@ -262,7 +262,9 @@ public class ResolvableType implements Serializable { @@ -262,7 +262,9 @@ public class ResolvableType implements Serializable {
* @see #isAssignableFrom(ResolvableType)
*/
public boolean isAssignableFrom(Class<?> other) {
return isAssignableFrom(forClass(other), null);
// As of 6.1: shortcut assignability check for top-level Class references
return (this.type instanceof Class<?> clazz ? ClassUtils.isAssignable(clazz, other) :
isAssignableFrom(forClass(other), false, null));
}
/**
@ -277,10 +279,10 @@ public class ResolvableType implements Serializable { @@ -277,10 +279,10 @@ public class ResolvableType implements Serializable {
* {@code ResolvableType}; {@code false} otherwise
*/
public boolean isAssignableFrom(ResolvableType other) {
return isAssignableFrom(other, null);
return isAssignableFrom(other, false, null);
}
private boolean isAssignableFrom(ResolvableType other, @Nullable Map<Type, Type> matchedBefore) {
private boolean isAssignableFrom(ResolvableType other, boolean strict, @Nullable Map<Type, Type> matchedBefore) {
Assert.notNull(other, "ResolvableType must not be null");
// If we cannot resolve types, we are not assignable
@ -288,13 +290,21 @@ public class ResolvableType implements Serializable { @@ -288,13 +290,21 @@ public class ResolvableType implements Serializable {
return false;
}
// Deal with array by delegating to the component type
if (isArray()) {
return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType()));
if (matchedBefore != null) {
if (matchedBefore.get(this.type) == other.type) {
return true;
}
}
else {
// As of 6.1: shortcut assignability check for top-level Class references
if (this.type instanceof Class<?> clazz && other.type instanceof Class<?> otherClazz) {
return (strict ? clazz.isAssignableFrom(otherClazz) : ClassUtils.isAssignable(clazz, otherClazz));
}
}
if (matchedBefore != null && matchedBefore.get(this.type) == other.type) {
return true;
// Deal with array by delegating to the component type
if (isArray()) {
return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType(), true, matchedBefore));
}
// Deal with wildcard bounds
@ -340,13 +350,15 @@ public class ResolvableType implements Serializable { @@ -340,13 +350,15 @@ public class ResolvableType implements Serializable {
}
}
if (ourResolved == null) {
ourResolved = resolve(Object.class);
ourResolved = toClass();
}
Class<?> otherResolved = other.toClass();
// We need an exact type match for generics
// List<CharSequence> is not assignable from List<String>
if (exactMatch ? !ourResolved.equals(otherResolved) : !ClassUtils.isAssignable(ourResolved, otherResolved)) {
if (exactMatch ? !ourResolved.equals(otherResolved) :
(strict ? !ourResolved.isAssignableFrom(otherResolved) :
!ClassUtils.isAssignable(ourResolved, otherResolved))) {
return false;
}
@ -357,13 +369,15 @@ public class ResolvableType implements Serializable { @@ -357,13 +369,15 @@ public class ResolvableType implements Serializable {
if (ourGenerics.length != typeGenerics.length) {
return false;
}
if (matchedBefore == null) {
matchedBefore = new IdentityHashMap<>(1);
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) {
return false;
if (ourGenerics.length > 0) {
if (matchedBefore == null) {
matchedBefore = new IdentityHashMap<>(1);
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true, matchedBefore)) {
return false;
}
}
}
}

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

@ -697,13 +697,20 @@ class ResolvableTypeTests { @@ -697,13 +697,20 @@ class ResolvableTypeTests {
assertThat(type.getGeneric(0).as(Collection.class).getGeneric(0).as(Collection.class).resolve()).isNull();
}
@Test
void intArrayNotAssignableToIntegerArray() throws Exception {
ResolvableType integerArray = ResolvableType.forField(Fields.class.getField("integerArray"));
ResolvableType intArray = ResolvableType.forField(Fields.class.getField("intArray"));
assertThat(integerArray.isAssignableFrom(intArray)).isFalse();
assertThat(intArray.isAssignableFrom(integerArray)).isFalse();
}
@Test
void resolveBoundedTypeVariableResult() throws Exception {
ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVariableResult"));
assertThat(type.resolve()).isEqualTo(CharSequence.class);
}
@Test
void resolveBoundedTypeVariableWildcardResult() throws Exception {
ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVariableWildcardResult"));
@ -718,30 +725,26 @@ class ResolvableTypeTests { @@ -718,30 +725,26 @@ class ResolvableTypeTests {
@Test
void resolveTypeVariableFromSimpleInterfaceType() {
ResolvableType type = ResolvableType.forClass(
MySimpleInterfaceType.class).as(MyInterfaceType.class);
ResolvableType type = ResolvableType.forClass(MySimpleInterfaceType.class).as(MyInterfaceType.class);
assertThat(type.resolveGeneric()).isEqualTo(String.class);
}
@Test
void resolveTypeVariableFromSimpleCollectionInterfaceType() {
ResolvableType type = ResolvableType.forClass(
MyCollectionInterfaceType.class).as(MyInterfaceType.class);
ResolvableType type = ResolvableType.forClass(MyCollectionInterfaceType.class).as(MyInterfaceType.class);
assertThat(type.resolveGeneric()).isEqualTo(Collection.class);
assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class);
}
@Test
void resolveTypeVariableFromSimpleSuperclassType() {
ResolvableType type = ResolvableType.forClass(
MySimpleSuperclassType.class).as(MySuperclassType.class);
ResolvableType type = ResolvableType.forClass(MySimpleSuperclassType.class).as(MySuperclassType.class);
assertThat(type.resolveGeneric()).isEqualTo(String.class);
}
@Test
void resolveTypeVariableFromSimpleCollectionSuperclassType() {
ResolvableType type = ResolvableType.forClass(
MyCollectionSuperclassType.class).as(MySuperclassType.class);
ResolvableType type = ResolvableType.forClass(MyCollectionSuperclassType.class).as(MySuperclassType.class);
assertThat(type.resolveGeneric()).isEqualTo(Collection.class);
assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class);
}
@ -768,8 +771,7 @@ class ResolvableTypeTests { @@ -768,8 +771,7 @@ class ResolvableTypeTests {
void resolveTypeVariableFromSuperType() throws Exception {
ResolvableType type = ResolvableType.forClass(ExtendsList.class);
assertThat(type.resolve()).isEqualTo(ExtendsList.class);
assertThat(type.asCollection().resolveGeneric())
.isEqualTo(CharSequence.class);
assertThat(type.asCollection().resolveGeneric()).isEqualTo(CharSequence.class);
}
@Test
@ -1021,6 +1023,7 @@ class ResolvableTypeTests { @@ -1021,6 +1023,7 @@ class ResolvableTypeTests {
void isAssignableFromCannotBeResolved() throws Exception {
ResolvableType objectType = ResolvableType.forClass(Object.class);
ResolvableType unresolvableVariable = ResolvableType.forField(AssignmentBase.class.getField("o"));
assertThat(unresolvableVariable.resolve()).isNull();
assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable);
assertThatResolvableType(unresolvableVariable).isAssignableFrom(objectType);
@ -1294,7 +1297,7 @@ class ResolvableTypeTests { @@ -1294,7 +1297,7 @@ class ResolvableTypeTests {
}
@Test
void hasUnresolvableGenericsWhenImplementesRawInterface() throws Exception {
void hasUnresolvableGenericsWhenImplementingRawInterface() throws Exception {
ResolvableType type = ResolvableType.forClass(MySimpleInterfaceTypeWithImplementsRaw.class);
for (ResolvableType generic : type.getGenerics()) {
assertThat(generic.resolve()).isNotNull();
@ -1432,6 +1435,10 @@ class ResolvableTypeTests { @@ -1432,6 +1435,10 @@ class ResolvableTypeTests {
public Map<Map<String, Integer>, Map<Byte, Long>> nested;
public T[] variableTypeGenericArray;
public Integer[] integerArray;
public int[] intArray;
}

Loading…
Cancel
Save