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;
/**
* 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.
*/
private final ResolvableType componentType;
/**
* Copy of the resolved value.
*/
private final Class<?> resolved;
/**
* Private constructor used to create a new {@link ResolvableType}.
@ -134,6 +129,7 @@ public final class ResolvableType implements Serializable {
this.typeProvider = typeProvider;
this.variableResolver = variableResolver;
this.componentType = componentType;
this.resolved = resolveClass();
}
@ -166,7 +162,7 @@ public final class ResolvableType implements Serializable {
*/
public Object 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 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) {
return (ourBounds != null && ourBounds.isSameKind(typeBounds) &&
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) {
return ourBounds.isAssignableFrom(type);
}
// Main assignability check
boolean rtn = resolve(Object.class).isAssignableFrom(type.resolve(Object.class));
// Main assignability check about to follow
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
// List<CharSequence> is not assignable from List<String>
rtn &= (!checkingGeneric || resolve(Object.class).equals(type.resolve(Object.class)));
// Recursively check each generic
for (int i = 0; i < getGenerics().length; i++) {
rtn &= getGeneric(i).isAssignableFrom(type.as(resolve(Object.class)).getGeneric(i), true);
if (checkingGeneric ? !ourResolved.equals(typeResolved) : !ourResolved.isAssignableFrom(typeResolved)) {
return false;
}
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
* array.
* Return {@code true} if this type resolves to a Class that represents an array.
* @see #getComponentType()
*/
public boolean isArray() {
if (this == NONE) {
return false;
}
return (((this.type instanceof Class) &&
((Class<?>) this.type).isArray()) ||
this.type instanceof GenericArrayType ||
this.resolveType().isArray());
return (((this.type instanceof Class && ((Class<?>) this.type).isArray())) ||
this.type instanceof GenericArrayType || resolveType().isArray());
}
/**
@ -562,10 +591,6 @@ public final class ResolvableType implements Serializable {
* @see #resolveGenerics()
*/
public Class<?> resolve(Class<?> fallback) {
if (!this.isResolved) {
this.resolved = resolveClass();
this.isResolved = true;
}
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}.
* NOTE: the returned {@link ResolvableType} should only be used as an intermediary as
* it cannot be serialized.
* <p>Note: The returned {@link ResolvableType} should only be used as an intermediary
* as it cannot be serialized.
*/
ResolvableType resolveType() {
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).
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
if (isArray()) {
return getComponentType() + "[]";
}
StringBuilder result = new StringBuilder();
result.append(resolve() == null ? "?" : resolve().getName());
if (this.resolved == null) {
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()) {
result.append('<');
result.append(StringUtils.arrayToDelimitedString(getGenerics(), ", "));
@ -705,8 +739,7 @@ public final class ResolvableType implements Serializable {
if (other == null) {
return false;
}
Object src = this.variableResolver.getSource();
return (src == this ? src == other.getSource() : ObjectUtils.nullSafeEquals(src, other.getSource()));
return ObjectUtils.nullSafeEquals(this.variableResolver.getSource(), other.getSource());
}
private static ResolvableType[] forTypes(Type[] types, VariableResolver owner) {

View File

@ -1183,6 +1183,13 @@ public class ResolvableTypeTests {
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 {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
@ -1202,10 +1209,8 @@ public class ResolvableTypeTests {
@Override
public void equalTo(boolean... values) {
for (int i = 0; i < fromTypes.length; i++) {
assertThat(stringDesc(type) + " isAssignableFrom "
+ stringDesc(fromTypes[i]),
type.isAssignableFrom(fromTypes[i]),
Matchers.equalTo(values[i]));
assertThat(stringDesc(type) + " isAssignableFrom " + stringDesc(fromTypes[i]),
type.isAssignableFrom(fromTypes[i]), Matchers.equalTo(values[i]));
}
}
};
@ -1435,11 +1440,28 @@ public class ResolvableTypeTests {
}
static class TypedEnclosedInParameterizedType extends
EnclosedInParameterizedType<Integer> {
static class TypedEnclosedInParameterizedType extends EnclosedInParameterizedType<Integer> {
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;
}
}