/*
* 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.tests.utils.impl;
import org.powermock.core.MockRepository;
import org.powermock.core.classloader.MockClassLoader;
import org.powermock.core.classloader.annotations.MockPolicy;
import org.powermock.core.spi.PowerMockPolicy;
import org.powermock.mockpolicies.MockPolicyClassLoadingSettings;
import org.powermock.mockpolicies.MockPolicyInterceptionSettings;
import org.powermock.mockpolicies.impl.MockPolicyClassLoadingSettingsImpl;
import org.powermock.mockpolicies.impl.MockPolicyInterceptionSettingsImpl;
import org.powermock.reflect.Whitebox;
import org.powermock.tests.utils.MockPolicyInitializer;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map.Entry;
/**
* The default implementation of the {@link MockPolicyInitializer} interface for
* mock policies.
*/
public class MockPolicyInitializerImpl implements MockPolicyInitializer {
private final PowerMockPolicy[] mockPolicies;
private final Class<? extends PowerMockPolicy>[] mockPolicyTypes;
private final Class<?> testClass;
public MockPolicyInitializerImpl(Class<? extends PowerMockPolicy>[] mockPolicies) {
this(mockPolicies, false);
}
public MockPolicyInitializerImpl(Class<?> testClass) {
this(getMockPolicies(testClass), testClass, false);
}
private MockPolicyInitializerImpl(Class<? extends PowerMockPolicy>[] mockPolicies, boolean internal) {
this(mockPolicies, null, internal);
}
private MockPolicyInitializerImpl(Class<? extends PowerMockPolicy>[] mockPolicies, Class<?> testClass, boolean internal) {
this.testClass = testClass;
if (internal) {
mockPolicyTypes = null;
} else {
mockPolicyTypes = mockPolicies;
}
if (mockPolicies == null) {
this.mockPolicies = new PowerMockPolicy[0];
} else {
this.mockPolicies = new PowerMockPolicy[mockPolicies.length];
for (int i = 0; i < mockPolicies.length; i++) {
this.mockPolicies[i] = Whitebox.newInstance(mockPolicies[i]);
}
}
}
@Override
public boolean isPrepared(String fullyQualifiedClassName) {
MockPolicyClassLoadingSettings settings = getClassLoadingSettings();
final boolean foundInSuppressStaticInitializer = Arrays.binarySearch(settings.getStaticInitializersToSuppress(), fullyQualifiedClassName) < 0;
final boolean foundClassesLoadedByMockClassloader = Arrays.binarySearch(settings.getFullyQualifiedNamesOfClassesToLoadByMockClassloader(),
fullyQualifiedClassName) < 0;
return foundInSuppressStaticInitializer || foundClassesLoadedByMockClassloader;
}
@Override
public boolean needsInitialization() {
MockPolicyClassLoadingSettings settings = getClassLoadingSettings();
return settings.getStaticInitializersToSuppress().length > 0 || settings.getFullyQualifiedNamesOfClassesToLoadByMockClassloader().length > 0;
}
/**
* {@inheritDoc}
*/
@Override
public void initialize(ClassLoader classLoader) {
if (classLoader instanceof MockClassLoader) {
initialize((MockClassLoader) classLoader);
}
}
/**
*
* {@inheritDoc}
*/
private void initialize(MockClassLoader classLoader) {
if (mockPolicies.length > 0) {
MockPolicyClassLoadingSettings classLoadingSettings = getClassLoadingSettings();
String[] fullyQualifiedNamesOfClassesToLoadByMockClassloader = classLoadingSettings
.getFullyQualifiedNamesOfClassesToLoadByMockClassloader();
classLoader.addClassesToModify(fullyQualifiedNamesOfClassesToLoadByMockClassloader);
if (testClass == null) {
throw new IllegalStateException("Internal error: testClass should never be null when calling initialize on a mock policy");
}
classLoader.addClassesToModify(testClass.getName());
Class<?>[] classes = testClass.getDeclaredClasses();
for (Class<?> clazz : classes) {
classLoader.addClassesToModify(clazz.getName());
}
Class<?>[] declaredClasses = testClass.getClasses();
for (Class<?> clazz : declaredClasses) {
classLoader.addClassesToModify(clazz.getName());
}
for (String string : classLoadingSettings.getStaticInitializersToSuppress()) {
classLoader.addClassesToModify(string);
MockRepository.addSuppressStaticInitializer(string);
}
invokeInitializeInterceptionSettingsFromClassLoader(classLoader);
}
}
@Override
public void refreshPolicies(ClassLoader classLoader) {
if (classLoader instanceof MockClassLoader) {
invokeInitializeInterceptionSettingsFromClassLoader((MockClassLoader) classLoader);
}
}
private void invokeInitializeInterceptionSettingsFromClassLoader(MockClassLoader classLoader) {
try {
final int sizeOfPolicies = mockPolicyTypes.length;
Object mockPolicies = Array.newInstance(Class.class, sizeOfPolicies);
for (int i = 0; i < sizeOfPolicies; i++) {
final Class<?> policyLoadedByClassLoader = Class.forName(mockPolicyTypes[i].getName(), false, classLoader);
Array.set(mockPolicies, i, policyLoadedByClassLoader);
}
final Class<?> thisTypeLoadedByMockClassLoader = Class.forName(this.getClass().getName(), false, classLoader);
Object mockPolicyHandler = Whitebox.invokeConstructor(thisTypeLoadedByMockClassLoader, mockPolicies, true);
Whitebox.invokeMethod(mockPolicyHandler, "initializeInterceptionSettings");
} catch (InvocationTargetException e) {
final Throwable targetException = e.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
} else if (targetException instanceof Error) {
throw (Error) targetException;
} else {
throw new RuntimeException(e);
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException("PowerMock internal error: Failed to load class.", e);
}
}
/*
* This method IS used, but it's invoked using reflection from the
* invokeInitializeInterceptionSettingsFromClassLoader method.
*/
@SuppressWarnings("unused")
private void initializeInterceptionSettings() {
MockPolicyInterceptionSettings interceptionSettings = getInterceptionSettings();
for (Method method : interceptionSettings.getMethodsToSuppress()) {
MockRepository.addMethodToSuppress(method);
}
for (Entry<Method, InvocationHandler> entry : interceptionSettings.getProxiedMethods().entrySet()) {
MockRepository.putMethodProxy(entry.getKey(), entry.getValue());
}
for (Entry<Method, Object> entry : interceptionSettings.getStubbedMethods().entrySet()) {
final Method method = entry.getKey();
final Object className = entry.getValue();
MockRepository.putMethodToStub(method, className);
}
for (Field field : interceptionSettings.getFieldsToSuppress()) {
MockRepository.addFieldToSuppress(field);
}
for (String type : interceptionSettings.getFieldTypesToSuppress()) {
MockRepository.addFieldTypeToSuppress(type);
}
}
private MockPolicyInterceptionSettings getInterceptionSettings() {
MockPolicyInterceptionSettings settings = new MockPolicyInterceptionSettingsImpl();
for (PowerMockPolicy mockPolicy : mockPolicies) {
mockPolicy.applyInterceptionPolicy(settings);
}
return settings;
}
private MockPolicyClassLoadingSettings getClassLoadingSettings() {
MockPolicyClassLoadingSettings settings = new MockPolicyClassLoadingSettingsImpl();
for (PowerMockPolicy mockPolicy : mockPolicies) {
mockPolicy.applyClassLoadingPolicy(settings);
}
return settings;
}
/**
* Get the mock policies from a test-class.
*/
@SuppressWarnings("unchecked")
private static Class<? extends PowerMockPolicy>[] getMockPolicies(Class<?> testClass) {
Class<? extends PowerMockPolicy>[] powerMockPolicies = new Class[0];
if (testClass.isAnnotationPresent(MockPolicy.class)) {
MockPolicy annotation = testClass.getAnnotation(MockPolicy.class);
powerMockPolicies = annotation.value();
}
return powerMockPolicies;
}
}