/*
* Copyright (c) 2006-2011 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.capturing;
import java.lang.instrument.*;
import java.security.*;
import java.util.*;
import mockit.external.asm.*;
import mockit.external.asm.commons.*;
import mockit.internal.*;
import mockit.internal.state.*;
import mockit.internal.util.*;
final class CaptureTransformer implements ClassFileTransformer
{
private final CapturedType metadata;
private final String capturedType;
private final CaptureOfImplementations modifierFactory;
private final SuperTypeCollector superTypeCollector;
private final Map<String, byte[]> transformedClasses;
private boolean inactive;
CaptureTransformer(CapturedType metadata, CaptureOfImplementations modifierFactory, boolean forTestClass)
{
this.metadata = metadata;
capturedType = metadata.baseType.getName().replace('.', '/');
this.modifierFactory = modifierFactory;
superTypeCollector = new SuperTypeCollector();
transformedClasses = forTestClass ? new HashMap<String, byte[]>(2) : null;
}
void deactivate()
{
inactive = true;
if (transformedClasses != null) {
RedefinitionEngine redefinitionEngine = new RedefinitionEngine();
for (Map.Entry<String, byte[]> classNameAndOriginalBytecode : transformedClasses.entrySet()) {
String className = classNameAndOriginalBytecode.getKey();
byte[] originalBytecode = classNameAndOriginalBytecode.getValue();
redefinitionEngine.restoreToDefinition(className, originalBytecode);
}
transformedClasses.clear();
}
}
public byte[] transform(
ClassLoader loader, String internalClassName, Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer)
{
if (
inactive || classBeingRedefined != null || TestRun.getCurrentTestInstance() == null ||
internalClassName.startsWith("mockit/internal/"))
{
return null;
}
String className = internalClassName.replace('/', '.');
if (!metadata.isToBeCaptured(className)) {
return null;
}
ClassReader cr = new ClassReader(classfileBuffer);
byte[] modifiedBytecode = null;
try {
cr.accept(superTypeCollector, true);
}
catch (VisitInterruptedException ignore) {
if (superTypeCollector.classExtendsCapturedType) {
modifiedBytecode = modifyAndRegisterClass(loader, className, cr);
}
}
return modifiedBytecode;
}
private byte[] modifyAndRegisterClass(ClassLoader loader, String className, ClassReader cr)
{
ClassWriter modifier = modifierFactory.createModifier(loader, cr, capturedType);
cr.accept(modifier, false);
byte[] originalBytecode = cr.b;
if (transformedClasses == null) {
TestRun.mockFixture().addTransformedClass(className, originalBytecode);
}
else {
transformedClasses.put(className, originalBytecode);
}
return modifier.toByteArray();
}
private final class SuperTypeCollector extends EmptyVisitor
{
boolean classExtendsCapturedType;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
classExtendsCapturedType = false;
if (capturedType.equals(superName)) {
classExtendsCapturedType = true;
}
else {
for (String itfc : interfaces) {
if (capturedType.equals(itfc)) {
classExtendsCapturedType = true;
break;
}
}
}
if (!classExtendsCapturedType && !"java/lang/Object".equals(superName)) {
String superClassName = superName.replace('/', '.');
ClassReader cr = ClassFile.createClassFileReader(superClassName);
cr.accept(superTypeCollector, true);
}
throw VisitInterruptedException.INSTANCE;
}
}
}