org.mockito.internal.configuration.injection.filter.TypeBasedCandidateFilter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mockito-core Show documentation
Show all versions of mockito-core Show documentation
Mockito mock objects library core API and implementation
/*
* Copyright (c) 2007 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.configuration.injection.filter;
import static org.mockito.internal.exceptions.Reporter.moreThanOneMockCandidate;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
import org.mockito.internal.util.MockUtil;
public class TypeBasedCandidateFilter implements MockCandidateFilter {
private final MockCandidateFilter next;
public TypeBasedCandidateFilter(MockCandidateFilter next) {
this.next = next;
}
protected boolean isCompatibleTypes(Type typeToMock, Type mockType, Field injectMocksField) {
boolean result = false;
if (typeToMock instanceof ParameterizedType) {
if (mockType instanceof ParameterizedType) {
// ParameterizedType.equals() is documented as:
// "Instances of classes that implement this interface must implement
// an equals() method that equates any two instances that share the
// same generic type declaration and have equal type parameters."
// Unfortunately, e.g. Wildcard parameter "?" doesn't equal java.lang.String,
// and e.g. Set doesn't equal TreeSet, so roll our own comparison if
// ParameterizedTypeImpl.equals() returns false
if (typeToMock.equals(mockType)) {
result = true;
} else {
ParameterizedType genericTypeToMock = (ParameterizedType) typeToMock;
ParameterizedType genericMockType = (ParameterizedType) mockType;
Type[] actualTypeArguments = genericTypeToMock.getActualTypeArguments();
Type[] actualTypeArguments2 = genericMockType.getActualTypeArguments();
// Recurse on type parameters, so we properly test whether e.g. Wildcard bounds
// have a match
result =
recurseOnTypeArguments(
injectMocksField, actualTypeArguments, actualTypeArguments2);
}
} else {
// mockType is a non-parameterized Class, i.e. a concrete class.
// so walk concrete class' type hierarchy
Class concreteMockClass = (Class) mockType;
Stream mockSuperTypes = getSuperTypes(concreteMockClass);
result =
mockSuperTypes.anyMatch(
mockSuperType ->
isCompatibleTypes(
typeToMock, mockSuperType, injectMocksField));
}
} else if (typeToMock instanceof WildcardType) {
WildcardType wildcardTypeToMock = (WildcardType) typeToMock;
Type[] upperBounds = wildcardTypeToMock.getUpperBounds();
result =
Arrays.stream(upperBounds)
.anyMatch(t -> isCompatibleTypes(t, mockType, injectMocksField));
} else if (typeToMock instanceof Class && mockType instanceof Class) {
result = ((Class) typeToMock).isAssignableFrom((Class) mockType);
} // no need to check for GenericArrayType, as Mockito cannot mock this anyway
return result;
}
private Stream getSuperTypes(Class concreteMockClass) {
Stream mockInterfaces = Arrays.stream(concreteMockClass.getGenericInterfaces());
Type genericSuperclass = concreteMockClass.getGenericSuperclass();
// for java.lang.Object, genericSuperclass is null
if (genericSuperclass != null) {
Stream mockSuperTypes =
Stream.concat(mockInterfaces, Stream.of(genericSuperclass));
return mockSuperTypes;
} else {
return mockInterfaces;
}
}
private boolean recurseOnTypeArguments(
Field injectMocksField, Type[] actualTypeArguments, Type[] actualTypeArguments2) {
boolean isCompatible = true;
for (int i = 0; i < actualTypeArguments.length; i++) {
Type actualTypeArgument = actualTypeArguments[i];
Type actualTypeArgument2 = actualTypeArguments2[i];
if (actualTypeArgument instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) actualTypeArgument;
// this is a TypeVariable declared by the class under test that turned
// up in one of its fields,
// e.g. class ClassUnderTest { List tList; Set tSet}
// The TypeVariable`s actual type is declared by the field containing
// the object under test, i.e. the field annotated with @InjectMocks
// e.g. @InjectMocks ClassUnderTest underTest = ..
Type genericType = injectMocksField.getGenericType();
if (genericType instanceof ParameterizedType) {
Type[] injectMocksFieldTypeParameters =
((ParameterizedType) genericType).getActualTypeArguments();
// Find index of given TypeVariable where it was defined, e.g. 0 for T1 in
// ClassUnderTest
// (we're always able to find it, otherwise test class wouldn't have compiled))
TypeVariable[] genericTypeParameters =
injectMocksField.getType().getTypeParameters();
int variableIndex = -1;
for (int i2 = 0; i2 < genericTypeParameters.length; i2++) {
if (genericTypeParameters[i2].equals(typeVariable)) {
variableIndex = i2;
break;
}
}
// now test whether actual type for the type variable is compatible, e.g. for
// class ClassUnderTest {..}
// T1 would be the String in
// ClassUnderTest underTest = ..
isCompatible &=
isCompatibleTypes(
injectMocksFieldTypeParameters[variableIndex],
actualTypeArgument2,
injectMocksField);
} else {
// must be a concrete class, recurse on super types that may have type
// parameters
isCompatible &=
getSuperTypes((Class) genericType)
.anyMatch(
superType ->
isCompatibleTypes(
superType,
actualTypeArgument2,
injectMocksField));
}
} else {
isCompatible &=
isCompatibleTypes(
actualTypeArgument, actualTypeArgument2, injectMocksField);
}
}
return isCompatible;
}
@Override
public OngoingInjector filterCandidate(
final Collection