From aa2fadd8da0bcb87db5bad42c0774b868fce70cd Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 19 Dec 2013 14:55:16 +0100 Subject: [PATCH] Revised ResolvableType's handling of (self-referential) type variables Also resolving at construction time now, and shortcutting assignability evaluation. Issue: SPR-11219 --- .../springframework/core/ResolvableType.java | 111 ++++++++++++------ .../core/ResolvableTypeTests.java | 34 +++++- 2 files changed, 100 insertions(+), 45 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index 0e603741a1..d65475956f 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -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 + // In the from X is assignable to if (typeBounds != null) { return (ourBounds != null && ourBounds.isSameKind(typeBounds) && ourBounds.isAssignableFrom(typeBounds.getBounds())); } - // in the form is assignable to X ... + // In the form 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 is not assignable from List - 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. + *

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) { diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index b79ad11578..ea9aaed552 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -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>", 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 { + static class TypedEnclosedInParameterizedType extends EnclosedInParameterizedType { class TypedInnerTyped extends InnerTyped { } } + + public interface IProvider

{ + } + + public interface IBase> { + } + + public abstract class AbstractBase> implements IBase { + } + + public class BaseImplementation extends AbstractBase { + } + + public class BaseProvider> implements IProvider> { + + public Collection> stuff; + } + }