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:
parent
260bbe319d
commit
aa2fadd8da
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue