/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.mockups;
import java.lang.reflect.*;
import java.lang.reflect.Type;
import java.util.*;
import javax.annotation.*;
import mockit.*;
import mockit.external.asm.*;
import mockit.internal.*;
import mockit.internal.startup.*;
import mockit.internal.state.*;
import static mockit.external.asm.ClassReader.*;
public final class MockClassSetup
{
@Nonnull final Class<?> realClass;
@Nullable private ClassReader rcReader;
@Nonnull private final MockMethods mockMethods;
@Nonnull final MockUp<?> mockUp;
private final boolean forStartupMock;
public MockClassSetup(
@Nonnull Class<?> realClass, @Nonnull Class<?> classToMock, @Nullable Type mockedType, @Nonnull MockUp<?> mockUp)
{
this(realClass, classToMock, mockedType, mockUp, null);
}
public MockClassSetup(
@Nonnull Class<?> realClass, @Nullable Type mockedType, @Nonnull MockUp<?> mockUp, @Nullable byte[] realClassCode)
{
this(realClass, realClass, mockedType, mockUp, realClassCode);
}
private MockClassSetup(
@Nonnull Class<?> realClass, @Nonnull Class<?> classToMock, @Nullable Type mockedType, @Nonnull MockUp<?> mockUp,
@Nullable byte[] realClassCode)
{
this.realClass = classToMock;
mockMethods = new MockMethods(realClass, mockedType);
this.mockUp = mockUp;
forStartupMock = Startup.initializing;
rcReader = realClassCode == null ? null : new ClassReader(realClassCode);
Class<?> mockUpClass = mockUp.getClass();
new MockMethodCollector(mockMethods).collectMockMethods(mockUpClass);
mockMethods.registerMockStates(mockUp, forStartupMock);
MockClasses mockClasses = TestRun.getMockClasses();
if (forStartupMock) {
mockClasses.addMock(mockMethods.getMockClassInternalName(), mockUp);
}
else {
mockClasses.addMock(mockUp);
}
}
public void redefineMethodsInGeneratedClass()
{
byte[] modifiedClassFile = modifyRealClass(realClass);
if (modifiedClassFile != null) {
applyClassModifications(realClass, modifiedClassFile);
}
}
@Nonnull
public Set<Class<?>> redefineMethods()
{
return redefineMethodsInClassHierarchy();
}
@Nonnull
private Set<Class<?>> redefineMethodsInClassHierarchy()
{
Set<Class<?>> redefinedClasses = new HashSet<Class<?>>();
@Nullable Class<?> classToModify = realClass;
while (classToModify != null && mockMethods.hasUnusedMocks()) {
byte[] modifiedClassFile = modifyRealClass(classToModify);
if (modifiedClassFile != null) {
applyClassModifications(classToModify, modifiedClassFile);
redefinedClasses.add(classToModify);
}
Class<?> superClass = classToModify.getSuperclass();
classToModify = superClass == Object.class || superClass == Proxy.class ? null : superClass;
rcReader = null;
}
return redefinedClasses;
}
@Nullable
private byte[] modifyRealClass(@Nonnull Class<?> classToModify)
{
if (rcReader == null) {
rcReader = createClassReaderForRealClass(classToModify);
}
MockupsModifier modifier = new MockupsModifier(rcReader, classToModify, mockUp, mockMethods);
rcReader.accept(modifier, SKIP_FRAMES);
return modifier.wasModified() ? modifier.toByteArray() : null;
}
@Nonnull
BaseClassModifier createClassModifier(@Nonnull ClassReader cr)
{
return new MockupsModifier(cr, realClass, mockUp, mockMethods);
}
@Nonnull
private static ClassReader createClassReaderForRealClass(@Nonnull Class<?> classToModify)
{
return ClassFile.createReaderFromLastRedefinitionIfAny(classToModify);
}
void applyClassModifications(@Nonnull Class<?> classToModify, @Nonnull byte[] modifiedClassFile)
{
Startup.redefineMethods(classToModify, modifiedClassFile);
if (forStartupMock) {
CachedClassfiles.addClassfile(classToModify, modifiedClassFile);
}
else {
String mockClassDesc = mockMethods.getMockClassInternalName();
TestRun.mockFixture().addRedefinedClass(mockClassDesc, classToModify, modifiedClassFile);
}
}
}