/*
* Copyright (c) 2006 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 javax.annotation.*;
import mockit.external.asm.*;
import mockit.internal.*;
import mockit.internal.state.*;
import mockit.internal.util.*;
import static mockit.external.asm.ClassReader.*;
import static mockit.internal.capturing.CapturedType.*;
public final class CaptureTransformer<M> implements ClassFileTransformer
{
@Nonnull private final CapturedType capturedType;
@Nonnull private final String capturedTypeDesc;
@Nonnull private final CaptureOfImplementations<M> captureOfImplementations;
@Nonnull private final Map<ClassIdentification, byte[]> transformedClasses;
@Nullable private final M typeMetadata;
private boolean inactive;
CaptureTransformer(
@Nonnull CapturedType capturedType, @Nonnull CaptureOfImplementations<M> captureOfImplementations,
boolean registerTransformedClasses, @Nullable M typeMetadata)
{
this.capturedType = capturedType;
capturedTypeDesc = Type.getInternalName(capturedType.baseType);
this.captureOfImplementations = captureOfImplementations;
transformedClasses = registerTransformedClasses ?
new HashMap<ClassIdentification, byte[]>(2) : Collections.<ClassIdentification, byte[]>emptyMap();
this.typeMetadata = typeMetadata;
}
public void deactivate()
{
inactive = true;
if (!transformedClasses.isEmpty()) {
RedefinitionEngine redefinitionEngine = new RedefinitionEngine();
for (Map.Entry<ClassIdentification, byte[]> classNameAndOriginalBytecode : transformedClasses.entrySet()) {
ClassIdentification classId = classNameAndOriginalBytecode.getKey();
byte[] originalBytecode = classNameAndOriginalBytecode.getValue();
redefinitionEngine.restoreToDefinition(classId.getLoadedClass(), originalBytecode);
}
transformedClasses.clear();
}
}
@Nullable @Override
public byte[] transform(
@Nullable ClassLoader loader, @Nonnull String classDesc, @Nullable Class<?> classBeingRedefined,
@Nullable ProtectionDomain protectionDomain, @Nonnull byte[] classfileBuffer)
{
if (classBeingRedefined != null || inactive || isNotToBeCaptured(loader, protectionDomain, classDesc)) {
return null;
}
ClassReader cr = new ClassReader(classfileBuffer);
SuperTypeCollector superTypeCollector = new SuperTypeCollector(loader);
try {
cr.accept(superTypeCollector, SKIP_DEBUG);
}
catch (VisitInterruptedException ignore) {
if (superTypeCollector.classExtendsCapturedType) {
String className = classDesc.replace('/', '.');
return modifyAndRegisterClass(loader, className, cr);
}
}
return null;
}
@Nonnull
private byte[] modifyAndRegisterClass(
@Nullable ClassLoader loader, @Nonnull String className, @Nonnull ClassReader cr)
{
ClassVisitor modifier = captureOfImplementations.createModifier(loader, cr, capturedType.baseType, typeMetadata);
cr.accept(modifier, SKIP_FRAMES);
ClassIdentification classId = new ClassIdentification(loader, className);
byte[] originalBytecode = cr.b;
if (transformedClasses == Collections.<ClassIdentification, byte[]>emptyMap()) {
TestRun.mockFixture().addTransformedClass(classId, originalBytecode);
}
else {
transformedClasses.put(classId, originalBytecode);
}
TestRun.mockFixture().registerMockedClass(capturedType.baseType);
return modifier.toByteArray();
}
private final class SuperTypeCollector extends ClassVisitor
{
@Nullable private final ClassLoader loader;
boolean classExtendsCapturedType;
private SuperTypeCollector(@Nullable ClassLoader loader) { this.loader = loader; }
@Override
public void visit(
int version, int access, @Nonnull String name, @Nullable String signature, @Nullable String superName,
@Nullable String[] interfaces)
{
classExtendsCapturedType = false;
if (capturedTypeDesc.equals(superName)) {
classExtendsCapturedType = true;
throw VisitInterruptedException.INSTANCE;
}
boolean haveInterfaces = interfaces != null && interfaces.length > 0;
if (haveInterfaces) {
interruptVisitIfClassImplementsAnInterface(interfaces);
}
if (superName != null) {
if (!"java/lang/Object mockit/MockUp".contains(superName)) {
searchSuperType(superName);
}
if (haveInterfaces) {
for (String implementedInterface : interfaces) {
searchSuperType(implementedInterface);
}
}
}
throw VisitInterruptedException.INSTANCE;
}
private void interruptVisitIfClassImplementsAnInterface(@Nonnull String[] interfaces)
{
for (String implementedInterface : interfaces) {
if (capturedTypeDesc.equals(implementedInterface)) {
classExtendsCapturedType = true;
throw VisitInterruptedException.INSTANCE;
}
}
}
private void searchSuperType(@Nonnull String superName)
{
ClassReader cr = ClassFile.createClassFileReader(loader, superName);
try {
cr.accept(this, SKIP_DEBUG);
}
catch (VisitInterruptedException e) {
if (classExtendsCapturedType) throw e;
}
}
}
@Nullable
public <C extends CaptureOfImplementations<?>> C getCaptureOfImplementationsIfApplicable(@Nonnull Class<?> aType)
{
if (capturedType.baseType.isAssignableFrom(aType) && typeMetadata != null) {
//noinspection unchecked
return (C) captureOfImplementations;
}
return null;
}
public boolean areCapturedClasses(@Nonnull Class<?> mockedClass1, @Nonnull Class<?> mockedClass2)
{
Class<?> baseType = capturedType.baseType;
return baseType.isAssignableFrom(mockedClass1) && baseType.isAssignableFrom(mockedClass2);
}
}