/* * 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>(); /** * Registers the type variables for the given type and all of its superclasses and superinterfaces. */ protected void registerAllTypeVariables(Type classType) { Queue<Type> typesToRegister = new LinkedList<Type>(); Set<Type> registeredTypes = new HashSet<Type>(); typesToRegister.add(classType); while (!typesToRegister.isEmpty()) { Type typeToRegister = typesToRegister.poll(); if (typeToRegister == null || registeredTypes.contains(typeToRegister)) { continue; } registerTypeVariablesOn(typeToRegister); registeredTypes.add(typeToRegister); Class<?> rawType = extractRawTypeOf(typeToRegister); typesToRegister.add(rawType.getGenericSuperclass()); typesToRegister.addAll(Arrays.asList(rawType.getGenericInterfaces())); } } protected 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 + "'"); } 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); int arity = 0; while(genericReturnType instanceof GenericArrayType) { arity++; genericReturnType = ((GenericArrayType) genericReturnType).getGenericComponentType(); } GenericMetadataSupport genericMetadataSupport = resolveGenericType(genericReturnType, method); if (arity == 0) { return genericMetadataSupport; } else { return new GenericArrayReturnType(genericMetadataSupport, arity); } } private GenericMetadataSupport resolveGenericType(Type type, Method method) { if (type instanceof Class) { return new NotGenericReturnTypeSupport(this, type); } if (type instanceof ParameterizedType) { return new ParameterizedReturnType(this, method.getTypeParameters(), (ParameterizedType) type); } if (type instanceof TypeVariable) { return new TypeVariableReturnType(this, method.getTypeParameters(), (TypeVariable<?>) type); } throw new MockitoException("Ouch, it shouldn't happen, type '" + type.getClass().getCanonicalName() + "' on method : '" + method.toGenericString() + "' is not supported : " + type); } /** * 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; registerTypeParametersOn(clazz.getTypeParameters()); registerAllTypeVariables(clazz); } @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() { registerAllTypeVariables(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; } @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. } } private static class GenericArrayReturnType extends GenericMetadataSupport { private final GenericMetadataSupport genericArrayType; private final int arity; public GenericArrayReturnType(GenericMetadataSupport genericArrayType, int arity) { this.genericArrayType = genericArrayType; this.arity = arity; } @Override public Class<?> rawType() { Class<?> rawComponentType = genericArrayType.rawType(); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < arity; i++) { stringBuilder.append("["); } try { return Class.forName(stringBuilder.append("L").append(rawComponentType.getName()).append(";").toString(), false, rawComponentType.getClassLoader()); } catch (ClassNotFoundException e) { throw new IllegalStateException("This was not supposed to happend", e); } } } /** * Non-Generic metadata for {@link Class} returned via {@link Method#getGenericReturnType()}. */ private static class NotGenericReturnTypeSupport extends GenericMetadataSupport { private final Class<?> returnType; public NotGenericReturnTypeSupport(GenericMetadataSupport source, Type genericReturnType) { returnType = (Class<?>) genericReturnType; this.contextualActualTypeParameters = source.contextualActualTypeParameters; registerAllTypeVariables(returnType); } @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; } } }