/*
* Copyright 2011 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.powermock.core;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.powermock.core.spi.MethodInvocationControl;
import org.powermock.core.spi.NewInvocationControl;
import org.powermock.reflect.exceptions.MethodNotFoundException;
import org.powermock.reflect.internal.TypeUtils;
import org.powermock.reflect.internal.WhiteboxImpl;
/**
* All mock invocations are routed through this gateway. This includes method
* calls, construction of new instances and more. Do not use this class
* directly, but always go through the PowerMock facade.
*/
public class MockGateway {
public static final Object PROCEED = new Object();
public static final Object SUPPRESS = new Object();
/**
* Used to tell the MockGateway that the next call should not be mocked
* regardless if a {@link MethodInvocationControl} is found in the
* {@link MockRepository}. Used to allow for e.g. recursive partial mocking.
*/
public static final String DONT_MOCK_NEXT_CALL = "DontMockNextCall";
/**
* Tells PowerMock to mock standard methods. These are
* {@link Object#toString()}, {@link Object#hashCode()} and
* {@link Object#equals(Object)}. By default this is <code>true</code>.
*/
public static boolean MOCK_STANDARD_METHODS = true;
/**
* Tells PowerMock whether or not to mock
* {@link java.lang.Object#getClass()}.
*/
public static boolean MOCK_GET_CLASS_METHOD = false;
// used for static methods
public static Object methodCall(Class<?> type, String methodName, Object[] args, Class<?>[] sig,
String returnTypeAsString) throws Throwable {
return doMethodCall(type, methodName, args, sig, returnTypeAsString);
}
private static Object doMethodCall(Object object, String methodName, Object[] args, Class<?>[] sig,
String returnTypeAsString) throws Throwable, NoSuchMethodException {
if (!shouldMockMethod(methodName, sig)) {
return PROCEED;
}
Object returnValue = null;
MethodInvocationControl methodInvocationControl = null;
Class<?> objectType = null;
if (object instanceof Class<?>) {
objectType = (Class<?>) object;
methodInvocationControl = MockRepository.getStaticMethodInvocationControl(objectType);
} else {
final Class<? extends Object> type = object.getClass();
objectType = WhiteboxImpl.getUnmockedType(type);
methodInvocationControl = MockRepository.getInstanceMethodInvocationControl(object);
}
/*
* if invocationControl is null or the method is not mocked, invoke
* original method or suppress the method code otherwise invoke the
* invocation handler.
*/
Method method = null;
try {
method = WhiteboxImpl.getBestMethodCandidate(objectType, methodName, sig, true);
} catch (MethodNotFoundException e) {
/*
* Dirty hack to get around issue 110
* (http://code.google.com/p/powermock/issues/detail?id=110). Review
* this! What we do here is to try to find a reflective method on
* class. This has begun to fail since version 1.2 when we supported
* mocking static methods in system classes.
*/
try {
method = WhiteboxImpl.getMethod(Class.class, methodName, sig);
} catch (MethodNotFoundException e2) {
throw e;
}
}
if (methodInvocationControl != null && methodInvocationControl.isMocked(method) && shouldMockThisCall()) {
returnValue = methodInvocationControl.invoke(object, method, args);
if (returnValue == SUPPRESS) {
returnValue = TypeUtils.getDefaultValue(returnTypeAsString);
}
} else if (MockRepository.hasMethodProxy(method)) {
/*
* We must temporary remove the method proxy when invoking the
* invocation handler because if the invocation handler delegates
* the call we will end up here again and we'll get a
* StackOverflowError.
*/
final InvocationHandler invocationHandler = MockRepository.removeMethodProxy(method);
try {
returnValue = invocationHandler.invoke(object, method, args);
} finally {
// Set the method proxy again after the invocation
MockRepository.putMethodProxy(method, invocationHandler);
}
} else if (MockRepository.shouldSuppressMethod(method, objectType)) {
returnValue = TypeUtils.getDefaultValue(returnTypeAsString);
} else if (MockRepository.shouldStubMethod(method)) {
returnValue = MockRepository.getMethodToStub(method);
} else {
returnValue = PROCEED;
}
return returnValue;
}
private static boolean shouldMockMethod(String methodName, Class<?>[] sig) {
if (isJavaStandardMethod(methodName, sig) && !MOCK_STANDARD_METHODS) {
return false;
} else if (isGetClassMethod(methodName, sig) && !MOCK_GET_CLASS_METHOD) {
return false;
} else {
return true;
}
}
private static boolean isJavaStandardMethod(String methodName, Class<?>[] sig) {
return (methodName.equals("equals") && sig.length == 1) || (methodName.equals("hashCode") && sig.length == 0)
|| (methodName.equals("toString") && sig.length == 0);
}
private static boolean isGetClassMethod(String methodName, Class<?>[] sig) {
return methodName.equals("getClass") && sig.length == 0;
}
private static boolean shouldMockThisCall() {
Object shouldSkipMockingOfNextCall = MockRepository.getAdditionalState(DONT_MOCK_NEXT_CALL);
final boolean shouldMockThisCall;
if (shouldSkipMockingOfNextCall == null) {
shouldMockThisCall = true;
} else {
shouldMockThisCall = false;
}
MockRepository.removeAdditionalState(DONT_MOCK_NEXT_CALL);
return shouldMockThisCall;
}
// used for instance methods
public static Object methodCall(Object instance, String methodName, Object[] args, Class<?>[] sig,
String returnTypeAsString) throws Throwable {
return doMethodCall(instance, methodName, args, sig, returnTypeAsString);
}
public static Object newInstanceCall(Class<?> type, Object[] args, Class<?>[] sig) throws Throwable {
final NewInvocationControl<?> newInvocationControl = MockRepository.getNewInstanceControl(type);
if (newInvocationControl != null) {
/*
* We need to deal with inner, local and anonymous inner classes
* specifically. For example when new is invoked on an inner class
* it seems like null is passed as an argument even though it
* shouldn't. We correct this here.
*
* Seems with Javassist 3.17.1-GA & Java 7, the 'null' is passed as the last argument.
*/
if (type.isMemberClass() && Modifier.isStatic(type.getModifiers())) {
if (args.length > 0 && (args[0] == null || args[args.length -1] == null) && sig.length > 0) {
args = copyArgumentsForInnerOrLocalOrAnonymousClass(args, false);
}
} else if (type.isLocalClass() || type.isAnonymousClass() || type.isMemberClass()) {
if (args.length > 0 && sig.length > 0 && sig[0].equals(type.getEnclosingClass())) {
args = copyArgumentsForInnerOrLocalOrAnonymousClass(args, true);
}
}
return newInvocationControl.invoke(type, args, sig);
}
// Check if we should suppress the constructor code
if (MockRepository.shouldSuppressConstructor(WhiteboxImpl.getConstructor(type, sig))) {
return WhiteboxImpl.getFirstParentConstructor(type);
}
return PROCEED;
}
public static Object fieldCall(Object instanceOrClassContainingTheField, Class<?> classDefiningField,
String fieldName, Class<?> fieldType) {
if (MockRepository.shouldSuppressField(WhiteboxImpl.getField(classDefiningField, fieldName))) {
return TypeUtils.getDefaultValue(fieldType);
}
return PROCEED;
}
public static Object staticConstructorCall(String className) {
if (MockRepository.shouldSuppressStaticInitializerFor(className)) {
return "suppress";
}
return PROCEED;
}
public static Object constructorCall(Class<?> type, Object[] args, Class<?>[] sig) throws Throwable {
final Constructor<?> constructor = WhiteboxImpl.getConstructor(type, sig);
if (MockRepository.shouldSuppressConstructor(constructor)) {
return null;
}
return PROCEED;
}
/**
* The first parameter of an inner, local or anonymous inner class is
* <code>null</code> or the enclosing instance. This should not be included
* in the substitute invocation since it is never expected by the user.
* <p>
* Seems with Javassist 3.17.1-GA & Java 7, the '<code>null</code>' is passed as the last argument.
*/
private static Object[] copyArgumentsForInnerOrLocalOrAnonymousClass(Object[] args, boolean excludeEnclosingInstance) {
Object[] newArgs = new Object[args.length - 1];
int start = 0;
int end = 0;
int j = 0;
if (args[0] == null || excludeEnclosingInstance) {
start = 1;
end = args.length;
} else {
start = 0;
end = args.length - 1;
}
for (int i = start; i < end; i++) {
newArgs[j++] = args[i];
}
args = newArgs;
return args;
}
}