Revised ResolvableType's handling of (self-referential) type variables

Also resolving at construction time now, and shortcutting assignability evaluation.

Issue: SPR-11219
This commit is contained in:
Juergen Hoeller 2013-12-19 14:55:16 +01:00
parent 260bbe319d
commit aa2fadd8da
2 changed files with 100 additions and 45 deletions

View File

@ -105,21 +105,16 @@ public final class ResolvableType implements Serializable {
*/ */
private final VariableResolver variableResolver; private final VariableResolver variableResolver;
/**
* If resolution has happened and {@link #resolved} contains a valid result.
*/
private boolean isResolved = false;
/**
* Late binding stored copy of the resolved value (valid when {@link #isResolved} is true).
*/
private Class<?> resolved;
/** /**
* The component type for an array or {@code null} if the type should be deduced. * The component type for an array or {@code null} if the type should be deduced.
*/ */
private final ResolvableType componentType; private final ResolvableType componentType;
/**
* Copy of the resolved value.
*/
private final Class<?> resolved;
/** /**
* Private constructor used to create a new {@link ResolvableType}. * Private constructor used to create a new {@link ResolvableType}.
@ -134,6 +129,7 @@ public final class ResolvableType implements Serializable {
this.typeProvider = typeProvider; this.typeProvider = typeProvider;
this.variableResolver = variableResolver; this.variableResolver = variableResolver;
this.componentType = componentType; this.componentType = componentType;
this.resolved = resolveClass();
} }
@ -166,7 +162,7 @@ public final class ResolvableType implements Serializable {
*/ */
public Object getSource() { public Object getSource() {
Object source = (this.typeProvider == null ? null : this.typeProvider.getSource()); Object source = (this.typeProvider == null ? null : this.typeProvider.getSource());
return (source == null ? this.type : source); return (source != null ? source : this.type);
} }
/** /**
@ -199,45 +195,78 @@ public final class ResolvableType implements Serializable {
WildcardBounds ourBounds = WildcardBounds.get(this); WildcardBounds ourBounds = WildcardBounds.get(this);
WildcardBounds typeBounds = WildcardBounds.get(type); WildcardBounds typeBounds = WildcardBounds.get(type);
// in the from X is assignable to <? extends Number> // In the from X is assignable to <? extends Number>
if (typeBounds != null) { if (typeBounds != null) {
return (ourBounds != null && ourBounds.isSameKind(typeBounds) && return (ourBounds != null && ourBounds.isSameKind(typeBounds) &&
ourBounds.isAssignableFrom(typeBounds.getBounds())); ourBounds.isAssignableFrom(typeBounds.getBounds()));
} }
// in the form <? extends Number> is assignable to X ... // In the form <? extends Number> is assignable to X...
if (ourBounds != null) { if (ourBounds != null) {
return ourBounds.isAssignableFrom(type); return ourBounds.isAssignableFrom(type);
} }
// Main assignability check // Main assignability check about to follow
boolean rtn = resolve(Object.class).isAssignableFrom(type.resolve(Object.class)); boolean checkGenerics = true;
Class<?> ourResolved = null;
if (this.type instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) this.type;
// Try default variable resolution
if (this.variableResolver != null) {
ResolvableType resolved = this.variableResolver.resolveVariable(variable);
if (resolved != null) {
ourResolved = resolved.resolve();
}
}
if (ourResolved == null) {
// Try variable resolution against target type
if (type.variableResolver != null) {
ResolvableType resolved = type.variableResolver.resolveVariable(variable);
if (resolved != null) {
ourResolved = resolved.resolve();
checkGenerics = false;
}
}
}
}
if (ourResolved == null) {
ourResolved = resolve(Object.class);
}
Class<?> typeResolved = type.resolve(Object.class);
// We need an exact type match for generics // We need an exact type match for generics
// List<CharSequence> is not assignable from List<String> // List<CharSequence> is not assignable from List<String>
rtn &= (!checkingGeneric || resolve(Object.class).equals(type.resolve(Object.class))); if (checkingGeneric ? !ourResolved.equals(typeResolved) : !ourResolved.isAssignableFrom(typeResolved)) {
return false;
// Recursively check each generic
for (int i = 0; i < getGenerics().length; i++) {
rtn &= getGeneric(i).isAssignableFrom(type.as(resolve(Object.class)).getGeneric(i), true);
} }
return rtn; if (checkGenerics) {
// Recursively check each generic
ResolvableType[] ourGenerics = getGenerics();
ResolvableType[] typeGenerics = type.as(ourResolved).getGenerics();
if (ourGenerics.length != typeGenerics.length) {
return false;
}
for (int i = 0; i < ourGenerics.length; i++) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true)) {
return false;
}
}
}
return true;
} }
/** /**
* Return {@code true} if this type will resolve to a Class that represents an * Return {@code true} if this type resolves to a Class that represents an array.
* array.
* @see #getComponentType() * @see #getComponentType()
*/ */
public boolean isArray() { public boolean isArray() {
if (this == NONE) { if (this == NONE) {
return false; return false;
} }
return (((this.type instanceof Class) && return (((this.type instanceof Class && ((Class<?>) this.type).isArray())) ||
((Class<?>) this.type).isArray()) || this.type instanceof GenericArrayType || resolveType().isArray());
this.type instanceof GenericArrayType ||
this.resolveType().isArray());
} }
/** /**
@ -562,10 +591,6 @@ public final class ResolvableType implements Serializable {
* @see #resolveGenerics() * @see #resolveGenerics()
*/ */
public Class<?> resolve(Class<?> fallback) { public Class<?> resolve(Class<?> fallback) {
if (!this.isResolved) {
this.resolved = resolveClass();
this.isResolved = true;
}
return (this.resolved != null ? this.resolved : fallback); return (this.resolved != null ? this.resolved : fallback);
} }
@ -582,8 +607,8 @@ public final class ResolvableType implements Serializable {
/** /**
* Resolve this type by a single level, returning the resolved value or {@link #NONE}. * Resolve this type by a single level, returning the resolved value or {@link #NONE}.
* NOTE: the returned {@link ResolvableType} should only be used as an intermediary as * <p>Note: The returned {@link ResolvableType} should only be used as an intermediary
* it cannot be serialized. * as it cannot be serialized.
*/ */
ResolvableType resolveType() { ResolvableType resolveType() {
if (this.type instanceof ParameterizedType) { if (this.type instanceof ParameterizedType) {
@ -642,17 +667,26 @@ public final class ResolvableType implements Serializable {
} }
/** /**
* Return a string representation of this type in its fully resolved form * Return a String representation of this type in its fully resolved form
* (including any generic parameters). * (including any generic parameters).
* @see java.lang.Object#toString()
*/ */
@Override @Override
public String toString() { public String toString() {
if (isArray()) { if (isArray()) {
return getComponentType() + "[]"; return getComponentType() + "[]";
} }
StringBuilder result = new StringBuilder(); if (this.resolved == null) {
result.append(resolve() == null ? "?" : resolve().getName()); return "?";
}
if (this.type instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) this.type;
if (this.variableResolver == null || this.variableResolver.resolveVariable(variable) == null) {
// Don't bother with variable boundaries for toString()...
// Can cause infinite recursions in case of self-references
return "?";
}
}
StringBuilder result = new StringBuilder(this.resolved.getName());
if (hasGenerics()) { if (hasGenerics()) {
result.append('<'); result.append('<');
result.append(StringUtils.arrayToDelimitedString(getGenerics(), ", ")); result.append(StringUtils.arrayToDelimitedString(getGenerics(), ", "));
@ -705,8 +739,7 @@ public final class ResolvableType implements Serializable {
if (other == null) { if (other == null) {
return false; return false;
} }
Object src = this.variableResolver.getSource(); return ObjectUtils.nullSafeEquals(this.variableResolver.getSource(), other.getSource());
return (src == this ? src == other.getSource() : ObjectUtils.nullSafeEquals(src, other.getSource()));
} }
private static ResolvableType[] forTypes(Type[] types, VariableResolver owner) { private static ResolvableType[] forTypes(Type[] types, VariableResolver owner) {

View File

@ -1183,6 +1183,13 @@ public class ResolvableTypeTests {
assertThat(type.hasUnresolvableGenerics(), equalTo(true)); assertThat(type.hasUnresolvableGenerics(), equalTo(true));
} }
@Test
public void testSpr11219() throws Exception {
ResolvableType type = ResolvableType.forField(BaseProvider.class.getField("stuff"), BaseProvider.class);
assertTrue(type.getNested(2).isAssignableFrom(ResolvableType.forClass(BaseImplementation.class)));
assertEquals("java.util.Collection<org.springframework.core.ResolvableTypeTests$IBase<?>>", type.toString());
}
private ResolvableType testSerialization(ResolvableType type) throws Exception { private ResolvableType testSerialization(ResolvableType type) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
@ -1202,10 +1209,8 @@ public class ResolvableTypeTests {
@Override @Override
public void equalTo(boolean... values) { public void equalTo(boolean... values) {
for (int i = 0; i < fromTypes.length; i++) { for (int i = 0; i < fromTypes.length; i++) {
assertThat(stringDesc(type) + " isAssignableFrom " assertThat(stringDesc(type) + " isAssignableFrom " + stringDesc(fromTypes[i]),
+ stringDesc(fromTypes[i]), type.isAssignableFrom(fromTypes[i]), Matchers.equalTo(values[i]));
type.isAssignableFrom(fromTypes[i]),
Matchers.equalTo(values[i]));
} }
} }
}; };
@ -1435,11 +1440,28 @@ public class ResolvableTypeTests {
} }
static class TypedEnclosedInParameterizedType extends static class TypedEnclosedInParameterizedType extends EnclosedInParameterizedType<Integer> {
EnclosedInParameterizedType<Integer> {
class TypedInnerTyped extends InnerTyped<Long> { class TypedInnerTyped extends InnerTyped<Long> {
} }
} }
public interface IProvider<P> {
}
public interface IBase<BT extends IBase<BT>> {
}
public abstract class AbstractBase<BT extends IBase<BT>> implements IBase<BT> {
}
public class BaseImplementation extends AbstractBase<BaseImplementation> {
}
public class BaseProvider<BT extends IBase<BT>> implements IProvider<IBase<BT>> {
public Collection<IBase<BT>> stuff;
}
} }