/*
* Copyright 2016 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.api.mockito.internal.mockcreation;
import org.mockito.MockSettings;
import org.mockito.Mockito;
import org.mockito.internal.InternalMockHandler;
import org.mockito.internal.creation.MockSettingsImpl;
import org.mockito.internal.creation.instance.DefaultInstantiatorProvider;
import org.mockito.internal.handler.MockHandlerFactory;
import org.mockito.internal.util.MockNameImpl;
import org.mockito.internal.util.reflection.LenientCopyTool;
import org.powermock.api.mockito.internal.invocation.MockitoMethodInvocationControl;
import org.powermock.api.mockito.repackaged.ClassImposterizer;
import org.powermock.api.mockito.repackaged.MethodInterceptorFilter;
import org.powermock.core.ClassReplicaCreator;
import org.powermock.core.DefaultFieldValueGenerator;
import org.powermock.core.MockRepository;
import org.powermock.core.classloader.MockClassLoader;
import org.powermock.reflect.Whitebox;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class DefaultMockCreator extends AbstractMockCreator {
private static final DefaultMockCreator MOCK_CREATOR = new DefaultMockCreator();
@SuppressWarnings("unchecked")
public static <T> T mock(Class<T> type, boolean isStatic, boolean isSpy, Object delegator,
MockSettings mockSettings, Method... methods) {
return MOCK_CREATOR.createMock(type, isStatic, isSpy, delegator, mockSettings, methods);
}
@SuppressWarnings("unchecked")
public <T> T createMock(Class<T> type, boolean isStatic, boolean isSpy, Object delegator,
MockSettings mockSettings, Method... methods) {
if (type == null) {
throw new IllegalArgumentException("The class to mock cannot be null");
}
validateType(type, isStatic, isSpy);
final String mockName = toInstanceName(type, mockSettings);
MockRepository.addAfterMethodRunner(new MockitoStateCleanerRunnable());
final Class<T> typeToMock;
if (isFinalJavaSystemClass(type)) {
typeToMock = (Class<T>) new ClassReplicaCreator().createClassReplica(type);
} else {
typeToMock = type;
}
final MockData<T> mockData = createMethodInvocationControl(mockName, typeToMock, methods, isSpy, delegator,
mockSettings);
T mock = mockData.getMock();
if (isFinalJavaSystemClass(type) && !isStatic) {
mock = Whitebox.newInstance(type);
DefaultFieldValueGenerator.fillWithDefaultValues(mock);
}
if (isStatic) {
MockRepository.putStaticMethodInvocationControl(type, mockData.getMethodInvocationControl());
} else {
MockRepository.putInstanceMethodInvocationControl(mock, mockData.getMethodInvocationControl());
}
if (isSpy) {
new LenientCopyTool().copyToMock(delegator, mock);
}
return mock;
}
private static <T> boolean isFinalJavaSystemClass(Class<T> type) {
return type.getName().startsWith("java.") && Modifier.isFinal(type.getModifiers());
}
@SuppressWarnings("unchecked")
private static <T> MockData<T> createMethodInvocationControl(final String mockName, Class<T> type,
Method[] methods, boolean isSpy, Object delegator, MockSettings mockSettings) {
final MockSettingsImpl settings;
if (mockSettings == null) {
// We change the context classloader to the current CL in order for the Mockito
// framework to load it's plugins (such as MockMaker) correctly.
final ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(DefaultMockCreator.class.getClassLoader());
try {
settings = (MockSettingsImpl) Mockito.withSettings();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
} else {
settings = (MockSettingsImpl) mockSettings;
}
if (isSpy) {
settings.defaultAnswer(Mockito.CALLS_REAL_METHODS);
}
settings.setMockName(new MockNameImpl(mockName));
settings.setTypeToMock(type);
InternalMockHandler mockHandler = new MockHandlerFactory().create(settings);
MethodInterceptorFilter filter = new PowerMockMethodInterceptorFilter(mockHandler, settings);
final T mock = new ClassImposterizer(new DefaultInstantiatorProvider().getInstantiator(settings)).imposterise(filter, type);
ClassLoader classLoader = mock.getClass().getClassLoader();
if (classLoader instanceof MockClassLoader) {
MockClassLoader mcl = (MockClassLoader) classLoader;
mcl.cache(mock.getClass());
}
final MockitoMethodInvocationControl invocationControl = new MockitoMethodInvocationControl(
filter,
isSpy && delegator == null ? new Object() : delegator,
mock,
methods);
return new MockData<T>(invocationControl, mock);
}
private String toInstanceName(Class<?> clazz, final MockSettings mockSettings) {
// if the settings define a mock name, use it
if (mockSettings instanceof MockSettingsImpl<?>) {
String settingName = ((MockSettingsImpl<?>) mockSettings).getName();
if (settingName != null) {
return settingName;
}
}
// else, use the class name as mock name
String className = clazz.getSimpleName();
if (className.length() == 0) {
return clazz.getName();
}
// lower case first letter
return className.substring(0, 1).toLowerCase() + className.substring(1);
}
/**
* Class that encapsulate a mock and its corresponding invocation control.
*/
private static class MockData<T> {
private final MockitoMethodInvocationControl methodInvocationControl;
private final T mock;
MockData(MockitoMethodInvocationControl methodInvocationControl, T mock) {
this.methodInvocationControl = methodInvocationControl;
this.mock = mock;
}
public MockitoMethodInvocationControl getMethodInvocationControl() {
return methodInvocationControl;
}
public T getMock() {
return mock;
}
}
/**
* Clear state in Mockito that retains memory between tests
*/
private static class MockitoStateCleanerRunnable implements Runnable {
public void run() {
MockitoStateCleaner cleaner = new MockitoStateCleaner();
cleaner.clearConfiguration();
cleaner.clearMockProgress();
}
}
}