/*
* Copyright (c) 2006 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 javax.annotation.*;
import mockit.*;
import mockit.internal.mockups.*;
import mockit.internal.util.*;
public final class MockClasses
{
private static final Field INVOKED_INSTANCE_FIELD;
private static final Method ON_TEAR_DOWN_METHOD;
static
{
try {
INVOKED_INSTANCE_FIELD = MockUp.class.getDeclaredField("invokedInstance");
INVOKED_INSTANCE_FIELD.setAccessible(true);
ON_TEAR_DOWN_METHOD = MockUp.class.getDeclaredMethod("onTearDown");
ON_TEAR_DOWN_METHOD.setAccessible(true);
}
catch (NoSuchFieldException e) { throw new RuntimeException(e); }
catch (NoSuchMethodException e) { throw new RuntimeException(e); }
}
private static void notifyOfTearDown(@Nonnull MockUp<?> mockUp)
{
try { ON_TEAR_DOWN_METHOD.invoke(mockUp); }
catch (IllegalAccessException ignore) {}
catch (InvocationTargetException e) { e.getCause().printStackTrace(); }
}
public static final class MockUpInstances
{
@Nonnull public final MockUp<?> initialMockUp;
boolean hasMockupsForSingleInstances;
MockUpInstances(@Nonnull MockUp<?> initialMockUp)
{
this.initialMockUp = initialMockUp;
hasMockupsForSingleInstances = false;
}
public boolean hasMockUpsForSingleInstances() { return hasMockupsForSingleInstances; }
void notifyMockUpOfTearDown() { notifyOfTearDown(initialMockUp); }
}
@Nonnull private final Map<String, MockUp<?>> startupMocks;
@Nonnull private final Map<Class<?>, MockUpInstances> mockupClassesToMockupInstances;
@Nonnull private final Map<Object, MockUp<?>> mockedToMockupInstances;
@Nonnull public final MockStates mockStates;
MockClasses()
{
startupMocks = new IdentityHashMap<String, MockUp<?>>(8);
mockupClassesToMockupInstances = new IdentityHashMap<Class<?>, MockUpInstances>();
mockedToMockupInstances = new IdentityHashMap<Object, MockUp<?>>();
mockStates = new MockStates();
}
public void addMock(@Nonnull String mockClassDesc, @Nonnull MockUp<?> mockUp)
{
startupMocks.put(mockClassDesc, mockUp);
}
public void addMock(@Nonnull MockUp<?> mockUp)
{
Class<?> mockUpClass = mockUp.getClass();
MockUpInstances newData = new MockUpInstances(mockUp);
mockupClassesToMockupInstances.put(mockUpClass, newData);
}
public void addMock(@Nonnull MockUp<?> mockUp, @Nonnull Object mockedInstance)
{
MockUp<?> previousMockup = mockedToMockupInstances.put(mockedInstance, mockUp);
assert previousMockup == null;
MockUpInstances mockUpInstances = mockupClassesToMockupInstances.get(mockUp.getClass());
mockUpInstances.hasMockupsForSingleInstances = true;
}
@Nullable
MockUp<?> getMock(@Nonnull String mockUpClassDesc, @Nullable Object mockedInstance)
{
if (mockedInstance != null) {
MockUp<?> mockUpForSingleInstance = mockedToMockupInstances.get(mockedInstance);
if (mockUpForSingleInstance != null) {
return mockUpForSingleInstance;
}
}
MockUp<?> startupMock = startupMocks.get(mockUpClassDesc);
if (startupMock != null) {
return startupMock;
}
Class<?> mockUpClass = ClassLoad.loadByInternalName(mockUpClassDesc);
MockUpInstances mockUpInstances = mockupClassesToMockupInstances.get(mockUpClass);
Object invokedInstance = mockedInstance;
if (mockedInstance == null) {
invokedInstance = Void.class;
}
else if (mockUpInstances.hasMockUpsForSingleInstances()) {
return null;
}
try { INVOKED_INSTANCE_FIELD.set(mockUpInstances.initialMockUp, invokedInstance); }
catch (IllegalAccessException ignore) {}
return mockUpInstances.initialMockUp;
}
@Nullable
public MockUpInstances findPreviouslyAppliedMockUps(@Nonnull MockUp<?> newMockUp)
{
Class<?> mockUpClass = newMockUp.getClass();
MockUpInstances mockUpInstances = mockupClassesToMockupInstances.get(mockUpClass);
if (mockUpInstances != null && mockUpInstances.hasMockupsForSingleInstances) {
mockStates.copyMockStates(mockUpInstances.initialMockUp, newMockUp);
}
return mockUpInstances;
}
private void discardMockupInstances(@Nonnull Map<Object, MockUp<?>> previousMockInstances)
{
if (!previousMockInstances.isEmpty()) {
mockedToMockupInstances.entrySet().retainAll(previousMockInstances.entrySet());
}
else if (!mockedToMockupInstances.isEmpty()) {
mockedToMockupInstances.clear();
}
}
private void discardMockupInstancesExceptPreviousOnes(@Nonnull Map<Class<?>, Boolean> previousMockupClasses)
{
updatePreviousMockups(previousMockupClasses);
for (Entry<Class<?>, MockUpInstances> mockupClassAndInstances : mockupClassesToMockupInstances.entrySet()) {
Class<?> mockupClass = mockupClassAndInstances.getKey();
if (!previousMockupClasses.containsKey(mockupClass)) {
MockUpInstances mockUpInstances = mockupClassAndInstances.getValue();
mockUpInstances.notifyMockUpOfTearDown();
}
}
mockupClassesToMockupInstances.keySet().retainAll(previousMockupClasses.keySet());
}
private void updatePreviousMockups(@Nonnull Map<Class<?>, Boolean> previousMockupClasses)
{
for (Entry<Class<?>, Boolean> mockupClassAndData : previousMockupClasses.entrySet()) {
Class<?> mockupClass = mockupClassAndData.getKey();
MockUpInstances mockUpData = mockupClassesToMockupInstances.get(mockupClass);
mockUpData.hasMockupsForSingleInstances = mockupClassAndData.getValue();
}
}
private void discardAllMockupInstances()
{
if (!mockupClassesToMockupInstances.isEmpty()) {
for (MockUpInstances mockUpInstances : mockupClassesToMockupInstances.values()) {
mockUpInstances.notifyMockUpOfTearDown();
}
mockupClassesToMockupInstances.clear();
}
}
public void discardStartupMocks()
{
for (MockUp<?> startupMockup : startupMocks.values()) {
notifyOfTearDown(startupMockup);
}
}
final class SavePoint
{
@Nonnull private final Map<Object, MockUp<?>> previousMockInstances;
@Nonnull private final Map<Class<?>, Boolean> previousMockupClasses;
SavePoint()
{
previousMockInstances = new IdentityHashMap<Object, MockUp<?>>(mockedToMockupInstances);
previousMockupClasses = new IdentityHashMap<Class<?>, Boolean>();
for (Entry<Class<?>, MockUpInstances> mockUpClassAndData : mockupClassesToMockupInstances.entrySet()) {
Class<?> mockUpClass = mockUpClassAndData.getKey();
MockUpInstances mockUpData = mockUpClassAndData.getValue();
previousMockupClasses.put(mockUpClass, mockUpData.hasMockupsForSingleInstances);
}
}
void rollback()
{
discardMockupInstances(previousMockInstances);
if (!previousMockupClasses.isEmpty()) {
discardMockupInstancesExceptPreviousOnes(previousMockupClasses);
}
else {
discardAllMockupInstances();
}
}
}
}