/*
* Copyright (c) 2006-2013 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.state;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.*;
import mockit.internal.*;
import mockit.internal.expectations.mocking.*;
import mockit.internal.util.*;
/**
* Holds data about redefined real classes and their corresponding mock classes (if any), and provides methods to
* add/remove such state both from this instance and from other state holders with associated data.
*/
public final class MockFixture
{
/**
* Similar to {@code redefinedClasses}, but for classes modified by a {@code ClassFileTransformer} such as the
* {@code CaptureTransformer}, and containing the pre-transform bytecode instead of the modified one.
*/
private final Map<String, byte[]> transformedClasses = new HashMap<String, byte[]>(2);
/**
* Real classes currently redefined in the running JVM and their current (modified) bytecodes.
* <p/>
* The keys in the map allow each redefined real class to be later restored to a previous definition.
* <p/>
* The modified bytecode arrays in the map allow a new redefinition to be made on top of the current redefinition
* (in the case of the Mockups API), or to restore the class to a previous definition (provided the map is copied
* between redefinitions of the same class).
*/
private final Map<Class<?>, byte[]> redefinedClasses = new IdentityHashMap<Class<?>, byte[]>(8);
/**
* Subset of all currently redefined classes which contain one or more native methods.
* <p/>
* This is needed because in order to restore such methods it is necessary (for some classes) to re-register them
* with the JVM.
*
* @see #reregisterNativeMethodsForRestoredClass(Class)
*/
private final Set<String> redefinedClassesWithNativeMethods = new HashSet<String>();
/**
* Maps redefined real classes to the internal name of the corresponding mock classes, when it's the case.
* <p/>
* This allows any global state associated to a mock class to be discarded when the corresponding real class is
* later restored to its original definition.
*/
private final Map<Class<?>, String> realClassesToMockClasses = new IdentityHashMap<Class<?>, String>(8);
private final List<Class<?>> mockedClasses = new ArrayList<Class<?>>();
private final Map<Class<?>, InstanceFactory> mockedTypesAndInstances =
new IdentityHashMap<Class<?>, InstanceFactory>();
// Methods to add/remove transformed/redefined classes /////////////////////////////////////////////////////////////
public void addTransformedClass(String className, byte[] pretransformClassfile)
{
transformedClasses.put(className, pretransformClassfile);
}
public void addRedefinedClass(String mockClassInternalName, Class<?> redefinedClass, byte[] modifiedClassfile)
{
if (mockClassInternalName != null) {
String previousNames = realClassesToMockClasses.put(redefinedClass, mockClassInternalName);
if (previousNames != null) {
realClassesToMockClasses.put(redefinedClass, previousNames + ' ' + mockClassInternalName);
}
}
addRedefinedClass(redefinedClass, modifiedClassfile);
}
public void addRedefinedClass(Class<?> redefinedClass, byte[] modifiedClassfile)
{
redefinedClasses.put(redefinedClass, modifiedClassfile);
}
public void registerMockedClass(Class<?> mockedType)
{
if (!mockedClasses.contains(mockedType) && !GeneratedClasses.isGeneratedImplementationClass(mockedType)) {
mockedClasses.add(mockedType);
}
}
public boolean isInstanceOfMockedClass(Object mockedInstance)
{
Class<?> mockedClass = mockedInstance.getClass();
int n = mockedClasses.size();
for (int i = 0; i < n; i++) {
Class<?> mockedType = mockedClasses.get(i);
if (mockedType == mockedClass || mockedType.isAssignableFrom(mockedClass)) {
return true;
}
}
return false;
}
public void registerInstanceFactoryForMockedType(Class<?> mockedType, InstanceFactory mockedInstanceFactory)
{
registerMockedClass(mockedType);
mockedTypesAndInstances.put(mockedType, mockedInstanceFactory);
}
public InstanceFactory findInstanceFactory(Class<?> mockedType)
{
if (mockedType.isInterface() || Modifier.isAbstract(mockedType.getModifiers())) {
for (Entry<Class<?>, InstanceFactory> entry : mockedTypesAndInstances.entrySet()) {
Class<?> baseType = GeneratedClasses.getMockedClassOrInterfaceType(entry.getKey());
if (baseType == mockedType) {
return entry.getValue();
}
}
return null;
}
return mockedTypesAndInstances.get(mockedType);
}
public void restoreAndRemoveRedefinedClasses(Set<Class<?>> desiredClasses)
{
Set<Class<?>> classesToRestore = desiredClasses == null ? redefinedClasses.keySet() : desiredClasses;
RedefinitionEngine redefinitionEngine = new RedefinitionEngine();
for (Class<?> redefinedClass : classesToRestore) {
redefinitionEngine.restoreOriginalDefinition(redefinedClass);
restoreDefinition(redefinedClass);
discardStateForCorrespondingMockClassIfAny(redefinedClass);
}
if (desiredClasses == null) {
redefinedClasses.clear();
}
else {
redefinedClasses.keySet().removeAll(desiredClasses);
}
}
private void restoreDefinition(Class<?> redefinedClass)
{
if (redefinedClassesWithNativeMethods.contains(redefinedClass.getName())) {
reregisterNativeMethodsForRestoredClass(redefinedClass);
}
mockedTypesAndInstances.remove(redefinedClass);
mockedClasses.remove(redefinedClass);
}
private void discardStateForCorrespondingMockClassIfAny(Class<?> redefinedClass)
{
String mockClassesInternalNames = realClassesToMockClasses.remove(redefinedClass);
TestRun.getMockClasses().getMockStates().removeClassState(redefinedClass, mockClassesInternalNames);
}
void restoreTransformedClasses(Set<String> previousTransformedClasses)
{
if (!transformedClasses.isEmpty()) {
Set<String> classesToRestore;
if (previousTransformedClasses.isEmpty()) {
classesToRestore = transformedClasses.keySet();
}
else {
classesToRestore = getTransformedClasses();
classesToRestore.removeAll(previousTransformedClasses);
}
if (!classesToRestore.isEmpty()) {
restoreAndRemoveTransformedClasses(classesToRestore);
}
}
}
private void restoreAndRemoveTransformedClasses(Set<String> classesToRestore)
{
RedefinitionEngine redefinitionEngine = new RedefinitionEngine();
for (String transformedClassName : classesToRestore) {
byte[] definitionToRestore = transformedClasses.get(transformedClassName);
redefinitionEngine.restoreToDefinition(transformedClassName, definitionToRestore);
}
transformedClasses.keySet().removeAll(classesToRestore);
}
void restoreRedefinedClasses(Map<?, byte[]> previousDefinitions)
{
RedefinitionEngine redefinitionEngine = new RedefinitionEngine();
Iterator<Entry<Class<?>, byte[]>> itr = redefinedClasses.entrySet().iterator();
while (itr.hasNext()) {
Entry<Class<?>, byte[]> entry = itr.next();
Class<?> redefinedClass = entry.getKey();
byte[] currentDefinition = entry.getValue();
byte[] previousDefinition = previousDefinitions.get(redefinedClass);
if (currentDefinition != previousDefinition) {
redefinitionEngine.restoreDefinition(redefinedClass, previousDefinition);
if (previousDefinition == null) {
restoreDefinition(redefinedClass);
discardStateForCorrespondingMockClassIfAny(redefinedClass);
itr.remove();
}
else {
entry.setValue(previousDefinition);
}
}
}
}
// Methods that deal with redefined native methods /////////////////////////////////////////////////////////////////
public void addRedefinedClassWithNativeMethods(String redefinedClassInternalName)
{
redefinedClassesWithNativeMethods.add(redefinedClassInternalName.replace('/', '.'));
}
private void reregisterNativeMethodsForRestoredClass(Class<?> realClass)
{
Method registerNatives = null;
try {
registerNatives = realClass.getDeclaredMethod("registerNatives");
}
catch (NoSuchMethodException ignore) {
try {
registerNatives = realClass.getDeclaredMethod("initIDs");
}
catch (NoSuchMethodException alsoIgnore) {
// OK
}
}
if (registerNatives != null) {
try {
registerNatives.setAccessible(true);
registerNatives.invoke(null);
}
catch (IllegalAccessException ignore) {
// Won't happen.
}
catch (InvocationTargetException ignore) {
// Shouldn't happen either.
}
}
// OK, although another solution will be required for this particular class if it requires
// natives to be explicitly registered again (not all do, such as java.lang.Float).
}
// Getter methods for the maps of transformed/redefined classes ////////////////////////////////////////////////////
public Set<String> getTransformedClasses() { return new HashSet<String>(transformedClasses.keySet()); }
public Map<Class<?>, byte[]> getRedefinedClasses() { return new HashMap<Class<?>, byte[]>(redefinedClasses); }
public byte[] getRedefinedClassfile(Class<?> redefinedClass) { return redefinedClasses.get(redefinedClass); }
public boolean containsRedefinedClass(Class<?> redefinedClass)
{
return redefinedClasses.containsKey(redefinedClass);
}
}