All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mockit.internal.capturing.CaptureTransformer Maven / Gradle / Ivy

/*
 * Copyright (c) 2006 JMockit developers
 * This file is subject to the terms of the MIT license (see LICENSE.txt).
 */
package mockit.internal.capturing;

import static mockit.internal.capturing.CapturedType.isNotToBeCaptured;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import mockit.asm.classes.ClassReader;
import mockit.asm.classes.ClassVisitor;
import mockit.asm.metadata.ClassMetadataReader;
import mockit.asm.types.JavaType;
import mockit.internal.ClassFile;
import mockit.internal.ClassIdentification;
import mockit.internal.startup.Startup;
import mockit.internal.state.TestRun;

public final class CaptureTransformer implements ClassFileTransformer {
    @NonNull
    private final CapturedType capturedType;
    @NonNull
    private final String capturedTypeDesc;
    @NonNull
    private final CaptureOfImplementations captureOfImplementations;
    @NonNull
    private final Map transformedClasses;
    @NonNull
    private final Map superTypesSearched;
    @Nullable
    private final M typeMetadata;
    private boolean inactive;

    CaptureTransformer(@NonNull CapturedType capturedType,
            @NonNull CaptureOfImplementations captureOfImplementations, boolean registerTransformedClasses,
            @Nullable M typeMetadata) {
        this.capturedType = capturedType;
        capturedTypeDesc = JavaType.getInternalName(capturedType.baseType);
        this.captureOfImplementations = captureOfImplementations;
        transformedClasses = registerTransformedClasses ? new HashMap<>(2)
                : Collections. emptyMap();
        superTypesSearched = new HashMap<>();
        this.typeMetadata = typeMetadata;
    }

    public void deactivate() {
        inactive = true;

        if (!transformedClasses.isEmpty()) {
            for (Map.Entry classNameAndOriginalBytecode : transformedClasses.entrySet()) {
                ClassIdentification classId = classNameAndOriginalBytecode.getKey();
                byte[] originalBytecode = classNameAndOriginalBytecode.getValue();

                Startup.redefineMethods(classId, 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(protectionDomain, classDesc)) {
            return null;
        }

        if (isClassToBeCaptured(loader, classfileBuffer)) {
            String className = classDesc.replace('/', '.');
            ClassReader cr = new ClassReader(classfileBuffer);
            return modifyAndRegisterClass(loader, className, cr);
        }

        return null;
    }

    private boolean isClassToBeCaptured(@Nullable ClassLoader loader, @NonNull byte[] classfileBuffer) {
        ClassMetadataReader cmr = new ClassMetadataReader(classfileBuffer);
        String superName = cmr.getSuperClass();

        if (capturedTypeDesc.equals(superName)) {
            return true;
        }

        String[] interfaces = cmr.getInterfaces();

        if (interfaces != null && isClassWhichImplementsACapturingInterface(interfaces)) {
            return true;
        }

        return superName != null && searchSuperTypes(loader, superName, interfaces);
    }

    private boolean isClassWhichImplementsACapturingInterface(@NonNull String[] interfaces) {
        for (String implementedInterface : interfaces) {
            if (capturedTypeDesc.equals(implementedInterface)) {
                return true;
            }
        }

        return false;
    }

    private boolean searchSuperTypes(@Nullable ClassLoader loader, @NonNull String superName,
            @Nullable String[] interfaces) {
        if (!"java/lang/Object".equals(superName) && !superName.startsWith("mockit/")
                && searchSuperType(loader, superName)) {
            return true;
        }

        if (interfaces != null && interfaces.length > 0) {
            for (String itf : interfaces) {
                if (!itf.startsWith("java/") && !itf.startsWith("javax/") && searchSuperType(loader, itf)) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean searchSuperType(@Nullable ClassLoader loader, @NonNull String superName) {
        Boolean extendsCapturedType = superTypesSearched.get(superName);

        if (extendsCapturedType == null) {
            byte[] classfileBytes = ClassFile.getClassFile(loader, superName);
            extendsCapturedType = isClassToBeCaptured(loader, classfileBytes);
            superTypesSearched.put(superName, extendsCapturedType);
        }

        return extendsCapturedType;
    }

    @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);

        ClassIdentification classId = new ClassIdentification(loader, className);
        byte[] originalBytecode = cr.getBytecode();

        if (transformedClasses == Collections. emptyMap()) {
            TestRun.mockFixture().addTransformedClass(classId, originalBytecode);
        } else {
            transformedClasses.put(classId, originalBytecode);
        }

        TestRun.mockFixture().registerMockedClass(capturedType.baseType);
        return modifier.toByteArray();
    }

    @Nullable
    public > C getCaptureOfImplementationsIfApplicable(@NonNull Class aType) {
        if (typeMetadata != null && capturedType.baseType.isAssignableFrom(aType)) {
            // 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);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy