diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java
new file mode 100644
index 0000000000..1f463565d2
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java
@@ -0,0 +1,914 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.Collection;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ConcurrentReferenceHashMap;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Encapsulates a Java {@link java.lang.reflect.Type}, providing access to
+ * {@link #getSuperType() supertypes} , {@link #getInterfaces() interfaces} and
+ * {@link #getGeneric(int...) generic parameters} along with the ability to ultimately
+ * {@link #resolve() resolve} to a {@link java.lang.Class}.
+ *
+ *
{@code ResolvableTypes} may be obtained from {@link #forField(Field) fields},
+ * {@link #forMethodParameter(Method, int) method parameters},
+ * {@link #forMethodReturn(Method) method returns}, {@link #forClass(Class) classes} or
+ * directly from a {@link #forType(Type) java.lang.reflect.Type}. Most methods on this class
+ * will themselves return {@link ResolvableType}s, allowing easy navigation. For example:
+ *
+ *
+ * @author Phillip Webb
+ * @since 4.0
+ * @see TypeVariableResolver
+ * @see #forField(Field)
+ * @see #forMethodParameter(Method, int)
+ * @see #forMethodReturn(Method)
+ * @see #forConstructorParameter(Constructor, int)
+ * @see #forClass(Class)
+ * @see #forType(Type)
+ */
+public final class ResolvableType implements TypeVariableResolver {
+
+ private static ConcurrentReferenceHashMap cache =
+ new ConcurrentReferenceHashMap();
+
+
+ /**
+ * {@code ResolvableType} returned when no value is available. {@code NONE} is used
+ * in preference to {@code null} so that multiple method calls can be safely chained.
+ */
+ public static final ResolvableType NONE = new ResolvableType(null, null);
+
+
+ private static final ResolvableType[] EMPTY_TYPES_ARRAY = new ResolvableType[0];
+
+
+ /**
+ * The underlying java type being managed (only ever {@code null} for {@link #NONE})
+ */
+ private final Type type;
+
+ /**
+ * The {@link TypeVariableResolver} to use or {@code null} if no resolver is availble.
+ */
+ private final TypeVariableResolver variableResolver;
+
+ /**
+ * Stored copy of the resolved value or {@code null} if the resolve method has not
+ * yet been called. {@code void.class} is used when the resolve method failed.
+ */
+ private Class> resolved;
+
+
+ /**
+ * Private constructor used to create a new {@link ResolvableType}.
+ * @param type the underlying java type (may only be {@code null} for {@link #NONE})
+ * @param variableResolver the resolver used for {@link TypeVariable}s (may be {@code null})
+ */
+ private ResolvableType(Type type, TypeVariableResolver variableResolver) {
+ this.type = type;
+ this.variableResolver = variableResolver;
+ }
+
+
+ /**
+ * Return the underling java {@link Type} being managed. With the exception of
+ * the {@link #NONE} constant, this method will never return {@code null}.
+ */
+ public Type getType() {
+ return this.type;
+ }
+
+ /**
+ * Determines if this {@code ResolvableType} is assignable from the specified
+ * {@code type}. Attempts to follow the same rules as the Java compiler, considering
+ * if both the {@link #resolve() resolved} {@code Class} is
+ * {@link Class#isAssignableFrom(Class) assignable from} the given {@code type} as
+ * well as if all {@link #getGenerics() generics} are assignable.
+ * @param type the type to be checked
+ * @return {@code true} if the specified {@code type} can be assigned to this
+ * {@code type}.
+ */
+ public boolean isAssignableFrom(ResolvableType type) {
+ return isAssignableFrom(type, false);
+ }
+
+ private boolean isAssignableFrom(ResolvableType type, boolean checkingGeneric) {
+ Assert.notNull(type, "Type must not be null");
+
+ // If we cannot resolve types, we are not assignable
+ if (resolve() == null || type.resolve() == null) {
+ return false;
+ }
+
+ // Deal with array by delegating to the component type
+ if (isArray()) {
+ return (type.isArray() && getComponentType().isAssignableFrom(
+ type.getComponentType()));
+ }
+
+ // Deal with wildcard bounds
+ WildcardBounds ourBounds = WildcardBounds.get(this);
+ WildcardBounds typeBounds = WildcardBounds.get(type);
+
+ // 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 ...
+ if (ourBounds != null) {
+ return ourBounds.isAssignableFrom(type);
+ }
+
+ // Main assignability check
+ boolean rtn = resolve().isAssignableFrom(type.resolve());
+
+ // We need an exact type match for generics
+ // List is not assignable from List
+ rtn &= (!checkingGeneric || resolve().equals(type.resolve()));
+
+ // Recursively check each generic
+ for (int i = 0; i < getGenerics().length; i++) {
+ rtn &= getGeneric(i).isAssignableFrom(type.as(resolve()).getGeneric(i), true);
+ }
+
+ return rtn;
+ }
+
+ /**
+ * Return {@code true} if this type will resolve 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 the ResolvableType representing the component type of the array or
+ * {@link #NONE} if this type does not represent an array.
+ * @see #isArray()
+ */
+ public ResolvableType getComponentType() {
+ if (this == NONE) {
+ return NONE;
+ }
+ if (this.type instanceof Class) {
+ Class> componentType = ((Class>) this.type).getComponentType();
+ return (componentType == null ? NONE : forType(componentType,
+ this.variableResolver));
+ }
+ if (this.type instanceof GenericArrayType) {
+ return forType(((GenericArrayType) this.type).getGenericComponentType(),
+ this.variableResolver);
+ }
+ return resolveType().getComponentType();
+ }
+
+ /**
+ * Convenience method to return this type as a resolvable {@link Collection} type.
+ * Returns {@link #NONE} if this type does not implement or extend
+ * {@link Collection}.
+ * @see #as(Class)
+ * @see #asMap()
+ */
+ public ResolvableType asCollection() {
+ return as(Collection.class);
+ }
+
+ /**
+ * Convenience method to return this type as a resolvable {@link Map} type.
+ * Returns {@link #NONE} if this type does not implement or extend
+ * {@link Map}.
+ * @see #as(Class)
+ * @see #asCollection()
+ */
+ public ResolvableType asMap() {
+ return as(Map.class);
+ }
+
+ /**
+ * Return this type as a {@link ResolvableType} of the specified class. Searches
+ * {@link #getSuperType() supertype} and {@link #getInterfaces() interface}
+ * hierarchies to find a match, returning {@link #NONE} if this type does not
+ * implement or extends the specified class.
+ * @param type the required class type
+ * @return a {@link ResolvableType} representing this object as the specified type or
+ * {@link #NONE}
+ * @see #asCollection()
+ * @see #asMap()
+ * @see #getSuperType()
+ * @see #getInterfaces()
+ */
+ public ResolvableType as(Class> type) {
+ if (this == NONE) {
+ return NONE;
+ }
+ if (ObjectUtils.nullSafeEquals(resolve(), type)) {
+ return this;
+ }
+ for (ResolvableType interfaceType : getInterfaces()) {
+ ResolvableType interfaceAsType = interfaceType.as(type);
+ if (interfaceAsType != NONE) {
+ return interfaceAsType;
+ }
+ }
+ return getSuperType().as(type);
+ }
+
+ /**
+ * Return a {@link ResolvableType} representing the direct supertype of this type.
+ * If no supertype is available this method returns {@link #NONE}.
+ * @see #getInterfaces()
+ */
+ public ResolvableType getSuperType() {
+ Class> resolved = resolve();
+ if (resolved == null || resolved.getGenericSuperclass() == null) {
+ return NONE;
+ }
+ return forType(resolved.getGenericSuperclass(), this);
+ }
+
+ /**
+ * Return a {@link ResolvableType} array representing the direct interfaces
+ * implemented by this type. If this type does not implement any interfaces an
+ * empty array is returned.
+ * @see #getSuperType()
+ */
+ public ResolvableType[] getInterfaces() {
+ Class> resolved = resolve();
+ if (resolved == null || ObjectUtils.isEmpty(resolved.getGenericInterfaces())) {
+ return EMPTY_TYPES_ARRAY;
+ }
+ Type[] interfaceTypes = resolved.getGenericInterfaces();
+ ResolvableType[] interfaces = new ResolvableType[interfaceTypes.length];
+ for (int i = 0; i < interfaceTypes.length; i++) {
+ interfaces[i] = forType(interfaceTypes[i], this);
+ }
+ return interfaces;
+ }
+
+ /**
+ * Return {@code true} if this type contains generic parameters.
+ * @see #getGeneric(int...)
+ * @see #getGenerics()
+ */
+ public boolean hasGenerics() {
+ return (getGenerics().length > 0);
+ }
+
+ /**
+ * Returns a {@link ResolvableType} for the specified nesting level. See
+ * {@link #getNested(int, Map)} for details.
+ * @param nestingLevel the nesting level
+ * @return the {@link ResolvableType} type, or {@code #NONE}
+ */
+ public ResolvableType getNested(int nestingLevel) {
+ return getNested(nestingLevel, null);
+ }
+
+ /**
+ * Returns a {@link ResolvableType} for the specified nesting level. The nesting level
+ * refers to the specific generic parameter that should be returned. A nesting level
+ * of 1 indicates this type, 2 indicates the first nested generic, 3 the second and so
+ * on. For example, given {@code List>} level 1 refers to the
+ * {@code List}, level 2 the {@code Set} and level 3 the {@code Integer}.
+ *
+ *
The {@code typeIndexesPerLevel} map can be used to reference a specific generic
+ * for the given level. For example, an index of 0 would refer to a {@code Map} key,
+ * where as 1 would refer to the value. If the map does not contain an value for a
+ * specific level the last generic will be used (e.g. a {@code Map} value).
+ *
+ *
Nesting levels may also apply to array types, for example given
+ * {@code String[]}, a nesting level of 2 referes to {@code String}.
+ *
+ *
If a type does not {@link #hasGenerics() contain} generics the
+ * {@link #getSuperType() super-type} hierarchy will be considered.
+ * @param nestingLevel the required nesting level, indexed from 1 for the current
+ * type, 2 for the first nested generic, 3 for the second and so on.
+ * @param typeIndexesPerLevel a map containing the generic index for a given nesting
+ * level (may be {@code null}).
+ * @return a {@link ResolvableType} for the nested level or {@link #NONE}.
+ */
+ public ResolvableType getNested(int nestingLevel,
+ Map typeIndexesPerLevel) {
+ ResolvableType result = this;
+ for (int i = 2; i <= nestingLevel; i++) {
+ if (result.isArray()) {
+ result = result.getComponentType();
+ }
+ else {
+ // Handle derived types
+ while (result != ResolvableType.NONE && !result.hasGenerics()) {
+ result = result.getSuperType();
+ }
+ Integer index = (typeIndexesPerLevel == null ? null
+ : typeIndexesPerLevel.get(i));
+ index = (index == null ? result.getGenerics().length - 1 : index);
+ result = result.getGeneric(index);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Return a {@link ResolvableType} representing the generic parameter for the given
+ * indexes. Indexes are zero based, for example given the type
+ * {@code Map>}, {@code getGeneric(0)} will access the
+ * {@code Integer}. Nested generics can be accessed by specifying multiple indexes,
+ * for example {@code getGeneric(1, 0)} will access the {@code String} from the nested
+ * {@code List}. For convenience, if no indexes are specified the first generic is
+ * returned.
+ *
+ *
If no generic is available at the specified indexes {@link #NONE} is returned.
+ * @param indexes the indexes that refers to the generic parameter (may be omitted to
+ * return the first generic)
+ * @return a {@link ResolvableType} for the specified generic or {@link #NONE}
+ * @see #hasGenerics()
+ * @see #getGenerics()
+ * @see #resolveGeneric(int...)
+ * @see #resolveGenerics()
+ */
+ public ResolvableType getGeneric(int... indexes) {
+ try {
+ if (indexes == null || indexes.length == 0) {
+ return getGenerics()[0];
+ }
+ ResolvableType rtn = this;
+ for (int index : indexes) {
+ rtn = rtn.getGenerics()[index];
+ }
+ return rtn;
+ }
+ catch (IndexOutOfBoundsException ex) {
+ return NONE;
+ }
+ }
+
+ /**
+ * Return an array of {@link ResolvableType} representing the generics parameters of
+ * this type. If no generics are available an empty array is returned. If you need to
+ * access a specific generic consider using the {@link #getGeneric(int...)} method as
+ * it allows access to nested generics, and protects against
+ * {@code IndexOutOfBoundsExceptions}
+ * @return an array of {@link ResolvableType}s representing the generic parameters
+ * (never {@code null})
+ * @see #hasGenerics()
+ * @see #getGeneric(int...)
+ * @see #resolveGeneric(int...)
+ * @see #resolveGenerics()
+ */
+ public ResolvableType[] getGenerics() {
+ if (this == NONE) {
+ return EMPTY_TYPES_ARRAY;
+ }
+ if (this.type instanceof ParameterizedType) {
+ Type[] genericTypes = ((ParameterizedType) getType()).getActualTypeArguments();
+ ResolvableType[] generics = new ResolvableType[genericTypes.length];
+ for (int i = 0; i < genericTypes.length; i++) {
+ generics[i] = forType(genericTypes[i], this);
+ }
+ return generics;
+ }
+ return resolveType().getGenerics();
+ }
+
+ /**
+ * Convenience method that will {@link #getGenerics() get} and {@link #resolve()
+ * resolve} generic parameters.
+ * @return an array of resolved generic parameters (the resulting array will never be
+ * {@code null}, but it may contain {@code null} elements})
+ * @see #getGenerics()
+ * @see #resolve()
+ */
+ public Class>[] resolveGenerics() {
+ ResolvableType[] generics = getGenerics();
+ Class>[] resolvedGenerics = new Class>[generics.length];
+ for (int i = 0; i < generics.length; i++) {
+ resolvedGenerics[i] = generics[i].resolve();
+ }
+ return resolvedGenerics;
+ }
+
+ /**
+ * Convenience method that will {@link #getGeneric(int...) get} and
+ * {@link #resolve() resolve} a specific generic parameters.
+ * @param indexes the indexes that refers to the generic parameter (may be omitted to
+ * return the first generic)
+ * @return a resolved {@link Class} or {@code null}
+ * @see #getGeneric(int...)
+ * @see #resolve()
+ */
+ public Class> resolveGeneric(int... indexes) {
+ return getGeneric(indexes).resolve();
+ }
+
+ /**
+ * Resolve this type to a {@link java.lang.Class}, returning {@code null} if the type
+ * cannot be resolved. This method will consider bounds of {@link TypeVariable}s and
+ * {@link WildcardType}s if direct resolution fails.
+ * @return the resolved {@link Class} or {@code null}
+ * @see #resolve(Class)
+ * @see #resolveGeneric(int...)
+ * @see #resolveGenerics()
+ */
+ public Class> resolve() {
+ return resolve(null);
+ }
+
+ /**
+ * Resolve this type to a {@link java.lang.Class}, returning the specified
+ * {@code fallback} if the type cannot be resolved. This method will consider bounds
+ * of {@link TypeVariable}s and {@link WildcardType}s if direct resolution fails.
+ * @param fallback the fallback class to use if resolution fails (may be {@code null})
+ * @return the resolved {@link Class} or the {@code fallback}
+ * @see #resolve()
+ * @see #resolveGeneric(int...)
+ * @see #resolveGenerics()
+ */
+ public Class> resolve(Class> fallback) {
+ if (this.resolved == null) {
+ synchronized (this) {
+ this.resolved = resolveClass();
+ this.resolved = (this.resolved == null ? void.class : this.resolved);
+ }
+ }
+ return (this.resolved == void.class ? fallback : this.resolved);
+ }
+
+ private Class> resolveClass() {
+ if (this.type instanceof Class> || this.type == null) {
+ return (Class>) this.type;
+ }
+ if (this.type instanceof GenericArrayType) {
+ return Array.newInstance(getComponentType().resolve(), 0).getClass();
+ }
+ return resolveType().resolve();
+ }
+
+ /**
+ * Resolve this type by a single level, returning the resolved value or {@link #NONE}.
+ */
+ ResolvableType resolveType() {
+ Type resolved = null;
+ if (this.type instanceof ParameterizedType) {
+ resolved = ((ParameterizedType) this.type).getRawType();
+ }
+ else if (this.type instanceof WildcardType) {
+ resolved = resolveBounds(((WildcardType) this.type).getUpperBounds());
+ if (resolved == null) {
+ resolved = resolveBounds(((WildcardType) this.type).getLowerBounds());
+ }
+ }
+ else if (this.type instanceof TypeVariable) {
+ if (this.variableResolver != null) {
+ resolved = this.variableResolver.resolveVariable((TypeVariable>) this.type);
+ }
+ if (resolved == null) {
+ resolved = resolveBounds(((TypeVariable>) this.type).getBounds());
+ }
+ }
+ return (resolved == null ? NONE : forType(resolved, this.variableResolver));
+ }
+
+ private Type resolveBounds(Type[] bounds) {
+ if (ObjectUtils.isEmpty(bounds) || Object.class.equals(bounds[0])) {
+ return null;
+ }
+ return bounds[0];
+ }
+
+ public Type resolveVariable(TypeVariable> variable) {
+ Assert.notNull("Variable must not be null");
+ if (this.type instanceof ParameterizedType) {
+
+ ParameterizedType parameterizedType = (ParameterizedType) this.type;
+ Type owner = parameterizedType.getOwnerType();
+
+ if (parameterizedType.getRawType().equals(variable.getGenericDeclaration())) {
+ TypeVariable>[] variables = resolve().getTypeParameters();
+ for (int i = 0; i < variables.length; i++) {
+ if (ObjectUtils.nullSafeEquals(variables[i].getName(), variable.getName())) {
+ return parameterizedType.getActualTypeArguments()[i];
+ }
+ }
+ }
+
+ Type resolved = null;
+ if (this.variableResolver != null) {
+ resolved = this.variableResolver.resolveVariable(variable);
+ }
+ if (resolved == null && owner != null) {
+ resolved = forType(owner, this.variableResolver).resolveVariable(variable);
+ }
+ return resolved;
+ }
+
+ if (this.type instanceof TypeVariable>) {
+ return resolveType().resolveVariable(variable);
+ }
+
+ return null;
+ }
+
+ /**
+ * 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 (hasGenerics()) {
+ result.append("<");
+ result.append(StringUtils.arrayToDelimitedString(getGenerics(), ", "));
+ result.append(">");
+ }
+ return result.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return ObjectUtils.nullSafeHashCode(this.type) * 31
+ + ObjectUtils.nullSafeHashCode(this.variableResolver);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof ResolvableType) {
+ ResolvableType other = (ResolvableType) obj;
+ return ObjectUtils.nullSafeEquals(this.type, other.type)
+ && ObjectUtils.nullSafeEquals(this.variableResolver,
+ other.variableResolver);
+ }
+ return false;
+ }
+
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Class}. For example
+ * {@code ResolvableType.forClass(MyArrayList.class)}.
+ * @param sourceClass the source class (must not be {@code null}
+ * @return a {@link ResolvableType} for the specified class
+ * @see #forClass(Class, Class)
+ */
+ public static ResolvableType forClass(Class> sourceClass) {
+ Assert.notNull(sourceClass, "Source class must not be null");
+ return forType(sourceClass);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Class} with a given
+ * implementation. For example
+ * {@code ResolvableType.forClass(List.class, MyArrayList.class)}.
+ * @param sourceClass the source class (must not be {@code null}
+ * @param implementationClass the implementation class (must not be {@code null})
+ * @return a {@link ResolvableType} for the specified class backed by the given
+ * implementation class
+ * @see #forClass(Class)
+ */
+ public static ResolvableType forClass(Class> sourceClass, Class> implementationClass) {
+ Assert.notNull(sourceClass, "Source class must not be null");
+ Assert.notNull(implementationClass, "ImplementationClass must not be null");
+ ResolvableType asType = forType(implementationClass).as(sourceClass);
+ return (asType == NONE ? forType(sourceClass) : asType);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Field}.
+ * @param field the source field
+ * @return a {@link ResolvableType} for the specified field
+ * @see #forField(Field, Class)
+ */
+ public static ResolvableType forField(Field field) {
+ Assert.notNull(field, "Field must not be null");
+ return forType(field.getGenericType());
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Field} with a given
+ * implementation. Use this variant when the class that declares the field includes
+ * generic parameter variables that are satisfied by the implementation class.
+ * @param field the source field
+ * @param implementationClass the implementation class (must not be {@code null})
+ * @return a {@link ResolvableType} for the specified field
+ * @see #forField(Field)
+ */
+ public static ResolvableType forField(Field field, Class> implementationClass) {
+ Assert.notNull(field, "Field must not be null");
+ Assert.notNull(implementationClass, "ImplementationClass must not be null");
+ TypeVariableResolver variableResolver = forType(implementationClass).as(
+ field.getDeclaringClass());
+ return forType(field.getGenericType(), variableResolver);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Constructor} parameter.
+ * @param constructor the source constructor (must not be {@code null})
+ * @param parameterIndex the parameter index
+ * @return a {@link ResolvableType} for the specified constructor parameter
+ * @see #forConstructorParameter(Constructor, int, Class)
+ */
+ public static ResolvableType forConstructorParameter(Constructor> constructor,
+ int parameterIndex) {
+ Assert.notNull(constructor, "Constructor must not be null");
+ return forMethodParameter(MethodParameter.forMethodOrConstructor(constructor,
+ parameterIndex));
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Constructor} parameter
+ * with a given implementation. Use this variant when the class that declares the
+ * constructor includes generic parameter variables that are satisfied by the
+ * implementation class.
+ * @param constructor the source constructor (must not be {@code null})
+ * @param parameterIndex the parameter index
+ * @param implementationClass the implementation class (must not be {@code null})
+ * @return a {@link ResolvableType} for the specified constructor parameter
+ * @see #forConstructorParameter(Constructor, int)
+ */
+ public static ResolvableType forConstructorParameter(Constructor> constructor,
+ int parameterIndex, Class> implementationClass) {
+ Assert.notNull(constructor, "Constructor must not be null");
+ Assert.notNull(implementationClass, "ImplementationClass must not be null");
+ return forMethodParameter(
+ MethodParameter.forMethodOrConstructor(constructor, parameterIndex),
+ implementationClass);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Method} parameter.
+ * @param method the source method (must not be {@code null})
+ * @param parameterIndex the parameter index
+ * @return a {@link ResolvableType} for the specified method parameter
+ * @see #forMethodParameter(Method, int, Class)
+ * @see #forMethodParameter(MethodParameter)
+ */
+ public static ResolvableType forMethodParameter(Method method, int parameterIndex) {
+ Assert.notNull(method, "Method must not be null");
+ return forMethodParameter(MethodParameter.forMethodOrConstructor(method,
+ parameterIndex));
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Method} parameter with a
+ * given implementation. Use this variant when the class that declares the method
+ * includes generic parameter variables that are satisfied by the implementation
+ * class.
+ * @param method the source method (must not be {@code null})
+ * @param parameterIndex the parameter index
+ * @param implementationClass the implementation class (must not be {@code null})
+ * @return a {@link ResolvableType} for the specified method parameter
+ * @see #forMethodParameter(Method, int, Class)
+ * @see #forMethodParameter(MethodParameter)
+ */
+ public static ResolvableType forMethodParameter(Method method, int parameterIndex,
+ Class> implementationClass) {
+ Assert.notNull(method, "Method must not be null");
+ return forMethodParameter(
+ MethodParameter.forMethodOrConstructor(method, parameterIndex),
+ implementationClass);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link MethodParameter}.
+ * @param methodParameter the source method parameter (must not be {@code null})
+ * @return a {@link ResolvableType} for the specified method parameter
+ * @see #forMethodParameter(MethodParameter, Class)
+ * @see #forMethodParameter(Method, int)
+ */
+ public static ResolvableType forMethodParameter(MethodParameter methodParameter) {
+ Assert.notNull(methodParameter, "MethodParameter must not be null");
+ return forType(methodParameter.getGenericParameterType()).getNested(
+ methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link MethodParameter} with a
+ * given implementation. Use this variant when the class that declares the method
+ * includes generic parameter variables that are satisfied by the implementation
+ * class.
+ * @param methodParameter the source method parameter (must not be {@code null})
+ * @param implementationClass the implementation class (must not be {@code null})
+ * @return a {@link ResolvableType} for the specified method parameter
+ * @see #forMethodParameter(MethodParameter)
+ * @see #forMethodParameter(Method, int)
+ */
+ public static ResolvableType forMethodParameter(MethodParameter methodParameter,
+ Class> implementationClass) {
+ Assert.notNull(methodParameter, "MethodParameter must not be null");
+ Assert.notNull(implementationClass, "ImplementationClass must not be null");
+ TypeVariableResolver variableResolver = forType(implementationClass).as(
+ methodParameter.getMember().getDeclaringClass());
+ return forType(methodParameter.getGenericParameterType(), variableResolver).getNested(
+ methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Method} return.
+ * @param method the source for the method return
+ * @return a {@link ResolvableType} for the specified method return
+ * @see #forMethodReturn(Method, Class)
+ */
+ public static ResolvableType forMethodReturn(Method method) {
+ Assert.notNull(method, "Method must not be null");
+ return forType(method.getGenericReturnType());
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Method} return. Use this
+ * variant when the class that declares the method includes generic parameter
+ * variables that are satisfied by the implementation class.
+ * @param method the source for the method return
+ * @param implementationClass the implementation class (must not be {@code null})
+ * @return a {@link ResolvableType} for the specified method return
+ * @see #forMethodReturn(Method)
+ */
+ public static ResolvableType forMethodReturn(Method method,
+ Class> implementationClass) {
+ Assert.notNull(method, "Method must not be null");
+ Assert.notNull(implementationClass, "ImplementationClass must not be null");
+ TypeVariableResolver variableResolver = forType(implementationClass).as(
+ method.getDeclaringClass());
+ return forType(method.getGenericReturnType(), variableResolver);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link java.lang.reflect.Type}.
+ * @param type the source type (must not be {@code null})
+ * @return a {@link ResolvableType} for the specified {@link java.lang.reflect.Type}
+ */
+ public static ResolvableType forType(Type type) {
+ return forType(type, null);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link java.lang.reflect.Type}
+ * backed by a given {@link TypeVariableResolver}.
+ * @param type the source type (must not be {@code null})
+ * @param variableResolver the variable resolver
+ * @return a {@link ResolvableType} for the specified {@link java.lang.reflect.Type}
+ * and {@link TypeVariableResolver}
+ */
+ public static ResolvableType forType(Type type, TypeVariableResolver variableResolver) {
+ ResolvableType key = new ResolvableType(type, variableResolver);
+ // Check the cache, we may have a ResolvableType that may have already been resolved
+ ResolvableType resolvableType = cache.get(key);
+ if (resolvableType == null) {
+ resolvableType = key;
+ cache.put(key, resolvableType);
+ }
+ return resolvableType;
+ }
+
+
+ /**
+ * Internal helper to handle bounds from {@link WildcardType}s.
+ */
+ private static class WildcardBounds {
+
+
+ private final Kind kind;
+
+ private final ResolvableType[] bounds;
+
+
+ /**
+ * Private constructor to create a new {@link WildcardBounds} instance.
+ * @param kind the kind of bounds
+ * @param bounds the bounds
+ * @see #get(ResolvableType)
+ */
+ private WildcardBounds(Kind kind, ResolvableType[] bounds) {
+ this.kind = kind;
+ this.bounds = bounds;
+ }
+
+
+ /**
+ * Return {@code true} if this bounds is the same kind as the specified bounds.
+ */
+ public boolean isSameKind(WildcardBounds bounds) {
+ return this.kind == bounds.kind;
+ }
+
+ /**
+ * Return {@code true} if this bounds is assignable to all the specified types.
+ * @param types the types to test against
+ * @return {@code true} if this bounds is assignable to all types
+ */
+ public boolean isAssignableFrom(ResolvableType... types) {
+ for (ResolvableType bound : this.bounds) {
+ for (ResolvableType type : types) {
+ if (!isAssignable(bound, type)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean isAssignable(ResolvableType source, ResolvableType from) {
+ return (this.kind == Kind.UPPER ? source.isAssignableFrom(from)
+ : from.isAssignableFrom(source));
+ }
+
+ /**
+ * Return the underlying bounds.
+ */
+ public ResolvableType[] getBounds() {
+ return bounds;
+ }
+
+
+ /**
+ * Get a {@link WildcardBounds} instance for the specified type, returning
+ * {@code null} if the specified type cannot be resolved to a {@link WildcardType}.
+ * @param type the source type
+ * @return a {@link WildcardBounds} instance or {@code null}
+ */
+ public static WildcardBounds get(ResolvableType type) {
+ ResolvableType resolveToWildcard = type;
+ while(!(resolveToWildcard.getType() instanceof WildcardType)) {
+ if (resolveToWildcard == NONE) {
+ return null;
+ }
+ resolveToWildcard = resolveToWildcard.resolveType();
+ }
+ WildcardType wildcardType = (WildcardType) resolveToWildcard.type;
+ Kind boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER
+ : Kind.UPPER);
+ Type[] bounds = boundsType == Kind.UPPER ? wildcardType.getUpperBounds()
+ : wildcardType.getLowerBounds();
+ ResolvableType[] resolvableBounds = new ResolvableType[bounds.length];
+ for (int i = 0; i < bounds.length; i++) {
+ resolvableBounds[i] = forType(bounds[i], type.variableResolver);
+ }
+ return new WildcardBounds(boundsType, resolvableBounds);
+ }
+
+
+ /**
+ * The various kinds of bounds.
+ */
+ static enum Kind { UPPER, LOWER }
+
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/TypeVariableResolver.java b/spring-core/src/main/java/org/springframework/core/TypeVariableResolver.java
new file mode 100644
index 0000000000..71f1c1dadc
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/TypeVariableResolver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core;
+
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+/**
+ * Strategy interface that can be used to resolve {@link java.lang.reflect.TypeVariable}s.
+ *
+ * @author Phillip Webb
+ * @since 4.0
+ */
+public interface TypeVariableResolver {
+
+ /**
+ * Resolve the specified type variable.
+ * @param typeVariable the type variable to resolve (must not be {@code null})
+ * @return the resolved {@link java.lang.reflect.Type} for the variable or
+ * {@code null} if the variable cannot be resolved.
+ */
+ Type resolveVariable(TypeVariable> typeVariable);
+
+}
diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
new file mode 100644
index 0000000000..b4a91079ec
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
@@ -0,0 +1,1285 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core;
+
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.AbstractCollection;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.hamcrest.Matchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.util.MultiValueMap;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+// FIXME nested
+
+/**
+ * Tests for {@link ResolvableType}.
+ *
+ * @author Phillip Webb
+ */
+@SuppressWarnings("rawtypes")
+@RunWith(MockitoJUnitRunner.class)
+public class ResolvableTypeTests {
+
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Captor
+ private ArgumentCaptor> typeVariableCaptor;
+
+
+ @Test
+ public void noneReturnValues() throws Exception {
+ ResolvableType none = ResolvableType.NONE;
+ assertThat(none.as(Object.class), equalTo(ResolvableType.NONE));
+ assertThat(none.asCollection(), equalTo(ResolvableType.NONE));
+ assertThat(none.asMap(), equalTo(ResolvableType.NONE));
+ assertThat(none.getComponentType(), equalTo(ResolvableType.NONE));
+ assertThat(none.getGeneric(0), equalTo(ResolvableType.NONE));
+ assertThat(none.getGenerics().length, equalTo(0));
+ assertThat(none.getInterfaces().length, equalTo(0));
+ assertThat(none.getSuperType(), equalTo(ResolvableType.NONE));
+ assertThat(none.getType(), nullValue());
+ assertThat(none.hasGenerics(), equalTo(false));
+ assertThat(none.isArray(), equalTo(false));
+ assertThat(none.resolve(), nullValue());
+ assertThat(none.resolve(String.class), equalTo((Class) String.class));
+ assertThat(none.resolveGeneric(0), nullValue());
+ assertThat(none.resolveGenerics().length, equalTo(0));
+ assertThat(none.resolveVariable(mock(TypeVariable.class)), nullValue());
+ assertThat(none.toString(), equalTo("?"));
+ assertThat(none.isAssignableFrom(ResolvableType.forClass(Object.class)), equalTo(false));
+ }
+
+ @Test
+ public void forClass() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class);
+ assertThat(type.getType(), equalTo((Type) ExtendsList.class));
+ }
+
+ @Test
+ public void forClassMustNotBeNull() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Source class must not be null");
+ ResolvableType.forClass(null);
+ }
+
+ @Test
+ public void forField() throws Exception {
+ Field field = Fields.class.getField("charSequenceList");
+ ResolvableType type = ResolvableType.forField(field);
+ assertThat(type.getType(), equalTo(field.getGenericType()));
+ }
+
+ @Test
+ public void forPrivateField() throws Exception {
+ Field field = Fields.class.getDeclaredField("privateField");
+ ResolvableType type = ResolvableType.forField(field);
+ assertThat(type.getType(), equalTo(field.getGenericType()));
+ assertThat(type.resolve(), equalTo((Class) List.class));
+ }
+
+ @Test
+ public void forFieldMustNotBeNull() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Field must not be null");
+ ResolvableType.forField(null);
+ }
+
+ @Test
+ public void forConstructorParameter() throws Exception {
+ Constructor constructor = Constructors.class.getConstructor(List.class);
+ ResolvableType type = ResolvableType.forConstructorParameter(constructor, 0);
+ assertThat(type.getType(), equalTo(constructor.getGenericParameterTypes()[0]));
+ }
+
+ @Test
+ public void forConstructorParameterMustNotBeNull() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Constructor must not be null");
+ ResolvableType.forConstructorParameter(null, 0);
+ }
+
+ @Test
+ public void forMethodParameterByIndex() throws Exception {
+ Method method = Methods.class.getMethod("charSequenceParameter", List.class);
+ ResolvableType type = ResolvableType.forMethodParameter(method, 0);
+ assertThat(type.getType(), equalTo(method.getGenericParameterTypes()[0]));
+ }
+
+ @Test
+ public void forMethodParameterByIndexMustNotBeNull() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Method must not be null");
+ ResolvableType.forMethodParameter(null, 0);
+ }
+
+ @Test
+ public void forMethodParameter() throws Exception {
+ Method method = Methods.class.getMethod("charSequenceParameter", List.class);
+ MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
+ assertThat(type.getType(), equalTo(method.getGenericParameterTypes()[0]));
+ }
+
+ @Test
+ public void forMethodParameterWithNesting() throws Exception {
+ Method method = Methods.class.getMethod("nested", Map.class);
+ MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ methodParameter.increaseNestingLevel();
+ ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
+ assertThat(type.resolve(), equalTo((Class) Map.class));
+ assertThat(type.getGeneric(0).resolve(), equalTo((Class) Byte.class));
+ assertThat(type.getGeneric(1).resolve(), equalTo((Class) Long.class));
+ }
+
+ @Test
+ public void forMethodParameterWithNestingAndLevels() throws Exception {
+ Method method = Methods.class.getMethod("nested", Map.class);
+ MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ methodParameter.increaseNestingLevel();
+ methodParameter.setTypeIndexForCurrentLevel(0);
+ ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
+ assertThat(type.resolve(), equalTo((Class) Map.class));
+ assertThat(type.getGeneric(0).resolve(), equalTo((Class) String.class));
+ assertThat(type.getGeneric(1).resolve(), equalTo((Class) Integer.class));
+ }
+
+ @Test
+ public void forMethodParameterMustNotBeNull() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("MethodParameter must not be null");
+ ResolvableType.forMethodParameter(null);
+ }
+
+ @Test
+ public void forMethodReturn() throws Exception {
+ Method method = Methods.class.getMethod("charSequenceReturn");
+ ResolvableType type = ResolvableType.forMethodReturn(method);
+ assertThat(type.getType(), equalTo(method.getGenericReturnType()));
+ }
+
+ @Test
+ public void forMethodReturnMustNotBeNull() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Method must not be null");
+ ResolvableType.forMethodReturn(null);
+ }
+
+ @Test
+ public void classType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("classType"));
+ assertThat(type.getType().getClass(), equalTo((Class) Class.class));
+ }
+
+ @Test
+ public void paramaterizedType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("parameterizedType"));
+ assertThat(type.getType(), instanceOf(ParameterizedType.class));
+ }
+
+ @Test
+ public void arrayClassType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("arrayClassType"));
+ assertThat(type.getType(), instanceOf(Class.class));
+ assertThat(((Class) type.getType()).isArray(), equalTo(true));
+ }
+
+ @Test
+ public void genericArrayType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("genericArrayType"));
+ assertThat(type.getType(), instanceOf(GenericArrayType.class));
+ }
+
+ @Test
+ public void wildcardType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("wildcardType"));
+ assertThat(type.getType(), instanceOf(ParameterizedType.class));
+ assertThat(type.getGeneric().getType(), instanceOf(WildcardType.class));
+ }
+
+ @Test
+ public void typeVariableType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("typeVariableType"));
+ assertThat(type.getType(), instanceOf(TypeVariable.class));
+ }
+
+ @Test
+ public void getComponentTypeForClassArray() throws Exception {
+ Field field = Fields.class.getField("arrayClassType");
+ ResolvableType type = ResolvableType.forField(field);
+ assertThat(type.isArray(), equalTo(true));
+ assertThat(type.getComponentType().getType(),
+ equalTo((Type) ((Class) field.getGenericType()).getComponentType()));
+ }
+
+ @Test
+ public void getComponentTypeForGenericArrayType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("genericArrayType"));
+ assertThat(type.isArray(), equalTo(true));
+ assertThat(type.getComponentType().getType(),
+ equalTo(((GenericArrayType) type.getType()).getGenericComponentType()));
+ }
+
+ @Test
+ public void getComponentTypeForVariableThatResolvesToGenericArray() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ListOfGenericArray.class).asCollection().getGeneric();
+ assertThat(type.isArray(), equalTo(true));
+ assertThat(type.getType(), instanceOf(TypeVariable.class));
+ assertThat(type.getComponentType().getType().toString(),
+ equalTo("java.util.List"));
+ }
+
+ @Test
+ public void getComponentTypeForNonArray() throws Exception {
+ ResolvableType type = ResolvableType.forClass(String.class);
+ assertThat(type.isArray(), equalTo(false));
+ assertThat(type.getComponentType(), equalTo(ResolvableType.NONE));
+ }
+
+ @Test
+ public void asCollection() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class).asCollection();
+ assertThat(type.resolve(), equalTo((Class) Collection.class));
+ assertThat(type.resolveGeneric(), equalTo((Class) CharSequence.class));
+ }
+
+ @Test
+ public void asMap() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsMap.class).asMap();
+ assertThat(type.resolve(), equalTo((Class) Map.class));
+ assertThat(type.resolveGeneric(0), equalTo((Class) String.class));
+ assertThat(type.resolveGeneric(1), equalTo((Class) Integer.class));
+ }
+
+ @Test
+ public void asFromInterface() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class).as(List.class);
+ assertThat(type.getType().toString(), equalTo("java.util.List"));
+ }
+
+ @Test
+ public void asFromInheritedInterface() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class).as(Collection.class);
+ assertThat(type.getType().toString(), equalTo("java.util.Collection"));
+ }
+
+ @Test
+ public void asFromSuperType() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class).as(ArrayList.class);
+ assertThat(type.getType().toString(), equalTo("java.util.ArrayList"));
+ }
+
+ @Test
+ public void asFromInheritedSuperType() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class).as(List.class);
+ assertThat(type.getType().toString(), equalTo("java.util.List"));
+ }
+
+ @Test
+ public void asNotFound() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class).as(Map.class);
+ assertThat(type, sameInstance(ResolvableType.NONE));
+ }
+
+ @Test
+ public void asSelf() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class);
+ assertThat(type.as(ExtendsList.class), equalTo(type));
+ }
+
+ @Test
+ public void getSuperType() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class).getSuperType();
+ assertThat(type.resolve(), equalTo((Class) ArrayList.class));
+ type = type.getSuperType();
+ assertThat(type.resolve(), equalTo((Class) AbstractList.class));
+ type = type.getSuperType();
+ assertThat(type.resolve(), equalTo((Class) AbstractCollection.class));
+ type = type.getSuperType();
+ assertThat(type.resolve(), equalTo((Class) Object.class));
+ }
+
+ @Test
+ public void getInterfaces() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class);
+ assertThat(type.getInterfaces().length, equalTo(0));
+ SortedSet interfaces = new TreeSet();
+ for (ResolvableType interfaceType : type.getSuperType().getInterfaces()) {
+ interfaces.add(interfaceType.toString());
+ }
+ assertThat(interfaces.toString(), equalTo(
+ "["
+ + "java.io.Serializable, "
+ + "java.lang.Cloneable, "
+ + "java.util.List, "
+ + "java.util.RandomAccess"
+ + "]"));
+ }
+
+ @Test
+ public void noSuperType() throws Exception {
+ assertThat(ResolvableType.forClass(Object.class).getSuperType(),
+ equalTo(ResolvableType.NONE));
+ }
+
+ @Test
+ public void noInterfaces() throws Exception {
+ assertThat(ResolvableType.forClass(Object.class).getInterfaces().length,
+ equalTo(0));
+ }
+
+ @Test
+ public void nested() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("nested"));
+ type = type.getNested(2);
+ assertThat(type.resolve(), equalTo((Class) Map.class));
+ assertThat(type.getGeneric(0).resolve(), equalTo((Class) Byte.class));
+ assertThat(type.getGeneric(1).resolve(), equalTo((Class) Long.class));
+ }
+
+ @Test
+ public void nestedWithIndexes() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("nested"));
+ type = type.getNested(2, Collections.singletonMap(2, 0));
+ assertThat(type.resolve(), equalTo((Class) Map.class));
+ assertThat(type.getGeneric(0).resolve(), equalTo((Class) String.class));
+ assertThat(type.getGeneric(1).resolve(), equalTo((Class) Integer.class));
+ }
+
+ @Test
+ public void nestedWithArray() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("genericArrayType"));
+ type = type.getNested(2);
+ assertThat(type.resolve(), equalTo((Class) List.class));
+ assertThat(type.resolveGeneric(), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void getGeneric() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList"));
+ assertThat(type.getGeneric().getType(), equalTo((Type) String.class));
+ }
+
+ @Test
+ public void getGenericByIndex() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("stringIntegerMultiValueMap"));
+ assertThat(type.getGeneric(0).getType(), equalTo((Type) String.class));
+ assertThat(type.getGeneric(1).getType(), equalTo((Type) Integer.class));
+ }
+
+ @Test
+ public void getGenericOfGeneric() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("stringListList"));
+ assertThat(type.getGeneric().getType().toString(), equalTo("java.util.List"));
+ assertThat(type.getGeneric().getGeneric().getType(), equalTo((Type) String.class));
+ }
+
+ @Test
+ public void getGenericOfGenericByIndexes() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("stringListList"));
+ assertThat(type.getGeneric(0, 0).getType(), equalTo((Type) String.class));
+ }
+
+ @Test
+ public void getGenericOutOfBounds() throws Exception {
+ ResolvableType type = ResolvableType.forClass(List.class, ExtendsList.class);
+ assertThat(type.getGeneric(0), not(equalTo(ResolvableType.NONE)));
+ assertThat(type.getGeneric(1), equalTo(ResolvableType.NONE));
+ assertThat(type.getGeneric(0, 1), equalTo(ResolvableType.NONE));
+ }
+
+ @Test
+ public void hasGenerics() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class);
+ assertThat(type.hasGenerics(), equalTo(false));
+ assertThat(type.asCollection().hasGenerics(), equalTo(true));
+ }
+
+ @Test
+ public void getGenerics() throws Exception {
+ ResolvableType type = ResolvableType.forClass(List.class, ExtendsList.class);
+ ResolvableType[] generics = type.getGenerics();
+ assertThat(generics.length, equalTo(1));
+ assertThat(generics[0].resolve(), equalTo((Class) CharSequence.class));
+ }
+
+ @Test
+ public void noGetGenerics() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class);
+ ResolvableType[] generics = type.getGenerics();
+ assertThat(generics.length, equalTo(0));
+ }
+
+ @Test
+ public void getResolvedGenerics() throws Exception {
+ ResolvableType type = ResolvableType.forClass(List.class, ExtendsList.class);
+ Class>[] generics = type.resolveGenerics();
+ assertThat(generics.length, equalTo(1));
+ assertThat(generics[0], equalTo((Class) CharSequence.class));
+ }
+
+ @Test
+ public void resolveClassType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("classType"));
+ assertThat(type.resolve(), equalTo((Class) List.class));
+ }
+
+ @Test
+ public void resolveParameterizedType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("parameterizedType"));
+ assertThat(type.resolve(), equalTo((Class) List.class));
+ }
+
+ @Test
+ public void resolveArrayClassType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("arrayClassType"));
+ assertThat(type.resolve(), equalTo((Class) List[].class));
+ }
+
+ @Test
+ public void resolveGenericArrayType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("genericArrayType"));
+ assertThat(type.resolve(), equalTo((Class) List[].class));
+ assertThat(type.getComponentType().resolve(), equalTo((Class) List.class));
+ assertThat(type.getComponentType().getGeneric().resolve(), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void resolveGenericMultiArrayType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("genericMultiArrayType"));
+ assertThat(type.resolve(), equalTo((Class) List[][][].class));
+ assertThat(type.getComponentType().resolve(), equalTo((Class) List[][].class));
+ }
+
+ @Test
+ public void resolveGenericArrayFromGeneric() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("stringArrayList"));
+ ResolvableType generic = type.asCollection().getGeneric();
+ assertThat(generic.getType().toString(), equalTo("E"));
+ assertThat(generic.isArray(), equalTo(true));
+ assertThat(generic.resolve(), equalTo((Class) String[].class));
+ }
+
+ @Test
+ public void resolveWildcardTypeUpperBounds() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("wildcardType"));
+ assertThat(type.getGeneric().resolve(), equalTo((Class) Number.class));
+ }
+
+ @Test
+ public void resolveWildcardLowerBounds() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("wildcardSuperType"));
+ assertThat(type.getGeneric().resolve(), equalTo((Class) Number.class));
+ }
+
+ @Test
+ public void resolveVariableFromFieldType() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList"));
+ assertThat(type.resolve(), equalTo((Class) List.class));
+ assertThat(type.getGeneric().resolve(), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void resolveVariableFromFieldTypeUnknown() throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField("parameterizedType"));
+ assertThat(type.resolve(), equalTo((Class) List.class));
+ assertThat(type.getGeneric().resolve(), nullValue());
+ }
+
+ @Test
+ public void resolveVariableFromInheritedField() throws Exception {
+ ResolvableType type = ResolvableType.forField(
+ Fields.class.getField("stringIntegerMultiValueMap")).as(Map.class);
+ assertThat(type.getGeneric(0).resolve(), equalTo((Class) String.class));
+ assertThat(type.getGeneric(1).resolve(), equalTo((Class) List.class));
+ assertThat(type.getGeneric(1, 0).resolve(), equalTo((Class) Integer.class));
+ }
+
+ @Test
+ public void resolveVariableFromInheritedFieldSwitched() throws Exception {
+ ResolvableType type = ResolvableType.forField(
+ Fields.class.getField("stringIntegerMultiValueMapSwitched")).as(Map.class);
+ assertThat(type.getGeneric(0).resolve(), equalTo((Class) String.class));
+ assertThat(type.getGeneric(1).resolve(), equalTo((Class) List.class));
+ assertThat(type.getGeneric(1, 0).resolve(), equalTo((Class) Integer.class));
+ }
+
+ @Test
+ public void doesResolveFromOuterOwner() throws Exception {
+ ResolvableType type = ResolvableType.forField(
+ Fields.class.getField("listOfListOfUnknown")).as(Collection.class);
+ ResolvableType generic = type.getGeneric(0);
+ assertThat(generic.resolve(), equalTo((Class) List.class));
+ assertThat(generic.as(Collection.class).getGeneric(0).as(Collection.class).resolve(), nullValue());
+ }
+
+ @Test
+ public void resolveBoundedTypeVariableResult() throws Exception {
+ ResolvableType type = ResolvableType.forMethodReturn(Methods.class.getMethod("boundedTypeVaraibleResult"));
+ assertThat(type.resolve(), equalTo((Class) CharSequence.class));
+ }
+
+ @Test
+ public void resolveVariableNotFound() throws Exception {
+ ResolvableType type = ResolvableType.forMethodReturn(Methods.class.getMethod("typedReturn"));
+ assertThat(type.resolve(), nullValue());
+ }
+
+ @Test
+ public void resolveTypeVaraibleFromMethodReturn() throws Exception {
+ ResolvableType type = ResolvableType.forMethodReturn(Methods.class.getMethod("typedReturn"));
+ assertThat(type.resolve(), nullValue());
+ }
+
+ @Test
+ public void resolveTypeVaraibleFromMethodReturnWithInstanceClass() throws Exception {
+ ResolvableType type = ResolvableType.forMethodReturn(
+ Methods.class.getMethod("typedReturn"), TypedMethods.class);
+ assertThat(type.resolve(), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void resolveTypeVaraibleFromSimpleInterfaceType() {
+ ResolvableType type = ResolvableType.forClass(
+ MySimpleInterfaceType.class).as(MyInterfaceType.class);
+ assertThat(type.resolveGeneric(), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void resolveTypeVaraibleFromSimpleCollectionInterfaceType() {
+ ResolvableType type = ResolvableType.forClass(
+ MyCollectionInterfaceType.class).as(MyInterfaceType.class);
+ assertThat(type.resolveGeneric(), equalTo((Class) Collection.class));
+ assertThat(type.resolveGeneric(0, 0), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void resolveTypeVaraibleFromSimpleSuperclassType() {
+ ResolvableType type = ResolvableType.forClass(
+ MySimpleSuperclassType.class).as(MySuperclassType.class);
+ assertThat(type.resolveGeneric(), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void resolveTypeVaraibleFromSimpleCollectionSuperclassType() {
+ ResolvableType type = ResolvableType.forClass(
+ MyCollectionSuperclassType.class).as(MySuperclassType.class);
+ assertThat(type.resolveGeneric(), equalTo((Class) Collection.class));
+ assertThat(type.resolveGeneric(0, 0), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void resolveTypeVariableFromFieldTypeWithImplementsClass() throws Exception {
+ ResolvableType type = ResolvableType.forField(
+ Fields.class.getField("parameterizedType"), TypedFields.class);
+ assertThat(type.resolve(), equalTo((Class) List.class));
+ assertThat(type.getGeneric().resolve(), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void resolveTypeVariableFromSuperType() throws Exception {
+ ResolvableType type = ResolvableType.forClass(ExtendsList.class);
+ assertThat(type.resolve(), equalTo((Class) ExtendsList.class));
+ assertThat(type.asCollection().resolveGeneric(),
+ equalTo((Class) CharSequence.class));
+ }
+
+ @Test
+ public void resolveTypeVariableFromClassWithImplementsClass() throws Exception {
+ ResolvableType type = ResolvableType.forClass(
+ MySuperclassType.class, MyCollectionSuperclassType.class);
+ assertThat(type.resolveGeneric(), equalTo((Class) Collection.class));
+ assertThat(type.resolveGeneric(0, 0), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void resolveTypeVariableFromConstructorParameter() throws Exception {
+ Constructor> constructor = Constructors.class.getConstructor(List.class);
+ ResolvableType type = ResolvableType.forConstructorParameter(constructor, 0);
+ assertThat(type.resolve(), equalTo((Class) List.class));
+ assertThat(type.resolveGeneric(0), equalTo((Class) CharSequence.class));
+ }
+
+ @Test
+ public void resolveUnknownTypeVariableFromConstructorParameter() throws Exception {
+ Constructor> constructor = Constructors.class.getConstructor(Map.class);
+ ResolvableType type = ResolvableType.forConstructorParameter(constructor, 0);
+ assertThat(type.resolve(), equalTo((Class) Map.class));
+ assertThat(type.resolveGeneric(0), nullValue());
+ }
+
+ @Test
+ public void resolveTypeVariableFromConstructorParameterWithImplementsClass() throws Exception {
+ Constructor> constructor = Constructors.class.getConstructor(Map.class);
+ ResolvableType type = ResolvableType.forConstructorParameter(
+ constructor, 0, TypedConstructors.class);
+ assertThat(type.resolve(), equalTo((Class) Map.class));
+ assertThat(type.resolveGeneric(0), equalTo((Class) String.class));
+ }
+
+ @Test
+ public void resolveTypeVariableFromMethodParameter() throws Exception {
+ Method method = Methods.class.getMethod("typedParameter", Object.class);
+ ResolvableType type = ResolvableType.forMethodParameter(method, 0);
+ assertThat(type.resolve(), nullValue());
+ assertThat(type.getType().toString(), equalTo("T"));
+ }
+
+ @Test
+ public void resolveTypeVariableFromMethodParameterWithImplementsClass() throws Exception {
+ Method method = Methods.class.getMethod("typedParameter", Object.class);
+ ResolvableType type = ResolvableType.forMethodParameter(method, 0, TypedMethods.class);
+ assertThat(type.resolve(), equalTo((Class) String.class));
+ assertThat(type.getType().toString(), equalTo("T"));
+ }
+
+ @Test
+ public void resolveTypeVariableFromMethodParameterType() throws Exception {
+ Method method = Methods.class.getMethod("typedParameter", Object.class);
+ MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
+ assertThat(type.resolve(), nullValue());
+ assertThat(type.getType().toString(), equalTo("T"));
+ }
+
+ @Test
+ public void resolveTypeVariableFromMethodParameterTypeWithImplementsClass()
+ throws Exception {
+ Method method = Methods.class.getMethod("typedParameter", Object.class);
+ MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0);
+ ResolvableType type = ResolvableType.forMethodParameter(methodParameter, TypedMethods.class);
+ assertThat(type.resolve(), equalTo((Class) String.class));
+ assertThat(type.getType().toString(), equalTo("T"));
+ }
+
+ @Test
+ public void resolveTypeVariableFromMethodReturn() throws Exception {
+ Method method = Methods.class.getMethod("typedReturn");
+ ResolvableType type = ResolvableType.forMethodReturn(method);
+ assertThat(type.resolve(), nullValue());
+ assertThat(type.getType().toString(), equalTo("T"));
+ }
+
+ @Test
+ public void resolveTypeVariableFromMethodReturnWithImplementsClass() throws Exception {
+ Method method = Methods.class.getMethod("typedReturn");
+ ResolvableType type = ResolvableType.forMethodReturn(method, TypedMethods.class);
+ assertThat(type.resolve(), equalTo((Class) String.class));
+ assertThat(type.getType().toString(), equalTo("T"));
+ }
+
+ @Test
+ public void resolveTypeVariableFromType() throws Exception {
+ Type sourceType = Methods.class.getMethod("typedReturn").getGenericReturnType();
+ ResolvableType type = ResolvableType.forType(sourceType);
+ assertThat(type.resolve(), nullValue());
+ assertThat(type.getType().toString(), equalTo("T"));
+ }
+
+ @Test
+ public void resolveTypeVariableFromTypeWithVariableResolver() throws Exception {
+ Type sourceType = Methods.class.getMethod("typedReturn").getGenericReturnType();
+ ResolvableType type = ResolvableType.forType(
+ sourceType, ResolvableType.forClass(TypedMethods.class).as(Methods.class));
+ assertThat(type.resolve(), equalTo((Class) String.class));
+ assertThat(type.getType().toString(), equalTo("T"));
+ }
+
+ @Test
+ public void resolveTypeWithCustomVariableResolver() throws Exception {
+ TypeVariableResolver variableResolver = mock(TypeVariableResolver.class);
+ given(variableResolver.resolveVariable((TypeVariable>) anyObject())).willReturn(Long.class);
+
+ ResolvableType variable = ResolvableType.forType(
+ Fields.class.getField("typeVariableType").getGenericType(), variableResolver);
+ ResolvableType parameterized = ResolvableType.forType(
+ Fields.class.getField("parameterizedType").getGenericType(), variableResolver);
+
+ assertThat(variable.resolve(), equalTo((Class) Long.class));
+ assertThat(parameterized.resolve(), equalTo((Class) List.class));
+ assertThat(parameterized.resolveGeneric(), equalTo((Class) Long.class));
+ verify(variableResolver, atLeastOnce()).resolveVariable(this.typeVariableCaptor.capture());
+ assertThat(this.typeVariableCaptor.getValue().getName(), equalTo("T"));
+ }
+
+ @Test
+ public void toStrings() throws Exception {
+ assertThat(ResolvableType.NONE.toString(), equalTo("?"));
+
+ assertFieldToStringValue("classType", "java.util.List");
+ assertFieldToStringValue("typeVariableType", "?");
+ assertFieldToStringValue("parameterizedType", "java.util.List>");
+ assertFieldToStringValue("arrayClassType", "java.util.List[]");
+ assertFieldToStringValue("genericArrayType", "java.util.List[]");
+ assertFieldToStringValue("genericMultiArrayType", "java.util.List[][][]");
+ assertFieldToStringValue("wildcardType", "java.util.List");
+ assertFieldToStringValue("wildcardSuperType", "java.util.List");
+ assertFieldToStringValue("charSequenceList", "java.util.List");
+ assertFieldToStringValue("stringList", "java.util.List");
+ assertFieldToStringValue("stringListList", "java.util.List>");
+ assertFieldToStringValue("stringArrayList", "java.util.List");
+ assertFieldToStringValue("stringIntegerMultiValueMap", "org.springframework.util.MultiValueMap");
+ assertFieldToStringValue("stringIntegerMultiValueMapSwitched", VariableNameSwitch.class.getName() + "");
+ assertFieldToStringValue("listOfListOfUnknown", "java.util.List");
+
+ assertTypedFieldToStringValue("typeVariableType", "java.lang.String");
+ assertTypedFieldToStringValue("parameterizedType", "java.util.List");
+
+ assertThat(ResolvableType.forClass(ListOfGenericArray.class).toString(), equalTo(ListOfGenericArray.class.getName()));
+ assertThat(ResolvableType.forClass(List.class, ListOfGenericArray.class).toString(), equalTo("java.util.List[]>"));
+ }
+
+ private void assertFieldToStringValue(String field, String expected) throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField(field));
+ assertThat("field " + field + " toString", type.toString(), equalTo(expected));
+ }
+
+ private void assertTypedFieldToStringValue(String field, String expected)
+ throws Exception {
+ ResolvableType type = ResolvableType.forField(Fields.class.getField(field), TypedFields.class);
+ assertThat("field " + field + " toString", type.toString(), equalTo(expected));
+ }
+
+ @Test
+ public void resolveFromOuterClass() throws Exception {
+ Field field = EnclosedInParameterizedType.InnerTyped.class.getField("field");
+ ResolvableType type = ResolvableType.forField(
+ field, TypedEnclosedInParameterizedType.TypedInnerTyped.class);
+ assertThat(type.resolve(), equalTo((Type) Integer.class));
+ }
+
+ @Test
+ public void isAssignableFromMustNotBeNull() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Type must not be null");
+ ResolvableType.forClass(Object.class).isAssignableFrom(null);
+ }
+
+ @Test
+ public void isAssignableFromForNone() throws Exception {
+ ResolvableType objectType = ResolvableType.forClass(Object.class);
+ assertThat(objectType.isAssignableFrom(ResolvableType.NONE), equalTo(false));
+ assertThat(ResolvableType.NONE.isAssignableFrom(objectType), equalTo(false));
+ }
+
+ @Test
+ public void isAssignableFromForClassAndClass() throws Exception {
+ ResolvableType objectType = ResolvableType.forClass(Object.class);
+ ResolvableType charSequenceType = ResolvableType.forClass(CharSequence.class);
+ ResolvableType stringType = ResolvableType.forClass(String.class);
+
+ assertAssignable(objectType, objectType, charSequenceType, stringType).equalTo(true, true, true);
+ assertAssignable(charSequenceType, objectType, charSequenceType, stringType).equalTo(false, true, true);
+ assertAssignable(stringType, objectType, charSequenceType, stringType).equalTo(false, false, true);
+ }
+
+ @Test
+ public void isAssignableFromCannotBeResolved() throws Exception {
+ ResolvableType objectType = ResolvableType.forClass(Object.class);
+ ResolvableType unresolvableVariable = ResolvableType.forField(AssignmentBase.class.getField("o"));
+ assertThat(unresolvableVariable.resolve(), nullValue());
+ assertAssignable(objectType, unresolvableVariable).equalTo(false);
+ assertAssignable(unresolvableVariable, objectType).equalTo(false);
+ }
+
+ @Test
+ public void isAssignableFromForClassAndSimpleVariable() throws Exception {
+ ResolvableType objectType = ResolvableType.forClass(Object.class);
+ ResolvableType charSequenceType = ResolvableType.forClass(CharSequence.class);
+ ResolvableType stringType = ResolvableType.forClass(String.class);
+
+ ResolvableType objectVariable = ResolvableType.forField(AssignmentBase.class.getField("o"), Assignment.class);
+ ResolvableType charSequenceVariable = ResolvableType.forField(AssignmentBase.class.getField("c"), Assignment.class);
+ ResolvableType stringVariable = ResolvableType.forField(AssignmentBase.class.getField("s"), Assignment.class);
+
+ assertAssignable(objectType, objectVariable, charSequenceVariable, stringVariable).equalTo(true, true, true);
+ assertAssignable(charSequenceType, objectVariable, charSequenceVariable, stringVariable).equalTo(false, true, true);
+ assertAssignable(stringType, objectVariable, charSequenceVariable, stringVariable).equalTo(false, false, true);
+
+ assertAssignable(objectVariable, objectType, charSequenceType, stringType).equalTo(true, true, true);
+ assertAssignable(charSequenceVariable, objectType, charSequenceType, stringType).equalTo(false, true, true);
+ assertAssignable(stringVariable, objectType, charSequenceType, stringType).equalTo(false, false, true);
+
+ assertAssignable(objectVariable, objectVariable, charSequenceVariable, stringVariable).equalTo(true, true, true);
+ assertAssignable(charSequenceVariable, objectVariable, charSequenceVariable, stringVariable).equalTo(false, true, true);
+ assertAssignable(stringVariable, objectVariable, charSequenceVariable, stringVariable).equalTo(false, false, true);
+ }
+
+ @Test
+ public void isAssignableFromForSameClassNonExtendsGenerics() throws Exception {
+ ResolvableType objectList = ResolvableType.forField(AssignmentBase.class.getField("listo"), Assignment.class);
+ ResolvableType stringList = ResolvableType.forField(AssignmentBase.class.getField("lists"), Assignment.class);
+
+ assertAssignable(stringList, objectList).equalTo(false);
+ assertAssignable(objectList, stringList).equalTo(false);
+ assertAssignable(stringList, stringList).equalTo(true);
+ }
+
+ @Test
+ public void isAssignableFromForSameClassExtendsGenerics() throws Exception {
+
+ // Generic assignment can be a little confusing, given:
+ //
+ // List c1, List extends CharSequence> c2, List s;
+ //
+ // c2 = s; is allowed and is often used for argument input, for example
+ // see List.addAll(). You can get items from c2 but you cannot add items without
+ // getting a generic type 'is not applicable for the arguments' error. This makes
+ // sense since if you added a StringBuffer to c2 it would break the rules on s.
+ //
+ // c1 = s; not allowed. Since there is no '? extends' to cause the generic
+ // 'is not applicable for the arguments' error when adding (which would pollute
+ // s).
+
+ ResolvableType objectList = ResolvableType.forField(AssignmentBase.class.getField("listo"), Assignment.class);
+ ResolvableType charSequenceList = ResolvableType.forField(AssignmentBase.class.getField("listc"), Assignment.class);
+ ResolvableType stringList = ResolvableType.forField(AssignmentBase.class.getField("lists"), Assignment.class);
+ ResolvableType extendsObjectList = ResolvableType.forField(AssignmentBase.class.getField("listxo"), Assignment.class);
+ ResolvableType extendsCharSequenceList = ResolvableType.forField(AssignmentBase.class.getField("listxc"), Assignment.class);
+ ResolvableType extendsStringList = ResolvableType.forField(AssignmentBase.class.getField("listxs"), Assignment.class);
+
+ assertAssignable(objectList, extendsObjectList, extendsCharSequenceList, extendsStringList).equalTo(false, false, false);
+ assertAssignable(charSequenceList, extendsObjectList, extendsCharSequenceList, extendsStringList).equalTo(false, false, false);
+ assertAssignable(stringList, extendsObjectList, extendsCharSequenceList, extendsStringList).equalTo(false, false, false);
+ assertAssignable(extendsObjectList, objectList, charSequenceList, stringList).equalTo(true, true, true);
+ assertAssignable(extendsObjectList, extendsObjectList, extendsCharSequenceList, extendsStringList).equalTo(true, true, true);
+ assertAssignable(extendsCharSequenceList, extendsObjectList, extendsCharSequenceList, extendsStringList).equalTo(false, true, true);
+ assertAssignable(extendsCharSequenceList, objectList, charSequenceList, stringList).equalTo(false, true, true);
+ assertAssignable(extendsStringList, extendsObjectList, extendsCharSequenceList, extendsStringList).equalTo(false, false, true);
+ assertAssignable(extendsStringList, objectList, charSequenceList, stringList).equalTo(false, false, true);
+ }
+
+ @Test
+ public void isAssignableFromForDifferentClassesWithGenerics() throws Exception {
+ ResolvableType extendsCharSequenceCollection = ResolvableType.forField(AssignmentBase.class.getField("collectionxc"), Assignment.class);
+ ResolvableType charSequenceCollection = ResolvableType.forField(AssignmentBase.class.getField("collectionc"), Assignment.class);
+ ResolvableType charSequenceList = ResolvableType.forField(AssignmentBase.class.getField("listc"), Assignment.class);
+ ResolvableType extendsCharSequenceList = ResolvableType.forField(AssignmentBase.class.getField("listxc"), Assignment.class);
+ ResolvableType extendsStringList = ResolvableType.forField(AssignmentBase.class.getField("listxs"), Assignment.class);
+
+ assertAssignable(extendsCharSequenceCollection, charSequenceCollection, charSequenceList, extendsCharSequenceList, extendsStringList)
+ .equalTo(true, true, true, true);
+ assertAssignable(charSequenceCollection, charSequenceList, extendsCharSequenceList, extendsStringList)
+ .equalTo(true, false, false);
+ assertAssignable(charSequenceList, extendsCharSequenceCollection, charSequenceCollection)
+ .equalTo(false, false);
+ assertAssignable(extendsCharSequenceList, extendsCharSequenceCollection, charSequenceCollection)
+ .equalTo(false, false);
+ assertAssignable(extendsStringList, charSequenceCollection, charSequenceList, extendsCharSequenceList)
+ .equalTo(false, false, false);
+ }
+
+ @Test
+ public void isAssignableFromForArrays() throws Exception {
+ ResolvableType object = ResolvableType.forField(AssignmentBase.class.getField("o"), Assignment.class);
+ ResolvableType objectArray = ResolvableType.forField(AssignmentBase.class.getField("oarray"), Assignment.class);
+ ResolvableType charSequenceArray = ResolvableType.forField(AssignmentBase.class.getField("carray"), Assignment.class);
+ ResolvableType stringArray = ResolvableType.forField(AssignmentBase.class.getField("sarray"), Assignment.class);
+
+ assertAssignable(object, objectArray, charSequenceArray, stringArray).
+ equalTo(true, true, true);
+ assertAssignable(objectArray, object, objectArray, charSequenceArray, stringArray).
+ equalTo(false, true, true, true);
+ assertAssignable(charSequenceArray, object, objectArray, charSequenceArray, stringArray).
+ equalTo(false, false, true, true);
+ assertAssignable(stringArray, object, objectArray, charSequenceArray, stringArray).
+ equalTo(false, false, false, true);
+ }
+
+ @Test
+ public void isAssignableFromForWildcards() throws Exception {
+
+ ResolvableType object = ResolvableType.forClass(Object.class);
+ ResolvableType charSequence = ResolvableType.forClass(CharSequence.class);
+ ResolvableType string = ResolvableType.forClass(String.class);
+ ResolvableType extendsObject = ResolvableType.forField(AssignmentBase.class.getField("listxo"), Assignment.class).getGeneric();
+ ResolvableType extendsCharSequence = ResolvableType.forField(AssignmentBase.class.getField("listxc"), Assignment.class).getGeneric();
+ ResolvableType extendsString = ResolvableType.forField(AssignmentBase.class.getField("listxs"), Assignment.class).getGeneric();
+ ResolvableType superObject = ResolvableType.forField(AssignmentBase.class.getField("listso"), Assignment.class).getGeneric();
+ ResolvableType superCharSequence = ResolvableType.forField(AssignmentBase.class.getField("listsc"), Assignment.class).getGeneric();
+ ResolvableType superString = ResolvableType.forField(AssignmentBase.class.getField("listss"), Assignment.class).getGeneric();
+
+ // Language Spec 4.5.1. Type Arguments and Wildcards
+
+ // ? extends T <= ? extends S if T <: S
+ assertAssignable(extendsCharSequence, extendsObject, extendsCharSequence, extendsString).
+ equalTo(false, true, true);
+ assertAssignable(extendsCharSequence, object, charSequence, string).
+ equalTo(false, true, true);
+
+ // ? super T <= ? super S if S <: T
+ assertAssignable(superCharSequence, superObject, superCharSequence, superString).
+ equalTo(true, true, false);
+ assertAssignable(superCharSequence, object, charSequence, string).
+ equalTo(true, true, false);
+
+ // [Implied] super / extends cannot be mixed
+ assertAssignable(superCharSequence, extendsObject, extendsCharSequence, extendsString).
+ equalTo(false, false, false);
+ assertAssignable(extendsCharSequence, superObject, superCharSequence, superString).
+ equalTo(false, false, false);
+
+ // T <= T
+ assertAssignable(charSequence, object, charSequence, string).
+ equalTo(false, true, true);
+
+ // T <= ? extends T
+ assertAssignable(extendsCharSequence, object, charSequence, string).
+ equalTo(false, true, true);
+ assertAssignable(charSequence, extendsObject, extendsCharSequence, extendsString).
+ equalTo(false, false, false);
+
+ // T <= ? super T
+ assertAssignable(superCharSequence, object, charSequence, string).
+ equalTo(true, true, false);
+ assertAssignable(charSequence, superObject, superCharSequence, superString).
+ equalTo(false, false, false);
+ }
+
+ @Test
+ public void isAssignableFromForComplexWildcards() throws Exception {
+ ResolvableType complex1 = ResolvableType.forField(AssignmentBase.class.getField("complexWildcard1"));
+ ResolvableType complex2 = ResolvableType.forField(AssignmentBase.class.getField("complexWildcard2"));
+ ResolvableType complex3 = ResolvableType.forField(AssignmentBase.class.getField("complexWildcard3"));
+ ResolvableType complex4 = ResolvableType.forField(AssignmentBase.class.getField("complexWildcard4"));
+
+ assertAssignable(complex1, complex2).equalTo(true);
+ assertAssignable(complex2, complex1).equalTo(false);
+ assertAssignable(complex3, complex4).equalTo(true);
+ assertAssignable(complex4, complex3).equalTo(false);
+ }
+
+ @Test
+ public void hashCodeAndEquals() throws Exception {
+ ResolvableType forClass = ResolvableType.forClass(List.class);
+ ResolvableType forFieldDirect = ResolvableType.forField(Fields.class.getDeclaredField("stringList"));
+ ResolvableType forFieldViaType = ResolvableType.forType(Fields.class.getDeclaredField("stringList").getGenericType());
+ ResolvableType forFieldWithImplementation = ResolvableType.forField(Fields.class.getDeclaredField("stringList"), TypedFields.class);
+
+ assertThat(forClass, equalTo(forClass));
+ assertThat(forClass.hashCode(), equalTo(forClass.hashCode()));
+ assertThat(forClass, not(equalTo(forFieldDirect)));
+ assertThat(forClass, not(equalTo(forFieldWithImplementation)));
+
+ assertThat(forFieldDirect, equalTo(forFieldDirect));
+ assertThat(forFieldDirect, equalTo(forFieldViaType));
+ assertThat(forFieldDirect, not(equalTo(forFieldWithImplementation)));
+ }
+
+ @SuppressWarnings("unused")
+ private HashMap> myMap;
+
+ @Test
+ public void javaDocSample() throws Exception {
+ ResolvableType t = ResolvableType.forField(getClass().getDeclaredField("myMap"));
+ assertThat(t.getSuperType().toString(), equalTo("java.util.AbstractMap>"));
+ assertThat(t.asMap().toString(), equalTo("java.util.Map>"));
+ assertThat(t.getGeneric(0).resolve(), equalTo((Class)Integer.class));
+ assertThat(t.getGeneric(1).resolve(), equalTo((Class)List.class));
+ assertThat(t.getGeneric(1).toString(), equalTo("java.util.List"));
+ assertThat(t.resolveGeneric(1, 0), equalTo((Class) String.class));
+ }
+
+
+ private static AssertAssignbleMatcher assertAssignable(final ResolvableType type,
+ final ResolvableType... fromTypes) {
+ return new AssertAssignbleMatcher() {
+ @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]));
+ }
+ }
+ };
+ }
+
+ private static String stringDesc(ResolvableType type) {
+ if (type == ResolvableType.NONE) {
+ return "NONE";
+ }
+ if (type.getType().getClass().equals(Class.class)) {
+ return type.toString();
+ }
+ return type.getType() + ":" + type;
+ }
+
+
+ private static interface AssertAssignbleMatcher {
+
+ void equalTo(boolean... values);
+
+ }
+
+
+ static class ExtendsList extends ArrayList {
+
+ }
+
+
+ static class ExtendsMap extends HashMap {
+
+ }
+
+
+ static class Fields {
+
+ public List classType;
+
+ public T typeVariableType;
+
+ public List parameterizedType;
+
+ public List[] arrayClassType;
+
+ public List[] genericArrayType;
+
+ public List[][][] genericMultiArrayType;
+
+ public List extends Number> wildcardType;
+
+ public List super Number> wildcardSuperType = new ArrayList