Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
mockit.internal.expectations.mocking.ExpectationsModifier Maven / Gradle / Ivy
/*
* Copyright (c) 2006-2013 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.expectations.mocking;
import java.util.*;
import static java.lang.reflect.Modifier.*;
import static mockit.external.asm4.Opcodes.*;
import mockit.external.asm4.*;
import mockit.internal.startup.*;
import mockit.internal.util.*;
final class ExpectationsModifier extends MockedTypeModifier
{
private static final int METHOD_ACCESS_MASK = ACC_SYNTHETIC + ACC_ABSTRACT;
private static final Map DEFAULT_FILTERS = new HashMap();
static {
DEFAULT_FILTERS.put("java/lang/Object", " getClass hashCode");
DEFAULT_FILTERS.put("java/lang/String", "");
DEFAULT_FILTERS.put("java/lang/AbstractStringBuilder", "");
DEFAULT_FILTERS.put("java/lang/StringBuilder", "");
DEFAULT_FILTERS.put("java/lang/StringBuffer", "");
DEFAULT_FILTERS.put("java/lang/System", "arraycopy getProperties getSecurityManager identityHashCode");
DEFAULT_FILTERS.put("java/lang/Throwable", " fillInStackTrace");
DEFAULT_FILTERS.put("java/lang/Exception", "");
DEFAULT_FILTERS.put("java/lang/Thread", "currentThread isInterrupted interrupted");
DEFAULT_FILTERS.put("java/util/Hashtable", "get hash");
DEFAULT_FILTERS.put("java/util/ArrayList", "");
DEFAULT_FILTERS.put("java/util/HashMap", "");
DEFAULT_FILTERS.put("java/util/jar/JarEntry", "");
DEFAULT_FILTERS.put("java/util/logging/LogManager",
"getLogger getLogManager getUserContext readPrimordialConfiguration");
}
private final MockingConfiguration mockingCfg;
private String className;
private String baseClassNameForCapturedInstanceMethods;
private boolean stubOutClassInitialization;
private boolean ignoreConstructors;
private int executionMode;
private boolean isProxy;
private String defaultFilters;
ExpectationsModifier(ClassLoader classLoader, ClassReader classReader, MockedType typeMetadata)
{
super(classReader, null);
if (typeMetadata == null) {
mockingCfg = null;
}
else {
mockingCfg = typeMetadata.mockingCfg;
stubOutClassInitialization = typeMetadata.isClassInitializationToBeStubbedOut();
}
setUseMockingBridge(classLoader);
}
public void setClassNameForCapturedInstanceMethods(String internalClassName)
{
baseClassNameForCapturedInstanceMethods = internalClassName;
}
public void useDynamicMocking(boolean methodsOnly)
{
ignoreConstructors = methodsOnly;
executionMode = 1;
}
public void useDynamicMockingForInstanceMethods(MockedType typeMetadata)
{
ignoreConstructors = typeMetadata == null || typeMetadata.getMaxInstancesToCapture() <= 0;
executionMode = 2;
}
void useDynamicMockingForSuperClass()
{
if (executionMode == 0) {
executionMode = 3;
}
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
if ("java/lang/Class".equals(name) || "java/lang/ClassLoader".equals(name) ) {
throw new IllegalArgumentException("Class " + name.replace('/', '.') + " is not mockable");
}
super.visit(version, access, name, signature, superName, interfaces);
isProxy = "java/lang/reflect/Proxy".equals(superName);
if (isProxy) {
className = interfaces[0];
defaultFilters = null;
}
else {
className = name;
defaultFilters = DEFAULT_FILTERS.get(name);
if (defaultFilters != null && defaultFilters.length() == 0) {
throw VisitInterruptedException.INSTANCE;
}
}
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
{
boolean syntheticOrAbstractMethod = (access & METHOD_ACCESS_MASK) != 0;
if (syntheticOrAbstractMethod || isProxy && isConstructorOrSystemMethodNotToBeMocked(name, desc)) {
return unmodifiedBytecode(access, name, desc, signature, exceptions);
}
boolean noFiltersToMatch = mockingCfg == null;
boolean matchesFilters = noFiltersToMatch || mockingCfg.matchesFilters(name, desc);
if ("".equals(name)) {
return stubOutClassInitializationIfApplicable(access, noFiltersToMatch, matchesFilters);
}
else if (stubOutFinalizeMethod(access, name, desc)) {
return null;
}
if (
!matchesFilters ||
isMethodFromCapturedClassNotToBeMocked(access) ||
noFiltersToMatch && isMethodOrConstructorNotToBeMocked(access, name)
) {
return unmodifiedBytecode(access, name, desc, signature, exceptions);
}
// Otherwise, replace original implementation with redirect to JMockit.
validateModificationOfNativeMethod(access, name);
startModifiedMethodVersion(access, name, desc, signature, exceptions);
boolean visitingConstructor = "".equals(name);
if (visitingConstructor && superClassName != null) {
generateCallToSuperConstructor();
}
String internalClassName = className;
if (!visitingConstructor && baseClassNameForCapturedInstanceMethods != null) {
internalClassName = baseClassNameForCapturedInstanceMethods;
}
int actualExecutionMode = determineAppropriateExecutionMode(access, visitingConstructor);
if (useMockingBridge) {
return
generateCallToHandlerThroughMockingBridge(
access, signature, exceptions, internalClassName, actualExecutionMode);
}
generateDirectCallToHandler(internalClassName, access, name, desc, signature, exceptions, actualExecutionMode);
if (actualExecutionMode > 0) {
generateDecisionBetweenReturningOrContinuingToRealImplementation();
// Constructors of non-JRE classes can't be modified (unless running with "-noverify") in a way that
// "super(...)/this(...)" get called twice, so we disregard such calls when copying the original bytecode.
return visitingConstructor ? new DynamicConstructorModifier() : copyOriginalImplementationCode(access);
}
generateReturnWithObjectAtTopOfTheStack(methodDesc);
mw.visitMaxs(1, 0);
return methodAnnotationsVisitor;
}
private MethodVisitor unmodifiedBytecode(int access, String name, String desc, String signature, String[] exceptions)
{
return super.visitMethod(access, name, desc, signature, exceptions);
}
private boolean isConstructorOrSystemMethodNotToBeMocked(String name, String desc)
{
return
"".equals(name) || isMethodFromObject(name, desc) ||
"annotationType".equals(name) && "()Ljava/lang/Class;".equals(desc);
}
private MethodVisitor stubOutClassInitializationIfApplicable(int access, boolean noFilters, boolean matchesFilters)
{
mw = super.visitMethod(access, "", "()V", null, null);
if (!noFilters && matchesFilters || stubOutClassInitialization) {
generateEmptyImplementation();
return null;
}
return mw;
}
private boolean stubOutFinalizeMethod(int access, String name, String desc)
{
if ("finalize".equals(name) && "()V".equals(desc)) {
mw = super.visitMethod(access, name, desc, null, null);
generateEmptyImplementation();
return true;
}
return false;
}
private boolean isMethodFromCapturedClassNotToBeMocked(int access)
{
return baseClassNameForCapturedInstanceMethods != null && (isStatic(access) || isPrivate(access));
}
private boolean isMethodOrConstructorNotToBeMocked(int access, String name)
{
return
isConstructorToBeIgnored(name) ||
isStaticMethodToBeIgnored(access) ||
isNativeMethodForDynamicMocking(access) ||
useMockingBridge && isPrivate(access) && isNative(access) ||
defaultFilters != null && defaultFilters.contains(name);
}
private boolean isConstructorToBeIgnored(String name) { return ignoreConstructors && "".equals(name); }
private boolean isStaticMethodToBeIgnored(int access) { return executionMode == 2 && isStatic(access); }
private boolean isNativeMethodForDynamicMocking(int access) { return executionMode > 0 && isNative(access); }
private void validateModificationOfNativeMethod(int access, String name)
{
if (isNative(access) && !Startup.isJava6OrLater()) {
throw new IllegalArgumentException(
"Mocking of native methods not supported under JDK 1.5; please filter out method \"" +
name + "\", or run under JDK 1.6+");
}
}
private int determineAppropriateExecutionMode(int access, boolean visitingConstructor)
{
if (executionMode == 2) {
if (visitingConstructor) {
return ignoreConstructors ? 0 : 1;
}
else if (isStatic(access)) {
return 0;
}
}
return executionMode;
}
private MethodVisitor generateCallToHandlerThroughMockingBridge(
int access, String genericSignature, String[] exceptions, String internalClassName, int executionMode)
{
generateCodeToObtainInstanceOfMockingBridge(MockedBridge.MB);
// First and second "invoke" arguments:
boolean isStatic = generateCodeToPassThisOrNullIfStaticMethod(access);
mw.visitInsn(ACONST_NULL);
// Create array for call arguments (third "invoke" argument):
Type[] argTypes = Type.getArgumentTypes(methodDesc);
generateCodeToCreateArrayOfObject(7 + argTypes.length);
int i = 0;
generateCodeToFillArrayElement(i++, access);
generateCodeToFillArrayElement(i++, internalClassName);
generateCodeToFillArrayElement(i++, methodName);
generateCodeToFillArrayElement(i++, methodDesc);
generateCodeToFillArrayElement(i++, genericSignature);
generateCodeToFillArrayElement(i++, getListOfExceptionsAsSingleString(exceptions));
generateCodeToFillArrayElement(i++, executionMode);
generateCodeToPassMethodArgumentsAsVarargs(argTypes, i, isStatic ? 0 : 1);
generateCallToInvocationHandler();
generateDecisionBetweenReturningOrContinuingToRealImplementation();
// Copies the entire original implementation even for a constructor, in which case the complete bytecode inside
// the constructor fails the strict verification activated by "-Xfuture". However, this is necessary to allow the
// full execution of a JRE constructor when the call was not meant to be mocked.
return copyOriginalImplementationCode(access);
}
private MethodVisitor copyOriginalImplementationCode(int access)
{
if (isNative(access)) {
generateEmptyImplementation(methodDesc);
return methodAnnotationsVisitor;
}
return new DynamicModifier();
}
private class DynamicModifier extends MethodVisitor
{
DynamicModifier() { super(mw); }
@Override
public final void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int idx)
{
registerParameterName(name, idx);
// For some reason, the start position for "this" gets displaced by bytecode inserted at the beginning,
// in a method modified by the EMMA tool. If not treated, this causes a ClassFormatError.
if (end.position > 0 && start.position > end.position) {
start.position = end.position;
}
// Ignores any local variable with required information missing, to avoid a VerifyError/ClassFormatError.
if (start.position > 0 && end.position > 0) {
super.visitLocalVariable(name, desc, signature, start, end, idx);
}
}
}
private final class DynamicConstructorModifier extends DynamicModifier
{
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc)
{
disregardIfInvokingAnotherConstructor(opcode, owner, name, desc);
}
}
}