/* | |
* Copyright (c) 2007 Mockito contributors | |
* This program is made available under the terms of the MIT License. | |
*/ | |
package org.mockito.internal.util.reflection; | |
import org.mockito.exceptions.base.MockitoException; | |
import org.mockito.internal.util.Checks; | |
import java.lang.reflect.*; | |
import java.util.*; | |
/** | |
* This class can retrieve generic meta-data that the compiler stores on classes | |
* and accessible members. | |
* | |
* <p> | |
* The main idea of this code is to create a Map that will help to resolve return types. | |
* In order to actually work with nested generics, this map will have to be passed along new instances | |
* as a type context. | |
* </p> | |
* | |
* <p> | |
* Hence : | |
* <ul> | |
* <li>A new instance representing the metadata is created using the {@link #inferFrom(Type)} method from a real | |
* <code>Class</code> or from a <code>ParameterizedType</code>, other types are not yet supported.</li> | |
* | |
* <li>Then from this metadata, we can extract meta-data for a generic return type of a method, using | |
* {@link #resolveGenericReturnType(Method)}.</li> | |
* </ul> | |
* </p> | |
* | |
* <p> | |
* For now this code support the following kind of generic declarations : | |
* <pre class="code"><code class="java"> | |
* interface GenericsNest<K extends Comparable<K> & Cloneable> extends Map<K, Set<Number>> { | |
* Set<Number> remove(Object key); // override with fixed ParameterizedType | |
* List<? super Integer> returning_wildcard_with_class_lower_bound(); | |
* List<? super K> returning_wildcard_with_typeVar_lower_bound(); | |
* List<? extends K> returning_wildcard_with_typeVar_upper_bound(); | |
* K returningK(); | |
* <O extends K> List<O> paramType_with_type_params(); | |
* <S, T extends S> T two_type_params(); | |
* <O extends K> O typeVar_with_type_params(); | |
* Number returningNonGeneric(); | |
* } | |
* </code></pre> | |
* | |
* @see #inferFrom(Type) | |
* @see #resolveGenericReturnType(Method) | |
* @see org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs | |
*/ | |
public abstract class GenericMetadataSupport { | |
// public static MockitoLogger logger = new ConsoleMockitoLogger(); | |
/** | |
* Represents actual type variables resolved for current class. | |
*/ | |
protected Map<TypeVariable, Type> contextualActualTypeParameters = new HashMap<TypeVariable, Type>(); | |
protected void registerTypeVariablesOn(Type classType) { | |
if (!(classType instanceof ParameterizedType)) { | |
return; | |
} | |
ParameterizedType parameterizedType = (ParameterizedType) classType; | |
TypeVariable[] typeParameters = ((Class<?>) parameterizedType.getRawType()).getTypeParameters(); | |
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); | |
for (int i = 0; i < actualTypeArguments.length; i++) { | |
TypeVariable typeParameter = typeParameters[i]; | |
Type actualTypeArgument = actualTypeArguments[i]; | |
if (actualTypeArgument instanceof WildcardType) { | |
contextualActualTypeParameters.put(typeParameter, boundsOf((WildcardType) actualTypeArgument)); | |
} else if (typeParameter != actualTypeArgument) { | |
contextualActualTypeParameters.put(typeParameter, actualTypeArgument); | |
} | |
// logger.log("For '" + parameterizedType + "' found type variable : { '" + typeParameter + "(" + System.identityHashCode(typeParameter) + ")" + "' : '" + actualTypeArgument + "(" + System.identityHashCode(typeParameter) + ")" + "' }"); | |
} | |
} | |
protected void registerTypeParametersOn(TypeVariable[] typeParameters) { | |
for (TypeVariable type : typeParameters) { | |
registerTypeVariableIfNotPresent(type); | |
} | |
} | |
private void registerTypeVariableIfNotPresent(TypeVariable typeVariable) { | |
if (!contextualActualTypeParameters.containsKey(typeVariable)) { | |
contextualActualTypeParameters.put(typeVariable, boundsOf(typeVariable)); | |
// logger.log("For '" + typeVariable.getGenericDeclaration() + "' found type variable : { '" + typeVariable + "(" + System.identityHashCode(typeVariable) + ")" + "' : '" + boundsOf(typeVariable) + "' }"); | |
} | |
} | |
/** | |
* @param typeParameter The TypeVariable parameter | |
* @return A {@link BoundedType} for easy bound information, if first bound is a TypeVariable | |
* then retrieve BoundedType of this TypeVariable | |
*/ | |
private BoundedType boundsOf(TypeVariable typeParameter) { | |
if (typeParameter.getBounds()[0] instanceof TypeVariable) { | |
return boundsOf((TypeVariable) typeParameter.getBounds()[0]); | |
} | |
return new TypeVarBoundedType(typeParameter); | |
} | |
/** | |
* @param wildCard The WildCard type | |
* @return A {@link BoundedType} for easy bound information, if first bound is a TypeVariable | |
* then retrieve BoundedType of this TypeVariable | |
*/ | |
private BoundedType boundsOf(WildcardType wildCard) { | |
/* | |
* According to JLS(http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.5.1): | |
* - Lower and upper can't coexist: (for instance, this is not allowed: <? extends List<String> & super MyInterface>) | |
* - Multiple bounds are not supported (for instance, this is not allowed: <? extends List<String> & MyInterface>) | |
*/ | |
WildCardBoundedType wildCardBoundedType = new WildCardBoundedType(wildCard); | |
if (wildCardBoundedType.firstBound() instanceof TypeVariable) { | |
return boundsOf((TypeVariable) wildCardBoundedType.firstBound()); | |
} | |
return wildCardBoundedType; | |
} | |
/** | |
* @return Raw type of the current instance. | |
*/ | |
public abstract Class<?> rawType(); | |
/** | |
* @return Returns extra interfaces <strong>if relevant</strong>, otherwise empty List. | |
*/ | |
public List<Type> extraInterfaces() { | |
return Collections.emptyList(); | |
} | |
/** | |
* @return Returns an array with the raw types of {@link #extraInterfaces()} <strong>if relevant</strong>. | |
*/ | |
public Class<?>[] rawExtraInterfaces() { | |
return new Class[0]; | |
} | |
/** | |
* @return Returns true if metadata knows about extra-interfaces {@link #extraInterfaces()} <strong>if relevant</strong>. | |
*/ | |
public boolean hasRawExtraInterfaces() { | |
return rawExtraInterfaces().length > 0; | |
} | |
/** | |
* @return Actual type arguments matching the type variables of the raw type represented by this {@link GenericMetadataSupport} instance. | |
*/ | |
public Map<TypeVariable, Type> actualTypeArguments() { | |
TypeVariable[] typeParameters = rawType().getTypeParameters(); | |
LinkedHashMap<TypeVariable, Type> actualTypeArguments = new LinkedHashMap<TypeVariable, Type>(); | |
for (TypeVariable typeParameter : typeParameters) { | |
Type actualType = getActualTypeArgumentFor(typeParameter); | |
actualTypeArguments.put(typeParameter, actualType); | |
// logger.log("For '" + rawType().getCanonicalName() + "' returning explicit TypeVariable : { '" + typeParameter + "(" + System.identityHashCode(typeParameter) + ")" + "' : '" + actualType +"' }"); | |
} | |
return actualTypeArguments; | |
} | |
protected Type getActualTypeArgumentFor(TypeVariable typeParameter) { | |
Type type = this.contextualActualTypeParameters.get(typeParameter); | |
if (type instanceof TypeVariable) { | |
TypeVariable typeVariable = (TypeVariable) type; | |
return getActualTypeArgumentFor(typeVariable); | |
} | |
return type; | |
} | |
/** | |
* Resolve current method generic return type to a {@link GenericMetadataSupport}. | |
* | |
* @param method Method to resolve the return type. | |
* @return {@link GenericMetadataSupport} representing this generic return type. | |
*/ | |
public GenericMetadataSupport resolveGenericReturnType(Method method) { | |
Type genericReturnType = method.getGenericReturnType(); | |
// logger.log("Method '" + method.toGenericString() + "' has return type : " + genericReturnType.getClass().getInterfaces()[0].getSimpleName() + " : " + genericReturnType); | |
if (genericReturnType instanceof Class) { | |
return new NotGenericReturnTypeSupport(genericReturnType); | |
} | |
if (genericReturnType instanceof ParameterizedType) { | |
return new ParameterizedReturnType(this, method.getTypeParameters(), (ParameterizedType) method.getGenericReturnType()); | |
} | |
if (genericReturnType instanceof TypeVariable) { | |
return new TypeVariableReturnType(this, method.getTypeParameters(), (TypeVariable) genericReturnType); | |
} | |
throw new MockitoException("Ouch, it shouldn't happen, type '" + genericReturnType.getClass().getCanonicalName() + "' on method : '" + method.toGenericString() + "' is not supported : " + genericReturnType); | |
} | |
/** | |
* Create an new instance of {@link GenericMetadataSupport} inferred from a {@link Type}. | |
* | |
* <p> | |
* At the moment <code>type</code> can only be a {@link Class} or a {@link ParameterizedType}, otherwise | |
* it'll throw a {@link MockitoException}. | |
* </p> | |
* | |
* @param type The class from which the {@link GenericMetadataSupport} should be built. | |
* @return The new {@link GenericMetadataSupport}. | |
* @throws MockitoException Raised if type is not a {@link Class} or a {@link ParameterizedType}. | |
*/ | |
public static GenericMetadataSupport inferFrom(Type type) { | |
Checks.checkNotNull(type, "type"); | |
if (type instanceof Class) { | |
return new FromClassGenericMetadataSupport((Class<?>) type); | |
} | |
if (type instanceof ParameterizedType) { | |
return new FromParameterizedTypeGenericMetadataSupport((ParameterizedType) type); | |
} | |
throw new MockitoException("Type meta-data for this Type (" + type.getClass().getCanonicalName() + ") is not supported : " + type); | |
} | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
//// Below are specializations of GenericMetadataSupport that could handle retrieval of possible Types | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
/** | |
* Generic metadata implementation for {@link Class}. | |
* | |
* Offer support to retrieve generic metadata on a {@link Class} by reading type parameters and type variables on | |
* the class and its ancestors and interfaces. | |
*/ | |
private static class FromClassGenericMetadataSupport extends GenericMetadataSupport { | |
private final Class<?> clazz; | |
public FromClassGenericMetadataSupport(Class<?> clazz) { | |
this.clazz = clazz; | |
for (Class currentExploredClass = clazz; | |
currentExploredClass != null && currentExploredClass != Object.class; | |
currentExploredClass = superClassOf(currentExploredClass) | |
) { | |
readActualTypeParametersOnDeclaringClass(currentExploredClass); | |
} | |
} | |
private Class superClassOf(Class currentExploredClass) { | |
Type genericSuperclass = currentExploredClass.getGenericSuperclass(); | |
if (genericSuperclass instanceof ParameterizedType) { | |
Type rawType = ((ParameterizedType) genericSuperclass).getRawType(); | |
return (Class) rawType; | |
} | |
return (Class) genericSuperclass; | |
} | |
private void readActualTypeParametersOnDeclaringClass(Class<?> clazz) { | |
registerTypeParametersOn(clazz.getTypeParameters()); | |
registerTypeVariablesOn(clazz.getGenericSuperclass()); | |
for (Type genericInterface : clazz.getGenericInterfaces()) { | |
registerTypeVariablesOn(genericInterface); | |
} | |
} | |
@Override | |
public Class<?> rawType() { | |
return clazz; | |
} | |
} | |
/** | |
* Generic metadata implementation for "standalone" {@link ParameterizedType}. | |
* | |
* Offer support to retrieve generic metadata on a {@link ParameterizedType} by reading type variables of | |
* the related raw type and declared type variable of this parameterized type. | |
* | |
* This class is not designed to work on ParameterizedType returned by {@link Method#getGenericReturnType()}, as | |
* the ParameterizedType instance return in these cases could have Type Variables that refer to type declaration(s). | |
* That's what meant the "standalone" word at the beginning of the Javadoc. | |
* Instead use {@link ParameterizedReturnType}. | |
*/ | |
private static class FromParameterizedTypeGenericMetadataSupport extends GenericMetadataSupport { | |
private final ParameterizedType parameterizedType; | |
public FromParameterizedTypeGenericMetadataSupport(ParameterizedType parameterizedType) { | |
this.parameterizedType = parameterizedType; | |
readActualTypeParameters(); | |
} | |
private void readActualTypeParameters() { | |
registerTypeVariablesOn(parameterizedType.getRawType()); | |
registerTypeVariablesOn(parameterizedType); | |
} | |
@Override | |
public Class<?> rawType() { | |
return (Class<?>) parameterizedType.getRawType(); | |
} | |
} | |
/** | |
* Generic metadata specific to {@link ParameterizedType} returned via {@link Method#getGenericReturnType()}. | |
*/ | |
private static class ParameterizedReturnType extends GenericMetadataSupport { | |
private final ParameterizedType parameterizedType; | |
private final TypeVariable[] typeParameters; | |
public ParameterizedReturnType(GenericMetadataSupport source, TypeVariable[] typeParameters, ParameterizedType parameterizedType) { | |
this.parameterizedType = parameterizedType; | |
this.typeParameters = typeParameters; | |
this.contextualActualTypeParameters = source.contextualActualTypeParameters; | |
readTypeParameters(); | |
readTypeVariables(); | |
} | |
private void readTypeParameters() { | |
registerTypeParametersOn(typeParameters); | |
} | |
private void readTypeVariables() { | |
registerTypeVariablesOn(parameterizedType); | |
} | |
@Override | |
public Class<?> rawType() { | |
return (Class<?>) parameterizedType.getRawType(); | |
} | |
} | |
/** | |
* Generic metadata for {@link TypeVariable} returned via {@link Method#getGenericReturnType()}. | |
*/ | |
private static class TypeVariableReturnType extends GenericMetadataSupport { | |
private final TypeVariable typeVariable; | |
private final TypeVariable[] typeParameters; | |
private Class<?> rawType; | |
public TypeVariableReturnType(GenericMetadataSupport source, TypeVariable[] typeParameters, TypeVariable typeVariable) { | |
this.typeParameters = typeParameters; | |
this.typeVariable = typeVariable; | |
this.contextualActualTypeParameters = source.contextualActualTypeParameters; | |
readTypeParameters(); | |
readTypeVariables(); | |
} | |
private void readTypeParameters() { | |
registerTypeParametersOn(typeParameters); | |
} | |
private void readTypeVariables() { | |
for (Type type : typeVariable.getBounds()) { | |
registerTypeVariablesOn(type); | |
} | |
registerTypeParametersOn(new TypeVariable[] { typeVariable }); | |
registerTypeVariablesOn(getActualTypeArgumentFor(typeVariable)); | |
} | |
@Override | |
public Class<?> rawType() { | |
if (rawType == null) { | |
rawType = extractRawTypeOf(typeVariable); | |
} | |
return rawType; | |
} | |
private Class<?> extractRawTypeOf(Type type) { | |
if (type instanceof Class) { | |
return (Class<?>) type; | |
} | |
if (type instanceof ParameterizedType) { | |
return (Class<?>) ((ParameterizedType) type).getRawType(); | |
} | |
if (type instanceof BoundedType) { | |
return extractRawTypeOf(((BoundedType) type).firstBound()); | |
} | |
if (type instanceof TypeVariable) { | |
/* | |
* If type is a TypeVariable, then it is needed to gather data elsewhere. Usually TypeVariables are declared | |
* on the class definition, such as such as List<E>. | |
*/ | |
return extractRawTypeOf(contextualActualTypeParameters.get(type)); | |
} | |
throw new MockitoException("Raw extraction not supported for : '" + type + "'"); | |
} | |
@Override | |
public List<Type> extraInterfaces() { | |
Type type = extractActualBoundedTypeOf(typeVariable); | |
if (type instanceof BoundedType) { | |
return Arrays.asList(((BoundedType) type).interfaceBounds()); | |
} | |
if (type instanceof ParameterizedType) { | |
return Collections.singletonList(type); | |
} | |
if (type instanceof Class) { | |
return Collections.emptyList(); | |
} | |
throw new MockitoException("Cannot extract extra-interfaces from '" + typeVariable + "' : '" + type + "'"); | |
} | |
/** | |
* @return Returns an array with the extracted raw types of {@link #extraInterfaces()}. | |
* @see #extractRawTypeOf(java.lang.reflect.Type) | |
*/ | |
public Class<?>[] rawExtraInterfaces() { | |
List<Type> extraInterfaces = extraInterfaces(); | |
List<Class<?>> rawExtraInterfaces = new ArrayList<Class<?>>(); | |
for (Type extraInterface : extraInterfaces) { | |
Class<?> rawInterface = extractRawTypeOf(extraInterface); | |
// avoid interface collision with actual raw type (with typevariables, resolution ca be quite aggressive) | |
if(!rawType().equals(rawInterface)) { | |
rawExtraInterfaces.add(rawInterface); | |
} | |
} | |
return rawExtraInterfaces.toArray(new Class[rawExtraInterfaces.size()]); | |
} | |
private Type extractActualBoundedTypeOf(Type type) { | |
if (type instanceof TypeVariable) { | |
/* | |
If type is a TypeVariable, then it is needed to gather data elsewhere. Usually TypeVariables are declared | |
on the class definition, such as such as List<E>. | |
*/ | |
return extractActualBoundedTypeOf(contextualActualTypeParameters.get(type)); | |
} | |
if (type instanceof BoundedType) { | |
Type actualFirstBound = extractActualBoundedTypeOf(((BoundedType) type).firstBound()); | |
if (!(actualFirstBound instanceof BoundedType)) { | |
return type; // avoid going one step further, ie avoid : O(TypeVar) -> K(TypeVar) -> Some ParamType | |
} | |
return actualFirstBound; | |
} | |
return type; // irrelevant, we don't manage other types as they are not bounded. | |
} | |
} | |
/** | |
* Non-Generic metadata for {@link Class} returned via {@link Method#getGenericReturnType()}. | |
*/ | |
private static class NotGenericReturnTypeSupport extends GenericMetadataSupport { | |
private final Class<?> returnType; | |
public NotGenericReturnTypeSupport(Type genericReturnType) { | |
returnType = (Class<?>) genericReturnType; | |
} | |
@Override | |
public Class<?> rawType() { | |
return returnType; | |
} | |
} | |
/** | |
* Type representing bounds of a type | |
* | |
* @see TypeVarBoundedType | |
* @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4</a> | |
* @see WildCardBoundedType | |
* @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.5.1">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.5.1</a> | |
*/ | |
public interface BoundedType extends Type { | |
Type firstBound(); | |
Type[] interfaceBounds(); | |
} | |
/** | |
* Type representing bounds of a type variable, allows to keep all bounds information. | |
* | |
* <p>It uses the first bound in the array, as this array is never null and always contains at least | |
* one element (Object is always here if no bounds are declared).</p> | |
* | |
* <p>If upper bounds are declared with SomeClass and additional interfaces, then firstBound will be SomeClass and | |
* interfacesBound will be an array of the additional interfaces. | |
* | |
* i.e. <code>SomeClass</code>. | |
* <pre class="code"><code class="java"> | |
* interface UpperBoundedTypeWithClass<E extends Comparable<E> & Cloneable> { | |
* E get(); | |
* } | |
* // will return Comparable type | |
* </code></pre> | |
* </p> | |
* | |
* @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4</a> | |
*/ | |
public static class TypeVarBoundedType implements BoundedType { | |
private final TypeVariable typeVariable; | |
public TypeVarBoundedType(TypeVariable typeVariable) { | |
this.typeVariable = typeVariable; | |
} | |
/** | |
* @return either a class or an interface (parameterized or not), if no bounds declared Object is returned. | |
*/ | |
public Type firstBound() { | |
return typeVariable.getBounds()[0]; // | |
} | |
/** | |
* On a Type Variable (typeVar extends C_0 & I_1 & I_2 & etc), will return an array | |
* containing I_1 and I_2. | |
* | |
* @return other bounds for this type, these bounds can only be only interfaces as the JLS says, | |
* empty array if no other bound declared. | |
*/ | |
public Type[] interfaceBounds() { | |
Type[] interfaceBounds = new Type[typeVariable.getBounds().length - 1]; | |
System.arraycopy(typeVariable.getBounds(), 1, interfaceBounds, 0, typeVariable.getBounds().length - 1); | |
return interfaceBounds; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
return typeVariable.equals(((TypeVarBoundedType) o).typeVariable); | |
} | |
@Override | |
public int hashCode() { | |
return typeVariable.hashCode(); | |
} | |
@Override | |
public String toString() { | |
return "{firstBound=" + firstBound() + ", interfaceBounds=" + Arrays.deepToString(interfaceBounds()) + '}'; | |
} | |
public TypeVariable typeVariable() { | |
return typeVariable; | |
} | |
} | |
/** | |
* Type representing bounds of a wildcard, allows to keep all bounds information. | |
* | |
* <p>The JLS says that lower bound and upper bound are mutually exclusive, and that multiple bounds | |
* are not allowed. | |
* | |
* @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4</a> | |
*/ | |
public static class WildCardBoundedType implements BoundedType { | |
private final WildcardType wildcard; | |
public WildCardBoundedType(WildcardType wildcard) { | |
this.wildcard = wildcard; | |
} | |
/** | |
* @return The first bound, either a type or a reference to a TypeVariable | |
*/ | |
public Type firstBound() { | |
Type[] lowerBounds = wildcard.getLowerBounds(); | |
Type[] upperBounds = wildcard.getUpperBounds(); | |
return lowerBounds.length != 0 ? lowerBounds[0] : upperBounds[0]; | |
} | |
/** | |
* @return An empty array as, wildcard don't support multiple bounds. | |
*/ | |
public Type[] interfaceBounds() { | |
return new Type[0]; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
return wildcard.equals(((TypeVarBoundedType) o).typeVariable); | |
} | |
@Override | |
public int hashCode() { | |
return wildcard.hashCode(); | |
} | |
@Override | |
public String toString() { | |
return "{firstBound=" + firstBound() + ", interfaceBounds=[]}"; | |
} | |
public WildcardType wildCard() { | |
return wildcard; | |
} | |
} | |
} | |