/**************************************************************************************
 * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved.                 *
 * http://aspectwerkz.codehaus.org                                                    *
 * ---------------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the LGPL license      *
 * a copy of which has been included with this distribution in the license.txt file.  *
 **************************************************************************************/
package org.codehaus.aspectwerkz.transform.inlining;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.CodeVisitor;
import org.objectweb.asm.Constants;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;

import org.codehaus.aspectwerkz.CrossCuttingInfo;
import org.codehaus.aspectwerkz.DeploymentModel;
import org.codehaus.aspectwerkz.AdviceInfo;
import org.codehaus.aspectwerkz.transform.TransformationUtil;
import org.codehaus.aspectwerkz.transform.TransformationConstants;
import org.codehaus.aspectwerkz.joinpoint.management.JoinPointType;
import org.codehaus.aspectwerkz.joinpoint.management.JoinPointManager;
import org.codehaus.aspectwerkz.joinpoint.management.AdviceIndexInfo;
import org.codehaus.aspectwerkz.joinpoint.Signature;
import org.codehaus.aspectwerkz.joinpoint.Rtti;
import org.codehaus.aspectwerkz.aspect.AspectContainer;
import org.codehaus.aspectwerkz.expression.ExpressionContext;

import java.util.List;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;

/**
 * A compiler that compiles/generates a class that represents a specific join point, a class which invokes the advices
 * and the target join point statically.
 *
 * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
 * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur </a>
 */
public class JoinPointCompiler implements Constants, TransformationConstants {

    // static and member field names
    private static final String TARGET_CLASS_FIELD_NAME = "TARGET_CLASS";
    private static final String SYSTEM_FIELD_NAME = "SYSTEM";
    private static final String SIGNATURE_FIELD_NAME = "SIGNATURE";
    private static final String META_DATA_FIELD_NAME = "META_DATA";
    private static final String STATIC_JOIN_POINT_INSTANCE_FIELD_NAME = "STATIC_JOIN_POINT";
    private static final String AROUND_ADVICE_FIELD_PREFIX = "AROUND_";
    private static final String BEFORE_ADVICE_FIELD_PREFIX = "BEFORE_";
    private static final String AFTER_FINALLY_ADVICE_FIELD_PREFIX = "AFTER_FINALLY_";
    private static final String AFTER_RETURNING_ADVICE_FIELD_PREFIX = "AFTER_RETURNING_";
    private static final String AFTER_THROWING_ADVICE_FIELD_PREFIX = "AFTER_THROWING_";
    private static final String STACK_FRAME_FIELD_NAME = "STACK_FRAME_COUNTER";
    private static final String CALLEE_INSTANCE_FIELD_NAME = "CALLEE";
    private static final String CALLER_INSTANCE_FIELD_NAME = "CALLER";
    private static final String ARGUMENT_FIELD = "ARGUMENT_";
    private static final String RTTI_INSTANCE_FIELD_NAME = "RTTI";

    // runtime system signatures and types
    private static final String INVOKE_METHOD_NAME = "invoke";
    private static final String METHOD_SIGNATURE_IMPL_CLASS_SIGNATURE = "Lorg/codehaus/aspectwerkz/joinpoint/impl/MethodSignatureImpl;";
    private static final String CONSTRUCTOR_SIGNATURE_IMPL_CLASS_SIGNATURE = "Lorg/codehaus/aspectwerkz/joinpoint/impl/ConstructorSignatureImplInlined;";
    private static final String FIELD_SIGNATURE_IMPL_CLASS_SIGNATURE = "Lorg/codehaus/aspectwerkz/joinpoint/impl/FieldSignatureImpl;";
    private static final String HANDLER_SIGNATURE_IMPL_CLASS_SIGNATURE = "Lorg/codehaus/aspectwerkz/joinpoint/impl/CatchClauseSignatureImpl;";
    private static final String NEW_METHOD_SIGNATURE_METHOD_SIGNATURE = "(Ljava/lang/Class;I)Lorg/codehaus/aspectwerkz/joinpoint/impl/MethodSignatureImpl;";
    private static final String NEW_CONSTRUCTOR_SIGNATURE_METHOD_SIGNATURE = "(Ljava/lang/Class;I)Lorg/codehaus/aspectwerkz/joinpoint/impl/ConstructorSignatureImplInlined;";
    private static final String NEW_FIELD_SIGNATURE_METHOD_SIGNATURE = "(Ljava/lang/Class;I)Lorg/codehaus/aspectwerkz/joinpoint/impl/FieldSignatureImpl;";
    private static final String NEW_HANDLER_SIGNATURE_METHOD_SIGNATURE = "(Ljava/lang/Class;I)Lorg/codehaus/aspectwerkz/joinpoint/impl/CatchClauseSignatureImpl;";
    private static final String SIGNATURE_FACTORY_CLASS = "org/codehaus/aspectwerkz/joinpoint/management/SignatureFactory";
    private static final String SYSTEM_CLASS_SIGNATURE = "Lorg/codehaus/aspectwerkz/AspectSystem;";
    private static final String SYSTEM_CLASS_NAME = "org/codehaus/aspectwerkz/AspectSystem";
    private static final String ASPECT_MANAGER_CLASS_NAME = "org/codehaus/aspectwerkz/aspect/management/AspectManager";
    private static final String ASPECT_CONTAINER_CLASS_NAME = "org/codehaus/aspectwerkz/aspect/AspectContainer";
    private static final String GET_ASPECT_MANAGER_METHOD_NAME = "getAspectManager";
    private static final String GET_ASPECT_MANAGER_METHOD_SIGNATURE = "(Ljava/lang/String;)Lorg/codehaus/aspectwerkz/aspect/management/AspectManager;";
    private static final String GET_ASPECT_CONTAINER_METHOD_NAME = "getAspectContainer";
    private static final String GET_ASPECT_CONTAINER_METHOD_SIGNATURE = "(I)Lorg/codehaus/aspectwerkz/aspect/AspectContainer;";
    private static final String GET_PER_JVM_ASPECT_METHOD_NAME = "createPerJvmAspect";
    private static final String GET_PER_JVM_ASPECT_METHOD_SIGNATURE = "()Ljava/lang/Object;";
    private static final String GET_PER_CLASS_ASPECT_METHOD_NAME = "createPerClassAspect";
    private static final String GET_PER_CLASS_ASPECT_METHOD_SIGNATURE = "(Ljava/lang/Class;)Ljava/lang/Object;";
    private static final String SYSTEM_LOADER_CLASS_NAME = "org/codehaus/aspectwerkz/SystemLoader";
    private static final String GET_SYSTEM_METHOD_NAME = "getSystem";
    private static final String GET_SYSTEM_METHOD_NAME_SIGNATURE = "(Ljava/lang/Class;)Lorg/codehaus/aspectwerkz/AspectSystem;";
    private static final String GET_SIGNATURE_METHOD_NAME = "getSignature";
    private static final String GET_SIGNATURE_METHOD_SIGNATURE = "()Lorg/codehaus/aspectwerkz/joinpoint/Signature;";
    private static final String GET_RTTI_METHOD_NAME = "getRtti";
    private static final String GET_RTTI_METHOD_SIGNATURE = "()Lorg/codehaus/aspectwerkz/joinpoint/Rtti;";
    private static final String PROCEED_METHOD_NAME = "proceed";
    private static final String PROCEED_METHOD_SIGNATURE = "()Ljava/lang/Object;";
    private static final String DEEP_COPY_METHOD_NAME = "deepCopy";
    private static final String DEEP_COPY_METHOD_SIGNATURE = "()Lorg/codehaus/aspectwerkz/joinpoint/StaticJoinPoint;";
    private static final String ADD_META_DATA_METHOD_NAME = "addMetaData";
    private static final String ADD_META_DATA_METHOD_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Object;)V";
    private static final String MAP_CLASS_SIGNATURE = "Ljava/util/Map;";
    private static final String MAP_CLASS_NAME = "java/util/Map";
    private static final String PUT_METHOD_NAME = "put";
    private static final String PUT_METHOD_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;";
    private static final String GET_META_DATA_METHOD_NAME = "getMetaData";
    private static final String GET_TARGET_METHOD_NAME = "getTarget";
    private static final String GET_THIS_METHOD_NAME = "getThis";
    private static final String GET_CALLER_METHOD_NAME = "getCaller";
    private static final String GET_CALLEE_METHOD_NAME = "getCallee";
    private static final String GET_METHOD_NAME = "get";
    private static final String GET_METHOD_SIGNATURE = "(Ljava/lang/Object;)Ljava/lang/Object;";
    private static final String GET_META_DATA_METHOD_SIGNATURE = "(Ljava/lang/Object;)Ljava/lang/Object;";
    private static final String NEW_METHOD_SIGNATURE_METHOD_NAME = "newMethodSignature";
    private static final String NEW_CONSTRUCTOR_SIGNATURE_METHOD_NAME = "newConstructorSignature";
    private static final String NEW_FIELD_SIGNATURE_METHOD_NAME = "newFieldSignature";
    private static final String NEW_CATCH_CLAUSE_SIGNATURE_METHOD_NAME = "newCatchClauseSignature";
    private static final String HASH_MAP_CLASS_NAME = "java/util/HashMap";
    private static final String NO_PARAM_RETURN_VOID_SIGNATURE = "()V";
    private static final String CLASS_NOT_FOUND_EXCEPTION_CLASS_NAME = "java/lang/ClassNotFoundException";
    private static final String GET_CALLER_CLASS_METHOD_NAME = "getCallerClass";
    private static final String GET_CALLER_CLASS_METHOD_SIGNATURE = "()Ljava/lang/Class;";
    private static final String GET_TARGET_CLASS_METHOD_NAME = "getTargetClass";
    private static final String GET_TARGET_CLASS_METHOD_SIGNATURE = "()Ljava/lang/Class;";
    private static final String GET_TYPE_METHOD_NAME = "getType";
    private static final String GET_TYPE_METHOD_SIGNATURE = "()Ljava/lang/String;";
    private static final String RESET_METHOD_NAME = "reset";
    private static final String RUNTIME_EXCEPTION_CLASS_NAME = "java/lang/RuntimeException";
    private static final String FOR_NAME_METHOD_NAME = "forName";
    private static final String FOR_NAME_METHOD_SIGNATURE = "(Ljava/lang/String;)Ljava/lang/Class;";
    private static final String RUNTIME_EXCEPTION_INIT_METHOD_SIGNATURE = "(Ljava/lang/String;)V";
    private static final String IS_IN_CFLOW_METOD_NAME = "isInCflow";
    private static final String IS_IN_CFLOW_METOD_SIGNATURE = "()Z";
    private static final String STATIC_JOIN_POINT_CLASS_NAME = "org/codehaus/aspectwerkz/joinpoint/StaticJoinPoint";
    private static final String JOIN_POINT_CLASS_NAME = "org/codehaus/aspectwerkz/joinpoint/JoinPoint";
    private static final String JOIN_POINT_CLASS_JAVA_NAME = "org.codehaus.aspectwerkz.joinpoint.JoinPoint";
    private static final String NO_PARAMS_SIGNATURE = "()";

    private static final String RTTI_CLASS_SIGNATURE = "Lorg/codehaus/aspectwerkz/joinpoint/Rtti;";
    private static final String METHOD_RTTI_IMPL_CLASS_SIGNATURE = "Lorg/codehaus/aspectwerkz/joinpoint/impl/MethodRttiImpl;";
    private static final String METHOD_RTTI_IMPL_CLASS_NAME = "org/codehaus/aspectwerkz/joinpoint/impl/MethodRttiImpl";
    private static final String METHOD_SIGNATURE_IMPL_CLASS_NAME = "org/codehaus/aspectwerkz/joinpoint/impl/MethodSignatureImpl";
    private static final String METHOD_RTTI_IMPL_INIT_SIGNATURE = "(Lorg/codehaus/aspectwerkz/joinpoint/impl/MethodSignatureImpl;Ljava/lang/Object;Ljava/lang/Object;)V";
    private static final String CONSTRUCTOR_RTTI_IMPL_CLASS_SIGNATURE = "Lorg/codehaus/aspectwerkz/joinpoint/impl/ConstructorRttiImpl;";
    private static final String CONSTRUCTOR_RTTI_IMPL_CLASS_NAME = "org/codehaus/aspectwerkz/joinpoint/impl/ConstructorRttiImpl";
    private static final String CONSTRUCTOR_SIGNATURE_IMPL_CLASS_NAME = "org/codehaus/aspectwerkz/joinpoint/impl/ConstructorSignatureImpl";
    private static final String CONSTRUCTOR_RTTI_IMPL_INIT_SIGNATURE = "(Lorg/codehaus/aspectwerkz/joinpoint/impl/ConstructorSignatureImpl;Ljava/lang/Object;Ljava/lang/Object;)V";
    private static final String FIELD_RTTI_IMPL_CLASS_SIGNATURE = "Lorg/codehaus/aspectwerkz/joinpoint/impl/FieldRttiImpl;";
    private static final String FIELD_RTTI_IMPL_CLASS_NAME = "org/codehaus/aspectwerkz/joinpoint/impl/FieldRttiImpl";
    private static final String FIELD_SIGNATURE_IMPL_CLASS_NAME = "org/codehaus/aspectwerkz/joinpoint/impl/FieldSignatureImpl";
    private static final String FIELD_RTTI_IMPL_INIT_SIGNATURE = "(Lorg/codehaus/aspectwerkz/joinpoint/impl/FieldSignatureImpl;Ljava/lang/Object;Ljava/lang/Object;)V";
    private static final String HANDLER_RTTI_IMPL_CLASS_SIGNATURE = "Lorg/codehaus/aspectwerkz/joinpoint/impl/CatchClauseRttiImpl;";
    private static final String HANDLER_RTTI_IMPL_CLASS_NAME = "org/codehaus/aspectwerkz/joinpoint/impl/CatchClauseRttiImpl";
    private static final String HANDLER_SIGNATURE_IMPL_CLASS_NAME = "org/codehaus/aspectwerkz/joinpoint/impl/CatchClauseSignatureImpl";
    private static final String HANDLER_RTTI_IMPL_INIT_SIGNATURE = "(Lorg/codehaus/aspectwerkz/joinpoint/impl/CatchClauseSignatureImpl;Ljava/lang/Object;Ljava/lang/Object;)V";
    private static final String SET_PARAMETER_VALUES_METHOD_NAME = "setParameterValues";
    private static final String SET_PARAMETER_VALUES_METHOD_SIGNATURE = "([Ljava/lang/Object;)V";
    private static final String SET_PARAMETER_VALUE_METHOD_NAME = "setParameterValue";
    private static final String SET_PARAMETER_VALUE_METHOD_SIGNATURE = "(Ljava/lang/Object;)V";
    private static final String SET_FIELD_VALUE_METHOD_NAME = "setFieldValue";
    private static final String SET_FIELD_VALUE_METHOD_SIGNATURE = "(Ljava/lang/Object;)V";
    private static final String SET_RETURN_VALUE_METHOD_NAME = "setReturnValue";
    private static final String SET_RETURN_VALUE_METHOD_SIGNATURE = "(Ljava/lang/Object;)V";

    // FIXME define these two using VM option - if dump dir specified then dump
    private static final boolean DUMP_CLASSES = true;
    private static final String DUMP_DIR = "_dump/jp";

    private static final boolean STATIC_JOIN_POINT = true;
    private static final boolean NON_STATIC_JOIN_POINT = false;

    /**
     * Compiles a join point class, one specific class for each distinct join point. The compiled join point class
     * inherits the base join point class.
     *
     * @param joinPointClassName
     * @param joinPointType
     * @param joinPointHash
     * @param callerClassName
     * @param callerMethodName
     * @param callerMethodDesc
     * @param callerMethodModifiers
     * @param calleeClassName
     * @param calleeMemberName
     * @param calleeMemberDesc
     * @param calleeMemberModifiers
     * @param advices
     * @param loader
     * @param joinPointSequence
     * @return
     */
    public static Class loadJoinPoint(
            final String joinPointClassName,
            final int joinPointType,
            final int joinPointHash,
            final String callerClassName,
            final String callerMethodName,
            final String callerMethodDesc,
            final int callerMethodModifiers,
            final String calleeClassName,
            final String calleeMemberName,
            final String calleeMemberDesc,
            final int calleeMemberModifiers,
            final AdviceIndexInfo[] advices,
            final ClassLoader loader,
            final int joinPointSequence) {

        byte[] joinPointBytes = compileJoinPoint(
                joinPointClassName,
                joinPointType,
                joinPointHash,
                callerClassName,
                callerMethodName,
                callerMethodDesc,
                callerMethodModifiers,
                calleeClassName,
                calleeMemberName,
                calleeMemberDesc,
                calleeMemberModifiers,
                advices,
                joinPointSequence
        );

        // load and return the generated join point class
        return AsmHelper.loadClass(loader, joinPointBytes, joinPointClassName);
    }

    /**
     * Compiles a join point class, one specific class for each distinct join point. The compiled join point class
     * inherits the base join point class.
     *
     * @param joinPointClassName
     * @param joinPointType
     * @param joinPointHash
     * @param callerClassName
     * @param callerMethodName
     * @param callerMethodDesc
     * @param callerMethodModifiers
     * @param calleeClassName
     * @param calleeMemberName
     * @param calleeMemberDesc
     * @param calleeMemberModifiers
     * @param advices
     * @param joinPointSequence
     * @return the generated, compiled and loaded join point class
     */
    public static byte[] compileJoinPoint(
            final String joinPointClassName,
            final int joinPointType,
            final int joinPointHash,
            String callerClassName,
            final String callerMethodName,
            final String callerMethodDesc,
            final int callerMethodModifiers,
            String calleeClassName,
            final String calleeMemberName,
            final String calleeMemberDesc,
            final int calleeMemberModifiers,
            final AdviceIndexInfo[] advices,
            final int joinPointSequence) {

        // NOTE: internal compiler class name format is ALWAYS using '/'
        callerClassName = callerClassName.replace('.', '/');
        calleeClassName = calleeClassName.replace('.', '/');
        final String callerClassSignature = L + callerClassName + SEMICOLON;
        final String calleeClassSignature = L + calleeClassName + SEMICOLON;
        try {
            // grab the advices
            final AdviceMethodInfo[] aroundAdviceMethodInfos = getAdviceMethodInfos(
                    JoinPointManager.extractAroundAdvices(advices),
                    AROUND_ADVICE_FIELD_PREFIX
            );
            final AdviceMethodInfo[] beforeAdviceMethodInfos = getAdviceMethodInfos(
                    JoinPointManager.extractBeforeAdvices(advices),
                    BEFORE_ADVICE_FIELD_PREFIX
            );
            final AdviceMethodInfo[] afterFinallyAdviceMethodInfos = getAdviceMethodInfos(
                    JoinPointManager.extractAfterFinallyAdvices(advices),
                    AFTER_FINALLY_ADVICE_FIELD_PREFIX
            );
            final AdviceMethodInfo[] afterReturningAdviceMethodInfos = getAdviceMethodInfos(
                    JoinPointManager.extractAfterReturningAdvices(advices),
                    AFTER_RETURNING_ADVICE_FIELD_PREFIX
            );
            final AdviceMethodInfo[] afterThrowingAdviceMethodInfos = getAdviceMethodInfos(
                    JoinPointManager.extractAfterThrowingAdvices(advices),
                    AFTER_THROWING_ADVICE_FIELD_PREFIX
            );

            // start the class compilation
            final ClassWriter cw = AsmHelper.newClassWriter(true);

            // checks if at least one of the advices uses a JoinPoint (and not a StaticJoinPoint)
            // if so we need to create a RTTI instance as well as maintain sit during execution
            boolean requiresRtti = false;
            if (requiresRtti(aroundAdviceMethodInfos) ||
                requiresRtti(beforeAdviceMethodInfos) ||
                requiresRtti(afterFinallyAdviceMethodInfos) ||
                requiresRtti(afterReturningAdviceMethodInfos) ||
                requiresRtti(afterThrowingAdviceMethodInfos)) {
                // at least one of the advice is using JoinPoint and not only StaticJoinPoint
                requiresRtti = true;
                cw.visit(
                        AsmHelper.JAVA_VERSION,
                        ACC_PUBLIC + ACC_SUPER,
                        joinPointClassName,
                        OBJECT_CLASS_NAME,
                        new String[]{JOIN_POINT_CLASS_NAME},
                        null
                );
            } else {
                cw.visit(
                        AsmHelper.JAVA_VERSION,
                        ACC_PUBLIC + ACC_SUPER,
                        joinPointClassName,
                        OBJECT_CLASS_NAME,
                        new String[]{STATIC_JOIN_POINT_CLASS_NAME},
                        null
                );
            }

            // TODO: INNER CLASS OR NOT?
            // flag it as a public static inner class
            // Note: if <init> changes, we will need to pass the containing instance as arg0 and add a syntetic field
//            int innerIndex = joinPointClassName.lastIndexOf('$');
//            cw.visitInnerClass(joinPointClassName,
//                    joinPointClassName.substring(0, innerIndex),
//                    joinPointClassName.substring(innerIndex + 1, joinPointClassName.length()),
//                    ACC_PUBLIC + ACC_STATIC);

            createFieldsCommonToAllJoinPoints(
                    cw, requiresRtti, joinPointType, joinPointClassName,
                    callerClassSignature, calleeClassSignature
            );
            final String[] fieldNames = createJoinPointSpecificFields(cw, joinPointType, calleeMemberDesc);
            createStaticInitializer(
                    cw,
                    joinPointClassName,
                    joinPointType,
                    joinPointHash,
                    calleeClassName,
                    aroundAdviceMethodInfos,
                    beforeAdviceMethodInfos,
                    afterFinallyAdviceMethodInfos,
                    afterReturningAdviceMethodInfos,
                    afterThrowingAdviceMethodInfos
            );

            createConstructor(
                    cw, requiresRtti, joinPointClassName, joinPointType,
                    callerClassSignature, calleeClassSignature
            );

            createUtilityMethods(
                    cw, requiresRtti, joinPointType, joinPointClassName,
                    callerClassSignature, calleeClassSignature
            );

            createDeepCopyMethod(
                    cw, joinPointClassName, fieldNames,
                    getJoinPointArgumentTypes(joinPointType, calleeMemberDesc),
                    callerClassSignature, calleeClassSignature
            );

            if (aroundAdviceMethodInfos.length == 0) {
                createInlinedInvokeMethod(
                        cw,
                        requiresRtti,
                        callerClassName,
                        callerClassSignature,
                        callerMethodName,
                        callerMethodDesc,
                        callerMethodModifiers,
                        calleeClassName,
                        calleeClassSignature,
                        calleeMemberName,
                        calleeMemberDesc,
                        calleeMemberModifiers,
                        joinPointClassName,
                        joinPointType,
                        fieldNames,
                        joinPointSequence,
                        aroundAdviceMethodInfos,
                        beforeAdviceMethodInfos,
                        afterFinallyAdviceMethodInfos,
                        afterReturningAdviceMethodInfos,
                        afterThrowingAdviceMethodInfos
                );
            } else {
                createInvokeMethod(
                        cw,
                        requiresRtti,
                        callerClassName,
                        callerClassSignature,
                        callerMethodName,
                        callerMethodDesc,
                        callerMethodModifiers,
                        calleeClassName,
                        calleeClassSignature,
                        calleeMemberName,
                        calleeMemberDesc,
                        calleeMemberModifiers,
                        joinPointClassName,
                        joinPointType,
                        fieldNames,
                        joinPointSequence,
                        aroundAdviceMethodInfos,
                        beforeAdviceMethodInfos,
                        afterFinallyAdviceMethodInfos,
                        afterReturningAdviceMethodInfos,
                        afterThrowingAdviceMethodInfos
                );

                createProceedMethod(
                        cw,
                        requiresRtti,
                        calleeClassName,
                        calleeClassSignature,
                        calleeMemberName,
                        calleeMemberDesc,
                        calleeMemberModifiers,
                        joinPointClassName,
                        joinPointType,
                        fieldNames,
                        joinPointSequence,
                        aroundAdviceMethodInfos,
                        null
                );
            }

            cw.visitEnd();

            if (DUMP_CLASSES) {
                AsmHelper.dumpClass(DUMP_DIR, joinPointClassName, cw);
            }
            return cw.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
            StringBuffer buf = new StringBuffer();
            buf.append("could not compile join point instance for join point with hash [");
            buf.append(joinPointHash);
            buf.append("] and declaring class [");
            buf.append(callerClassName);
            buf.append("] due to: ");
            if (e instanceof InvocationTargetException) {
                buf.append(((InvocationTargetException)e).getTargetException().toString());
            } else {
                buf.append(e.toString());
            }
            throw new RuntimeException(buf.toString());
        }
    }

    /**
     * Computes the joinpoint classname : "target/class_type_hash_suffix"
     *
     * @param thisClassName
     * @param joinPointType
     * @param joinPointHash
     * @return the JIT joinpoint classname
     */
    public static String getJoinPointClassName(
            final String thisClassName,
            final int joinPointType,
            final int joinPointHash) {
        StringBuffer classNameBuf = new StringBuffer(thisClassName);
        // TODO: INNER CLASS OR NOT?
//        classNameBuf.append("$");
        classNameBuf.append('_');
        classNameBuf.append(joinPointType);
        classNameBuf.append('_');
        classNameBuf.append(joinPointHash);
        classNameBuf.append(JOIN_POINT_CLASS_SUFFIX);

        //replace minus signs on joinPointHash
        String joinPointClassName = classNameBuf.toString().replace('-', '_');
        return joinPointClassName.replace('.', '/');
    }

    /**
     * Creates fields common for all join point classes.
     *
     * @param cw
     * @param requiresRtti
     * @param joinPointType
     * @param joinPointClassName
     * @param callerClassSignature
     * @param calleeClassSignature
     */
    private static void createFieldsCommonToAllJoinPoints(
            final ClassWriter cw,
            final boolean requiresRtti,
            final int joinPointType,
            final String joinPointClassName,
            final String callerClassSignature,
            final String calleeClassSignature) {
        cw.visitField(
                ACC_PRIVATE + ACC_FINAL + ACC_STATIC,
                TARGET_CLASS_FIELD_NAME,
                CLASS_CLASS_SIGNATURE,
                null,
                null
        );
        cw.visitField(
                ACC_PRIVATE + ACC_FINAL + ACC_STATIC,
                SYSTEM_FIELD_NAME,
                SYSTEM_CLASS_SIGNATURE,
                null,
                null
        );
        switch (joinPointType) {
            case JoinPointType.METHOD_EXECUTION:
            case JoinPointType.METHOD_CALL:
                cw.visitField(
                        ACC_PRIVATE + ACC_FINAL + ACC_STATIC,
                        SIGNATURE_FIELD_NAME,
                        METHOD_SIGNATURE_IMPL_CLASS_SIGNATURE,
                        null,
                        null
                );
                break;
            case JoinPointType.CONSTRUCTOR_CALL:
            case JoinPointType.CONSTRUCTOR_EXECUTION:
                cw.visitField(
                        ACC_PRIVATE + ACC_FINAL + ACC_STATIC,
                        SIGNATURE_FIELD_NAME,
                        CONSTRUCTOR_SIGNATURE_IMPL_CLASS_SIGNATURE,
                        null,
                        null
                );
                break;
            case JoinPointType.FIELD_SET:
            case JoinPointType.FIELD_GET:
                cw.visitField(
                        ACC_PRIVATE + ACC_FINAL + ACC_STATIC,
                        SIGNATURE_FIELD_NAME,
                        FIELD_SIGNATURE_IMPL_CLASS_SIGNATURE,
                        null,
                        null
                );
                break;
            case JoinPointType.HANDLER:
                cw.visitField(
                        ACC_PRIVATE + ACC_FINAL + ACC_STATIC,
                        SIGNATURE_FIELD_NAME,
                        HANDLER_SIGNATURE_IMPL_CLASS_SIGNATURE,
                        null,
                        null
                );
                break;
            case JoinPointType.STATIC_INITALIZATION:
                break;
        }
        cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, META_DATA_FIELD_NAME, MAP_CLASS_SIGNATURE, null, null);
        cw.visitField(
                ACC_PRIVATE + ACC_FINAL + ACC_STATIC,
                STATIC_JOIN_POINT_INSTANCE_FIELD_NAME,
                L + joinPointClassName + SEMICOLON,
                null, null
        );
        if (requiresRtti) {
            switch (joinPointType) {
                case JoinPointType.METHOD_EXECUTION:
                case JoinPointType.METHOD_CALL:
                    cw.visitField(
                            ACC_PRIVATE,
                            RTTI_INSTANCE_FIELD_NAME,
                            METHOD_RTTI_IMPL_CLASS_SIGNATURE,
                            null,
                            null
                    );
                    break;
                case JoinPointType.CONSTRUCTOR_CALL:
                case JoinPointType.CONSTRUCTOR_EXECUTION:
                    cw.visitField(
                            ACC_PRIVATE,
                            RTTI_INSTANCE_FIELD_NAME,
                            CONSTRUCTOR_RTTI_IMPL_CLASS_SIGNATURE,
                            null,
                            null
                    );
                    break;
                case JoinPointType.FIELD_SET:
                case JoinPointType.FIELD_GET:
                    cw.visitField(
                            ACC_PRIVATE,
                            RTTI_INSTANCE_FIELD_NAME,
                            FIELD_RTTI_IMPL_CLASS_SIGNATURE,
                            null,
                            null
                    );
                    break;
                case JoinPointType.HANDLER:
                    cw.visitField(
                            ACC_PRIVATE,
                            RTTI_INSTANCE_FIELD_NAME,
                            HANDLER_RTTI_IMPL_CLASS_SIGNATURE,
                            null,
                            null
                    );
                    break;
                case JoinPointType.STATIC_INITALIZATION:
                    break;
            }
        }
        cw.visitField(ACC_PRIVATE, CALLEE_INSTANCE_FIELD_NAME, calleeClassSignature, null, null);
        cw.visitField(ACC_PRIVATE, CALLER_INSTANCE_FIELD_NAME, callerClassSignature, null, null);
        cw.visitField(ACC_PRIVATE, JoinPointCompiler.STACK_FRAME_FIELD_NAME, I, null, null);
    }

    /**
     * Creates join point specific fields.
     *
     * @param cw
     * @param joinPointType
     * @param signature     the join point signature
     * @return an array with the names of the fields
     */
    private static String[] createJoinPointSpecificFields(
            final ClassWriter cw,
            final int joinPointType,
            final String signature) {
        String[] fieldNames = null;
        switch (joinPointType) {
            case JoinPointType.METHOD_EXECUTION:
            case JoinPointType.METHOD_CALL:
            case JoinPointType.CONSTRUCTOR_CALL:
            case JoinPointType.CONSTRUCTOR_EXECUTION:
                // create the method argument fields
                Type[] argumentTypes = Type.getArgumentTypes(signature);
                fieldNames = new String[argumentTypes.length];
                for (int i = 0; i < argumentTypes.length; i++) {
                    Type argumentType = argumentTypes[i];
                    String fieldName = ARGUMENT_FIELD + i;
                    fieldNames[i] = fieldName;
                    cw.visitField(ACC_PRIVATE + ACC_FINAL, fieldName, argumentType.getDescriptor(), null, null);
                }
                break;
            case JoinPointType.FIELD_SET:
            case JoinPointType.FIELD_GET:
                // create the field argument field
                Type fieldType = Type.getType(signature);
                fieldNames = new String[1];
                String fieldName = ARGUMENT_FIELD + 0;
                fieldNames[0] = fieldName;
                cw.visitField(ACC_PRIVATE + ACC_FINAL, fieldName, fieldType.getDescriptor(), null, null);
                break;
            case JoinPointType.HANDLER:
                break;
            case JoinPointType.STATIC_INITALIZATION:
                break;
        }
        return fieldNames;
    }

    /**
     * Creates the static initializer for the join point.
     *
     * @param cw
     * @param joinPointClassName
     * @param joinPointType
     * @param joinPointHash
     * @param calleeClassName
     * @param aroundAdvices
     * @param beforeAdvices
     * @param afterFinallyAdvices
     * @param afterReturningAdvices
     * @param afterThrowingAdvices
     */
    private static void createStaticInitializer(
            final ClassWriter cw,
            final String joinPointClassName,
            final int joinPointType,
            final int joinPointHash,
            final String calleeClassName,
            final AdviceMethodInfo[] aroundAdvices,
            final AdviceMethodInfo[] beforeAdvices,
            final AdviceMethodInfo[] afterFinallyAdvices,
            final AdviceMethodInfo[] afterReturningAdvices,
            final AdviceMethodInfo[] afterThrowingAdvices) {
        CodeVisitor cv = cw.visitMethod(ACC_STATIC, CLINIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE, null, null);

        Label tryLabel = new Label();
        cv.visitLabel(tryLabel);
        cv.visitLdcInsn(calleeClassName.replace('/', '.'));
        cv.visitMethodInsn(INVOKESTATIC, CLASS_CLASS, FOR_NAME_METHOD_NAME, FOR_NAME_METHOD_SIGNATURE);
        cv.visitFieldInsn(PUTSTATIC, joinPointClassName, TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);

        Label finallyLabel = new Label();
        cv.visitLabel(finallyLabel);

        Label gotoFinallyLabel = new Label();
        cv.visitJumpInsn(GOTO, gotoFinallyLabel);

        Label catchLabel = new Label();
        cv.visitLabel(catchLabel);
        cv.visitVarInsn(ASTORE, 0);
        cv.visitTypeInsn(NEW, RUNTIME_EXCEPTION_CLASS_NAME);
        cv.visitInsn(DUP);
        cv.visitLdcInsn("could not load target class using Class.forName() in generated join point base class");

        cv.visitMethodInsn(
                INVOKESPECIAL,
                RUNTIME_EXCEPTION_CLASS_NAME,
                INIT_METHOD_NAME,
                RUNTIME_EXCEPTION_INIT_METHOD_SIGNATURE
        );

        cv.visitInsn(ATHROW);
        cv.visitLabel(gotoFinallyLabel);

        // load the system
        cv.visitFieldInsn(GETSTATIC, joinPointClassName, TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);
        cv.visitMethodInsn(
                INVOKESTATIC,
                SYSTEM_LOADER_CLASS_NAME,
                GET_SYSTEM_METHOD_NAME,
                GET_SYSTEM_METHOD_NAME_SIGNATURE
        );
        cv.visitFieldInsn(PUTSTATIC, joinPointClassName, SYSTEM_FIELD_NAME, SYSTEM_CLASS_SIGNATURE);

        // create the metadata map
        cv.visitTypeInsn(NEW, HASH_MAP_CLASS_NAME);
        cv.visitInsn(DUP);
        cv.visitMethodInsn(INVOKESPECIAL, HASH_MAP_CLASS_NAME, INIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE);
        cv.visitFieldInsn(PUTSTATIC, joinPointClassName, META_DATA_FIELD_NAME, MAP_CLASS_SIGNATURE);

        // create the Signature instance
        createSignature(joinPointType, cv, joinPointClassName, joinPointHash);

        // create the static JoinPoint instance
        cv.visitTypeInsn(NEW, joinPointClassName);
        cv.visitInsn(DUP);
        cv.visitMethodInsn(INVOKESPECIAL, joinPointClassName, INIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE);
        cv.visitFieldInsn(
                PUTSTATIC,
                joinPointClassName,
                STATIC_JOIN_POINT_INSTANCE_FIELD_NAME,
                L + joinPointClassName + SEMICOLON
        );

        // retrieve the aspect instances
        for (int i = 0; i < aroundAdvices.length; i++) {
            retrieveAspect(aroundAdvices[i], cw, AROUND_ADVICE_FIELD_PREFIX + i, cv, joinPointClassName);
        }
        for (int i = 0; i < beforeAdvices.length; i++) {
            retrieveAspect(beforeAdvices[i], cw, BEFORE_ADVICE_FIELD_PREFIX + i, cv, joinPointClassName);
        }
        for (int i = 0; i < afterFinallyAdvices.length; i++) {
            retrieveAspect(afterFinallyAdvices[i], cw, AFTER_FINALLY_ADVICE_FIELD_PREFIX + i, cv, joinPointClassName);
        }
        for (int i = 0; i < afterReturningAdvices.length; i++) {
            retrieveAspect(
                    afterReturningAdvices[i], cw, AFTER_RETURNING_ADVICE_FIELD_PREFIX + i, cv, joinPointClassName
            );
        }
        for (int i = 0; i < afterThrowingAdvices.length; i++) {
            retrieveAspect(
                    afterThrowingAdvices[i], cw, AFTER_THROWING_ADVICE_FIELD_PREFIX + i, cv, joinPointClassName
            );
        }
        cv.visitInsn(RETURN);
        cv.visitTryCatchBlock(tryLabel, finallyLabel, catchLabel, CLASS_NOT_FOUND_EXCEPTION_CLASS_NAME);
        cv.visitMaxs(0, 0);
    }

    /**
     * Creates the constructor for the join point.
     *
     * @param cw
     * @param requiresRtti
     * @param className
     * @param joinPointType
     */
    private static void createConstructor(
            final ClassWriter cw,
            final boolean requiresRtti,
            final String className,
            final int joinPointType,
            final String callerClassSignature,
            final String calleeClassSignature) {

        CodeVisitor cv = cw.visitMethod(ACC_PRIVATE, INIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE, null, null);

        cv.visitVarInsn(ALOAD, 0);
        cv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLASS_NAME, INIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE);
        cv.visitVarInsn(ALOAD, 0);
        cv.visitInsn(ICONST_M1);
        cv.visitFieldInsn(PUTFIELD, className, STACK_FRAME_FIELD_NAME, I);

        cv.visitInsn(RETURN);
        cv.visitMaxs(0, 0);
    }

    /**
     * Creates the signature for the join point.
     * <p/>
     * FIXME signature field should NOT be of type Signature but of the specific type (update all refs as well)
     *
     * @param joinPointType
     * @param cv
     * @param className
     * @param joinPointHash
     */
    private static void createSignature(
            final int joinPointType,
            final CodeVisitor cv,
            final String className,
            final int joinPointHash) {
        cv.visitFieldInsn(GETSTATIC, className, TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);
        cv.visitLdcInsn(new Integer(joinPointHash));

        switch (joinPointType) {
            case JoinPointType.METHOD_EXECUTION:
            case JoinPointType.METHOD_CALL:
                cv.visitMethodInsn(
                        INVOKESTATIC,
                        SIGNATURE_FACTORY_CLASS,
                        NEW_METHOD_SIGNATURE_METHOD_NAME,
                        NEW_METHOD_SIGNATURE_METHOD_SIGNATURE
                );
                cv.visitFieldInsn(PUTSTATIC, className, SIGNATURE_FIELD_NAME, METHOD_SIGNATURE_IMPL_CLASS_SIGNATURE);
                break;
            case JoinPointType.CONSTRUCTOR_CALL:
            case JoinPointType.CONSTRUCTOR_EXECUTION:
                cv.visitMethodInsn(
                        INVOKESTATIC,
                        SIGNATURE_FACTORY_CLASS,
                        NEW_CONSTRUCTOR_SIGNATURE_METHOD_NAME,
                        NEW_CONSTRUCTOR_SIGNATURE_METHOD_SIGNATURE
                );
                cv.visitFieldInsn(
                        PUTSTATIC, className, SIGNATURE_FIELD_NAME, CONSTRUCTOR_SIGNATURE_IMPL_CLASS_SIGNATURE
                );
                break;
            case JoinPointType.FIELD_SET:
            case JoinPointType.FIELD_GET:
                cv.visitMethodInsn(
                        INVOKESTATIC,
                        SIGNATURE_FACTORY_CLASS,
                        NEW_FIELD_SIGNATURE_METHOD_NAME,
                        NEW_FIELD_SIGNATURE_METHOD_SIGNATURE
                );
                cv.visitFieldInsn(PUTSTATIC, className, SIGNATURE_FIELD_NAME, FIELD_SIGNATURE_IMPL_CLASS_SIGNATURE);
                break;
            case JoinPointType.HANDLER:
                cv.visitMethodInsn(
                        INVOKESTATIC,
                        SIGNATURE_FACTORY_CLASS,
                        NEW_CATCH_CLAUSE_SIGNATURE_METHOD_NAME,
                        NEW_HANDLER_SIGNATURE_METHOD_SIGNATURE
                );
                cv.visitFieldInsn(PUTSTATIC, className, SIGNATURE_FIELD_NAME, HANDLER_SIGNATURE_IMPL_CLASS_SIGNATURE);
                break;
            case JoinPointType.STATIC_INITALIZATION:
                break;
        }
    }

    /**
     * Create and initialize the aspect field for a specific advice.
     *
     * @param adviceMethodInfo
     * @param cw
     * @param aspectFieldName
     * @param cv
     * @param joinPointClassName
     */
    private static boolean retrieveAspect(
            final AdviceMethodInfo adviceMethodInfo,
            final ClassWriter cw,
            final String aspectFieldName,
            final CodeVisitor cv,
            final String joinPointClassName) {

        AdviceInfo adviceInfo = adviceMethodInfo.adviceInfo;
        final CrossCuttingInfo info = adviceInfo.getAspectManager().getAspectContainer(adviceInfo.getAspectIndex())
                .getCrossCuttingInfo();
        final String aspectClassName = info.getAspectClass().getName().replace('.', '/');
        final String aspectClassSignature = L + aspectClassName + SEMICOLON;

        // add the aspect field
        cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, aspectFieldName, aspectClassSignature, null, null);

        // retrieve the aspect set it to the field
        cv.visitFieldInsn(GETSTATIC, joinPointClassName, SYSTEM_FIELD_NAME, SYSTEM_CLASS_SIGNATURE);
        cv.visitLdcInsn(info.getUuid());
        cv.visitMethodInsn(
                INVOKEVIRTUAL,
                SYSTEM_CLASS_NAME,
                GET_ASPECT_MANAGER_METHOD_NAME,
                GET_ASPECT_MANAGER_METHOD_SIGNATURE
        );
        cv.visitIntInsn(BIPUSH, adviceInfo.getAspectIndex());
        cv.visitMethodInsn(
                INVOKEVIRTUAL,
                ASPECT_MANAGER_CLASS_NAME,
                GET_ASPECT_CONTAINER_METHOD_NAME,
                GET_ASPECT_CONTAINER_METHOD_SIGNATURE
        );
        switch (info.getDeploymentModel()) {
            case DeploymentModel.PER_JVM:
                cv.visitMethodInsn(
                        INVOKEINTERFACE,
                        ASPECT_CONTAINER_CLASS_NAME,
                        GET_PER_JVM_ASPECT_METHOD_NAME,
                        GET_PER_JVM_ASPECT_METHOD_SIGNATURE
                );
                break;
            case DeploymentModel.PER_CLASS:
                cv.visitFieldInsn(GETSTATIC, joinPointClassName, TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);
                cv.visitMethodInsn(
                        INVOKEINTERFACE,
                        ASPECT_CONTAINER_CLASS_NAME,
                        GET_PER_CLASS_ASPECT_METHOD_NAME,
                        GET_PER_CLASS_ASPECT_METHOD_SIGNATURE
                );
                break;
            default:
                throw new UnsupportedOperationException(
                        "unsupported deployment model - " +
                        aspectFieldName + " " +
                        DeploymentModel.getDeploymentModelAsString(info.getDeploymentModel())
                );
        }
        cv.visitTypeInsn(CHECKCAST, aspectClassName);
        cv.visitFieldInsn(PUTSTATIC, joinPointClassName, aspectFieldName, aspectClassSignature);
        return false;
    }

    /**
     * Creates the 'invoke' method. This version delegates to the target join point directly, e.g. does not invoke the
     * 'proceed' method. Used when a join point has zero around advice.
     *
     * @param cw
     * @param requiresRtti
     * @param callerClassName
     * @param callerClassSignature
     * @param callerMethodName
     * @param callerMethodSignature
     * @param callerMethodModifiers
     * @param calleeClassName
     * @param calleeClassSignature
     * @param calleeMemberName
     * @param calleeMemberSignature
     * @param calleeMemberModifiers
     * @param joinPointClassName
     * @param joinPointType
     * @param fieldNames
     * @param joinPointSequence
     * @param aroundAdvice
     * @param beforeAdvice
     * @param afterFinallyAdvices
     * @param afterReturningAdvices
     * @param afterThrowingAdvices
     */
    private static void createInlinedInvokeMethod(
            final ClassWriter cw,
            final boolean requiresRtti,
            final String callerClassName,
            final String callerClassSignature,
            final String callerMethodName,
            final String callerMethodSignature,
            final int callerMethodModifiers,
            final String calleeClassName,
            final String calleeClassSignature,
            final String calleeMemberName,
            final String calleeMemberSignature,
            final int calleeMemberModifiers,
            final String joinPointClassName,
            final int joinPointType,
            final String[] fieldNames,
            final int joinPointSequence,
            final AdviceMethodInfo[] aroundAdvice,
            final AdviceMethodInfo[] beforeAdvice,
            final AdviceMethodInfo[] afterFinallyAdvices,
            final AdviceMethodInfo[] afterReturningAdvices,
            final AdviceMethodInfo[] afterThrowingAdvices) {

        Type[] argumentTypes = getJoinPointArgumentTypes(joinPointType, calleeMemberSignature);
        Type returnType = getJoinPointReturnType(joinPointType, calleeMemberSignature);

        final String invokeDesc = buildInvokeMethodSignature(
                argumentTypes,
                calleeClassSignature,
                callerClassSignature,
                returnType,
                calleeMemberModifiers
        );

        // create the method
        CodeVisitor cv = cw.visitMethod(
                ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
                INVOKE_METHOD_NAME,
                invokeDesc,
                new String[]{
                    THROWABLE_CLASS_NAME
                },
                null
        );

        // set CALLEE instance - StaticJP.m_callee = null or arg0 if target method is not static
        cv.visitFieldInsn(
                GETSTATIC, joinPointClassName, STATIC_JOIN_POINT_INSTANCE_FIELD_NAME,
                L + joinPointClassName + SEMICOLON
        );
        final int calleeIndex = 0;
        int argStartIndex = 0;
        if (!Modifier.isStatic(calleeMemberModifiers)) {
            cv.visitVarInsn(ALOAD, argStartIndex);
            argStartIndex++;
        } else {
            cv.visitInsn(ACONST_NULL);
        }
        final int callerIndex = argStartIndex + AsmHelper.getRegisterDepth(argumentTypes);

        cv.visitFieldInsn(PUTFIELD, joinPointClassName, CALLEE_INSTANCE_FIELD_NAME, calleeClassSignature);

        // set CALLER instance - StaticJP.m_caller = arg<last>
        cv.visitFieldInsn(
                GETSTATIC, joinPointClassName, STATIC_JOIN_POINT_INSTANCE_FIELD_NAME,
                L + joinPointClassName + SEMICOLON
        );

        cv.visitVarInsn(ALOAD, callerIndex);
        cv.visitFieldInsn(PUTFIELD, joinPointClassName, CALLER_INSTANCE_FIELD_NAME, callerClassSignature);

        createAndAddRttiInstance(
                cv, requiresRtti, STATIC_JOIN_POINT,
                joinPointType, 0, callerIndex, -1,
                calleeMemberModifiers, joinPointClassName
        );

        addParametersToRttiInstance(
                cv, requiresRtti, STATIC_JOIN_POINT,
                joinPointType, callerIndex, fieldNames,
                argStartIndex, argumentTypes, joinPointClassName
        );

        addBeforeAdviceInvocations(
                cv, STATIC_JOIN_POINT, beforeAdvice, joinPointClassName,
                argumentTypes, argStartIndex, -1
        );

        // handle different combinations of after advice (finally/throwing/returning)
        if (afterFinallyAdvices.length == 0 && afterThrowingAdvices.length == 0) {
            createPartOfInvokeMethodWithoutAfterFinallyAndAfterThrowingAdviceTypes(
                    cv, requiresRtti, STATIC_JOIN_POINT,
                    callerIndex,
                    joinPointClassName,
                    argumentTypes,
                    returnType,
                    argStartIndex,
                    joinPointType,
                    joinPointSequence,
                    calleeClassName,
                    calleeMemberName,
                    calleeMemberSignature,
                    calleeMemberModifiers,
                    afterReturningAdvices
            );
        } else if (afterThrowingAdvices.length == 0) {
            createPartOfInvokeMethodWithoutAfterThrowingAdviceTypes(
                    cv, requiresRtti, STATIC_JOIN_POINT,
                    callerIndex,
                    joinPointClassName,
                    argumentTypes,
                    returnType,
                    argStartIndex,
                    joinPointType,
                    joinPointSequence,
                    calleeClassName,
                    calleeMemberName,
                    calleeMemberSignature,
                    calleeMemberModifiers,
                    afterReturningAdvices,
                    afterFinallyAdvices
            );
        } else {
            createPartOfInvokeMethodWithAllAdviceTypes(
                    cv, requiresRtti, STATIC_JOIN_POINT,
                    callerIndex,
                    joinPointClassName,
                    argumentTypes,
                    returnType,
                    argStartIndex,
                    joinPointType,
                    joinPointSequence,
                    calleeClassName,
                    calleeMemberName,
                    calleeMemberSignature,
                    calleeMemberModifiers,
                    afterReturningAdvices,
                    afterFinallyAdvices,
                    afterThrowingAdvices
            );
        }
        cv.visitMaxs(0, 0);
    }

    /**
     * Creates the 'invoke' method. This version delegates the call to the proceed method. Used when we have at least
     * one around advice.
     *
     * @param cw
     * @param requiresRtti
     * @param callerClassName
     * @param callerClassSignature
     * @param callerMethodName
     * @param callerMethodSignature
     * @param callerMethodModifiers
     * @param calleeClassName
     * @param calleeClassSignature
     * @param calleeMemberName
     * @param calleeMemberSignature
     * @param calleeMemberModifiers
     * @param joinPointClassName
     * @param joinPointType
     * @param fieldNames
     * @param joinPointSequence
     * @param aroundAdvice
     * @param beforeAdvice
     * @param afterFinallyAdvices
     * @param afterReturningAdvices
     * @param afterThrowingAdvices
     */
    private static void createInvokeMethod(
            final ClassWriter cw,
            final boolean requiresRtti,
            final String callerClassName,
            final String callerClassSignature,
            final String callerMethodName,
            final String callerMethodSignature,
            final int callerMethodModifiers,
            final String calleeClassName,
            final String calleeClassSignature,
            final String calleeMemberName,
            final String calleeMemberSignature,
            final int calleeMemberModifiers,
            final String joinPointClassName,
            final int joinPointType,
            final String[] fieldNames,
            final int joinPointSequence,
            final AdviceMethodInfo[] aroundAdvice,
            final AdviceMethodInfo[] beforeAdvice,
            final AdviceMethodInfo[] afterFinallyAdvices,
            final AdviceMethodInfo[] afterReturningAdvices,
            final AdviceMethodInfo[] afterThrowingAdvices) {

        Type[] argumentTypes = getJoinPointArgumentTypes(joinPointType, calleeMemberSignature);
        Type returnType = getJoinPointReturnType(joinPointType, calleeMemberSignature);

        String invokeDesc = buildInvokeMethodSignature(
                argumentTypes,
                calleeClassSignature,
                callerClassSignature,
                returnType,
                calleeMemberModifiers
        );

        // create the method
        CodeVisitor cv = cw.visitMethod(
                ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
                INVOKE_METHOD_NAME,
                invokeDesc,
                new String[]{
                    THROWABLE_CLASS_NAME
                },
                null
        );

        // target arg starts at 0 or 1
        final int calleeIndex = 0;
        int argStartIndex = 0;
        if (!Modifier.isStatic(calleeMemberModifiers)) {
            argStartIndex++;
        }
        final int callerIndex = argStartIndex + AsmHelper.getRegisterDepth(argumentTypes);

        // the created jp instance will be on the register stack
        // after the target method args
        // and we keep 2 index for caller and callee instances
        final int joinPointInstanceIndex = AsmHelper.getRegisterDepth(argumentTypes) + 2;

        // only create a new join point instance per invocation (and set the params)
        // if we have at least one around advice
        createInvocationLocalJoinPointInstance(
                cv,
                joinPointClassName,
                callerClassSignature,
                calleeClassSignature,
                fieldNames,
                argumentTypes,
                argStartIndex,
                joinPointInstanceIndex
        );

        createAndAddRttiInstance(
                cv, requiresRtti, NON_STATIC_JOIN_POINT, joinPointType,
                calleeIndex, callerIndex, joinPointInstanceIndex,
                calleeMemberModifiers, joinPointClassName
        );

        addParametersToRttiInstance(
                cv, requiresRtti, NON_STATIC_JOIN_POINT,
                joinPointType, joinPointInstanceIndex, fieldNames,
                argStartIndex, argumentTypes, joinPointClassName
        );

        addBeforeAdviceInvocations(
                cv, NON_STATIC_JOIN_POINT, beforeAdvice, joinPointClassName,
                argumentTypes, argStartIndex, joinPointInstanceIndex
        );

        // handle different combinations of after advice (finally/throwing/returning)
        if (afterFinallyAdvices.length == 0 && afterThrowingAdvices.length == 0) {
            createPartOfInvokeMethodWithoutAfterFinallyAndAfterThrowingAdviceTypes(
                    cv, requiresRtti, NON_STATIC_JOIN_POINT,
                    joinPointInstanceIndex, joinPointClassName,
                    argumentTypes, returnType, argStartIndex,
                    0, 0, null, null, null, 0,
                    afterReturningAdvices
            );
        } else if (afterThrowingAdvices.length == 0) {
            createPartOfInvokeMethodWithoutAfterThrowingAdviceTypes(
                    cv, requiresRtti, NON_STATIC_JOIN_POINT,
                    joinPointInstanceIndex, joinPointClassName,
                    argumentTypes, returnType, argStartIndex,
                    0, 0, null, null, null, 0,
                    afterReturningAdvices, afterFinallyAdvices
            );
        } else {
            createPartOfInvokeMethodWithAllAdviceTypes(
                    cv, requiresRtti, NON_STATIC_JOIN_POINT,
                    joinPointInstanceIndex, joinPointClassName,
                    argumentTypes, returnType, argStartIndex,
                    0, 0, null, null, null, 0,
                    afterReturningAdvices, afterFinallyAdvices,
                    afterThrowingAdvices
            );
        }
        cv.visitMaxs(0, 0);
    }

    /**
     * Creates a RTTI instance and adds it to the JoinPoint instance (RTTI member field).
     *
     * @param cv
     * @param requiresRtti
     * @param isStaticJoinPoint
     * @param joinPointType
     * @param calleeIndex
     * @param callerIndex
     * @param joinPointInstanceIndex
     * @param calleeMemberModifiers
     * @param joinPointClassName
     */
    private static void createAndAddRttiInstance(
            final CodeVisitor cv,
            final boolean requiresRtti,
            final boolean isStaticJoinPoint,
            final int joinPointType,
            final int calleeIndex,
            final int callerIndex,
            final int joinPointInstanceIndex,
            final int calleeMemberModifiers,
            final String joinPointClassName) {
        if (requiresRtti) {
            switch (joinPointType) {
                case JoinPointType.METHOD_EXECUTION:
                case JoinPointType.METHOD_CALL:
                    createAndAddRttiInstance(
                            cv, isStaticJoinPoint,
                            calleeIndex, callerIndex, joinPointInstanceIndex,
                            calleeMemberModifiers, joinPointClassName,
                            METHOD_RTTI_IMPL_CLASS_NAME,
                            METHOD_RTTI_IMPL_CLASS_SIGNATURE,
                            METHOD_RTTI_IMPL_INIT_SIGNATURE,
                            METHOD_SIGNATURE_IMPL_CLASS_SIGNATURE
                    );
                    break;
                case JoinPointType.CONSTRUCTOR_CALL:
                case JoinPointType.CONSTRUCTOR_EXECUTION:
                    createAndAddRttiInstance(
                            cv, isStaticJoinPoint,
                            calleeIndex, callerIndex, joinPointInstanceIndex,
                            calleeMemberModifiers, joinPointClassName,
                            CONSTRUCTOR_RTTI_IMPL_CLASS_NAME,
                            CONSTRUCTOR_RTTI_IMPL_CLASS_SIGNATURE,
                            CONSTRUCTOR_RTTI_IMPL_INIT_SIGNATURE,
                            CONSTRUCTOR_SIGNATURE_IMPL_CLASS_SIGNATURE
                    );
                    break;
                case JoinPointType.FIELD_SET:
                case JoinPointType.FIELD_GET:
                    createAndAddRttiInstance(
                            cv, isStaticJoinPoint,
                            calleeIndex, callerIndex, joinPointInstanceIndex,
                            calleeMemberModifiers, joinPointClassName,
                            FIELD_RTTI_IMPL_CLASS_NAME,
                            FIELD_RTTI_IMPL_CLASS_SIGNATURE,
                            FIELD_RTTI_IMPL_INIT_SIGNATURE,
                            FIELD_SIGNATURE_IMPL_CLASS_SIGNATURE
                    );
                    break;
                case JoinPointType.HANDLER:
                    createAndAddRttiInstance(
                            cv, isStaticJoinPoint,
                            calleeIndex, callerIndex, joinPointInstanceIndex,
                            calleeMemberModifiers, joinPointClassName,
                            HANDLER_RTTI_IMPL_CLASS_NAME,
                            HANDLER_RTTI_IMPL_CLASS_SIGNATURE,
                            HANDLER_RTTI_IMPL_INIT_SIGNATURE,
                            HANDLER_SIGNATURE_IMPL_CLASS_SIGNATURE
                    );
                    break;
                case JoinPointType.STATIC_INITALIZATION:
                    break;
            }
        }
    }

    /**
     * Creates a RTTI instance and adds it to the JoinPoint instance (RTTI member field).
     *
     * @param cv
     * @param isStaticJoinPoint
     * @param calleeIndex
     * @param callerIndex
     * @param joinPointInstanceIndex
     * @param calleeMemberModifiers
     * @param joinPointClassName
     * @param rttiImplClassName
     * @param rttiImplClassSignature
     * @param rttiImplInitSignature
     * @param signatureImplClassSignature
     */
    private static void createAndAddRttiInstance(
            final CodeVisitor cv,
            final boolean isStaticJoinPoint,
            final int calleeIndex,
            final int callerIndex,
            final int joinPointInstanceIndex,
            final int calleeMemberModifiers,
            final String joinPointClassName,
            final String rttiImplClassName,
            final String rttiImplClassSignature,
            final String rttiImplInitSignature,
            final String signatureImplClassSignature) {
        if (isStaticJoinPoint) {
            cv.visitFieldInsn(
                    GETSTATIC, joinPointClassName,
                    STATIC_JOIN_POINT_INSTANCE_FIELD_NAME,
                    L + joinPointClassName + SEMICOLON
            );
        } else {
            cv.visitVarInsn(ALOAD, joinPointInstanceIndex);
        }
        cv.visitTypeInsn(NEW, rttiImplClassName);
        cv.visitInsn(DUP);
        cv.visitFieldInsn(GETSTATIC, joinPointClassName, SIGNATURE_FIELD_NAME, signatureImplClassSignature);
//        cv.visitTypeInsn(CHECKCAST, signatureImplClassName);
        cv.visitVarInsn(ALOAD, callerIndex);
        if (!Modifier.isStatic(calleeMemberModifiers)) {
            cv.visitVarInsn(ALOAD, calleeIndex);
        } else {
            cv.visitInsn(ACONST_NULL);
        }
        cv.visitMethodInsn(
                INVOKESPECIAL,
                rttiImplClassName,
                INIT_METHOD_NAME,
                rttiImplInitSignature
        );
        cv.visitFieldInsn(PUTFIELD, joinPointClassName, RTTI_INSTANCE_FIELD_NAME, rttiImplClassSignature);
    }

    /**
     * @param cv
     * @param requiresRtti
     * @param isStaticJoinPoint
     * @param joinPointInstanceIndex
     * @param joinPointClassName
     * @param argumentTypes
     * @param returnType
     * @param argStartIndex
     * @param joinPointType
     * @param joinPointSequence
     * @param calleeClassName
     * @param calleeMemberName
     * @param calleeMemberSignature
     * @param calleeMemberModifiers
     * @param afterReturningAdvices
     * @param afterFinallyAdvices
     * @param afterThrowingAdvices
     */
    private static void createPartOfInvokeMethodWithAllAdviceTypes(
            final CodeVisitor cv,
            final boolean requiresRtti,
            final boolean isStaticJoinPoint,
            final int joinPointInstanceIndex,
            final String joinPointClassName,
            final Type[] argumentTypes,
            final Type returnType,
            final int argStartIndex,
            final int joinPointType,
            final int joinPointSequence,
            final String calleeClassName,
            final String calleeMemberName,
            final String calleeMemberSignature,
            final int calleeMemberModifiers,
            final AdviceMethodInfo[] afterReturningAdvices,
            final AdviceMethodInfo[] afterFinallyAdvices,
            final AdviceMethodInfo[] afterThrowingAdvices) {

        final int returnValueIndex = joinPointInstanceIndex + 1;
        final int exceptionIndex1 = joinPointInstanceIndex + 2;
        final int exceptionIndex2 = joinPointInstanceIndex + 3;

        cv.visitInsn(ACONST_NULL);
        cv.visitVarInsn(ASTORE, returnValueIndex);

        Label tryLabel = new Label();
        cv.visitLabel(tryLabel);
        if (isStaticJoinPoint) {
            // if no around advice then optimize by invoking the target JP directly and no call to proceed()
            createInlinedJoinPointInvocation(
                    cv,
                    joinPointType,
                    joinPointSequence,
                    calleeClassName,
                    calleeMemberName,
                    calleeMemberSignature,
                    calleeMemberModifiers,
                    argumentTypes,
                    argStartIndex
            );
        } else {
            createInvocationToProceedMethod(cv, joinPointClassName, joinPointInstanceIndex, returnValueIndex);
        }

        addReturnValueToRttiInstance(
                cv, requiresRtti, isStaticJoinPoint, joinPointType, returnValueIndex,
                returnType, joinPointInstanceIndex, joinPointClassName
        );

        // after returning advice invocations
        addAfterReturningAdviceInvocations(
                cv, isStaticJoinPoint, returnType, afterReturningAdvices, joinPointClassName, argumentTypes,
                argStartIndex, joinPointInstanceIndex
        );

        Label finallyLabel1 = new Label();
        cv.visitLabel(finallyLabel1);
        addAfterFinallyAdviceInvocations(
                cv, isStaticJoinPoint, afterFinallyAdvices, joinPointClassName, argumentTypes,
                argStartIndex, joinPointInstanceIndex
        );

        Label gotoFinallyLabel = new Label();
        cv.visitJumpInsn(GOTO, gotoFinallyLabel);
        Label catchLabel = new Label();
        cv.visitLabel(catchLabel);

        // loop over the after throwing advices
        Label lastIfInstanceOfLabel = new Label();
        for (int i = afterThrowingAdvices.length - 1; i >= 0; i--) {
            AdviceMethodInfo advice = afterThrowingAdvices[i];

            // if (e instanceof TYPE) {...}
            cv.visitVarInsn(ASTORE, exceptionIndex1);
            cv.visitVarInsn(ALOAD, exceptionIndex1);
            cv.visitTypeInsn(INSTANCEOF, advice.specialArgumentType);
            Label ifInstanceOfLabel;
            if (i == afterThrowingAdvices.length - 1) {
                ifInstanceOfLabel = lastIfInstanceOfLabel;
            } else {
                ifInstanceOfLabel = new Label();
            }
            cv.visitJumpInsn(IFEQ, ifInstanceOfLabel);

            // after throwing advice invocation
            addAfterAdviceInvocation(
                    cv, isStaticJoinPoint, advice, advice.adviceMethod, advice.aspectFieldName,
                    advice.aspectClassName, advice.aspectClassSignature, joinPointClassName,
                    joinPointInstanceIndex, argumentTypes, argStartIndex
            );
            cv.visitLabel(ifInstanceOfLabel);
            addAfterFinallyAdviceInvocations(
                    cv, isStaticJoinPoint, afterFinallyAdvices, joinPointClassName, argumentTypes,
                    argStartIndex, joinPointInstanceIndex
            );
        }

        // rethrow exception
        cv.visitVarInsn(ALOAD, exceptionIndex1);
        cv.visitInsn(ATHROW);

        // store exception
        Label exceptionLabel = new Label();
        cv.visitLabel(exceptionLabel);
        cv.visitVarInsn(ASTORE, exceptionIndex2);

        // after finally advice invocation
        Label finallyLabel2 = new Label();
        cv.visitLabel(finallyLabel2);
        addAfterFinallyAdviceInvocations(
                cv, isStaticJoinPoint, afterFinallyAdvices, joinPointClassName, argumentTypes,
                argStartIndex, joinPointInstanceIndex
        );

        // rethrow exception
        cv.visitVarInsn(ALOAD, exceptionIndex2);
        cv.visitInsn(ATHROW);
        cv.visitLabel(gotoFinallyLabel);

        if (!isStaticJoinPoint) {
            // unwrap and return
            if (returnType.getSort() != Type.VOID) {
                cv.visitVarInsn(ALOAD, returnValueIndex);
                AsmHelper.unwrapType(cv, returnType);
            }
        }

        AsmHelper.addReturnStatement(cv, returnType);
        cv.visitTryCatchBlock(tryLabel, finallyLabel1, catchLabel, THROWABLE_CLASS_NAME);
        cv.visitTryCatchBlock(tryLabel, finallyLabel1, exceptionLabel, null);
        cv.visitTryCatchBlock(catchLabel, lastIfInstanceOfLabel, exceptionLabel, null);
        cv.visitTryCatchBlock(exceptionLabel, finallyLabel2, exceptionLabel, null);
    }

    /**
     * @param cv
     * @param requiresRtti
     * @param isStaticJoinPoint
     * @param joinPointInstanceIndex
     * @param joinPointClassName
     * @param argumentTypes
     * @param returnType
     * @param argStartIndex
     * @param joinPointType
     * @param joinPointSequence
     * @param calleeClassName
     * @param calleeMemberName
     * @param calleeMemberSignature
     * @param calleeMemberModifiers
     * @param afterReturningAdvices
     * @param afterFinallyAdvices
     */
    private static void createPartOfInvokeMethodWithoutAfterThrowingAdviceTypes(
            final CodeVisitor cv,
            final boolean requiresRtti,
            final boolean isStaticJoinPoint,
            final int joinPointInstanceIndex,
            final String joinPointClassName,
            final Type[] argumentTypes,
            final Type returnType,
            final int argStartIndex,
            final int joinPointType,
            final int joinPointSequence,
            final String calleeClassName,
            final String calleeMemberName,
            final String calleeMemberSignature,
            final int calleeMemberModifiers,
            final AdviceMethodInfo[] afterReturningAdvices,
            final AdviceMethodInfo[] afterFinallyAdvices) {

        final int returnValueIndex = joinPointInstanceIndex + 1;
        final int exceptionIndex = joinPointInstanceIndex + 2;

        cv.visitInsn(ACONST_NULL);
        cv.visitVarInsn(ASTORE, returnValueIndex);

        Label tryLabel = new Label();
        cv.visitLabel(tryLabel);
        if (isStaticJoinPoint) {
            // if no around advice then optimize by invoking the target JP directly and no call to proceed()
            createInlinedJoinPointInvocation(
                    cv,
                    joinPointType,
                    joinPointSequence,
                    calleeClassName,
                    calleeMemberName,
                    calleeMemberSignature,
                    calleeMemberModifiers,
                    argumentTypes,
                    argStartIndex
            );
        } else {
            createInvocationToProceedMethod(cv, joinPointClassName, joinPointInstanceIndex, returnValueIndex);
        }

        addReturnValueToRttiInstance(
                cv, requiresRtti, isStaticJoinPoint, joinPointType, returnValueIndex,
                returnType, joinPointInstanceIndex, joinPointClassName
        );

        addAfterReturningAdviceInvocations(
                cv, isStaticJoinPoint, returnType, afterReturningAdvices, joinPointClassName,
                argumentTypes, argStartIndex, joinPointInstanceIndex
        );

        Label finallyLabel1 = new Label();
        cv.visitLabel(finallyLabel1);
        addAfterFinallyAdviceInvocations(
                cv, isStaticJoinPoint, afterFinallyAdvices, joinPointClassName,
                argumentTypes, argStartIndex, joinPointInstanceIndex
        );

        Label gotoFinallyLabel = new Label();
        cv.visitJumpInsn(GOTO, gotoFinallyLabel);

        Label exceptionLabel = new Label();
        cv.visitLabel(exceptionLabel);
        cv.visitVarInsn(ASTORE, exceptionIndex);

        Label finallyLabel2 = new Label();
        cv.visitLabel(finallyLabel2);

        addAfterFinallyAdviceInvocations(
                cv, isStaticJoinPoint, afterFinallyAdvices, joinPointClassName,
                argumentTypes, argStartIndex, joinPointInstanceIndex
        );

        cv.visitVarInsn(ALOAD, exceptionIndex);
        cv.visitInsn(ATHROW);

        cv.visitLabel(gotoFinallyLabel);

        if (!isStaticJoinPoint) {
            // unwrap and return
            if (returnType.getSort() != Type.VOID) {
                cv.visitVarInsn(ALOAD, returnValueIndex);
                AsmHelper.unwrapType(cv, returnType);
            }
        }

        AsmHelper.addReturnStatement(cv, returnType);
        cv.visitTryCatchBlock(tryLabel, finallyLabel1, exceptionLabel, null);
        cv.visitTryCatchBlock(exceptionLabel, finallyLabel2, exceptionLabel, null);
    }

    /**
     * @param cv
     * @param requiresRtti
     * @param isStaticJoinPoint
     * @param joinPointInstanceIndex
     * @param joinPointClassName
     * @param argumentTypes
     * @param returnType
     * @param joinPointType
     * @param joinPointSequence
     * @param calleeClassName
     * @param calleeMemberName
     * @param calleeMemberSignature
     * @param calleeMemberModifiers
     * @param argStartIndex
     * @param afterReturningAdvices
     */
    private static void createPartOfInvokeMethodWithoutAfterFinallyAndAfterThrowingAdviceTypes(
            final CodeVisitor cv,
            final boolean requiresRtti,
            final boolean isStaticJoinPoint,
            final int joinPointInstanceIndex,
            final String joinPointClassName,
            final Type[] argumentTypes,
            final Type returnType,
            final int argStartIndex,
            final int joinPointType,
            final int joinPointSequence,
            final String calleeClassName,
            final String calleeMemberName,
            final String calleeMemberSignature,
            final int calleeMemberModifiers,
            final AdviceMethodInfo[] afterReturningAdvices) {

        final int returnValueIndex = joinPointInstanceIndex + 1;
        if (isStaticJoinPoint) {
            // if no around advice then optimize by invoking the target JP directly and no call to proceed()
            createInlinedJoinPointInvocation(
                    cv,
                    joinPointType,
                    joinPointSequence,
                    calleeClassName,
                    calleeMemberName,
                    calleeMemberSignature,
                    calleeMemberModifiers,
                    argumentTypes,
                    argStartIndex
            );
        } else {
            createInvocationToProceedMethod(cv, joinPointClassName, joinPointInstanceIndex, returnValueIndex);
        }

        addReturnValueToRttiInstance(
                cv, requiresRtti, isStaticJoinPoint, joinPointType, returnValueIndex,
                returnType, joinPointInstanceIndex, joinPointClassName
        );

        // after returning advice invocations
        addAfterReturningAdviceInvocations(
                cv, isStaticJoinPoint, returnType, afterReturningAdvices, joinPointClassName,
                argumentTypes, argStartIndex, joinPointInstanceIndex
        );

        if (!isStaticJoinPoint) {
            // unwrap and return
            if (returnType.getSort() != Type.VOID) {
                cv.visitVarInsn(ALOAD, returnValueIndex);
                AsmHelper.unwrapType(cv, returnType);
            }
        }
        AsmHelper.addReturnStatement(cv, returnType);
    }

    /**
     * Creates an invocation to the proceed method.
     *
     * @param cv
     * @param joinPointClassName
     * @param joinPointInstanceIndex
     * @param returnValueIndex
     */
    private static void createInvocationToProceedMethod(
            final CodeVisitor cv,
            final String joinPointClassName,
            final int joinPointInstanceIndex,
            final int returnValueIndex) {
        cv.visitVarInsn(ALOAD, joinPointInstanceIndex);
        cv.visitMethodInsn(INVOKEVIRTUAL, joinPointClassName, PROCEED_METHOD_NAME, PROCEED_METHOD_SIGNATURE);
        cv.visitVarInsn(ASTORE, returnValueIndex);
    }

    /**
     * Creates an "invocation local" join point instance, e.g. one join point per invocation. Needed for thread-safety
     * when invoking around advice.
     *
     * @param cv
     * @param joinPointClassName
     * @param callerClassSignature
     * @param calleeClassSignature
     * @param fieldNames
     * @param argumentTypes
     * @param argStartIndex          index on stack of first target method arg (0 or 1, depends of static target or
     *                               not)
     * @param joinPointInstanceIndex
     */
    private static void createInvocationLocalJoinPointInstance(
            final CodeVisitor cv,
            final String joinPointClassName,
            final String callerClassSignature,
            final String calleeClassSignature,
            final String[] fieldNames,
            final Type[] argumentTypes,
            final int argStartIndex,
            final int joinPointInstanceIndex) {
        // create the join point instance
        cv.visitTypeInsn(NEW, joinPointClassName);
        cv.visitInsn(DUP);
        cv.visitMethodInsn(INVOKESPECIAL, joinPointClassName, INIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE);

        // store the jp on the stack
        cv.visitVarInsn(ASTORE, joinPointInstanceIndex);

        // affect the target method arg to the jp (jp.m_arg<i> = <arg_i>)
        int argStackIndex = argStartIndex;
        for (int i = 0; i < fieldNames.length; i++) {
            String fieldName = fieldNames[i];
            cv.visitVarInsn(ALOAD, joinPointInstanceIndex);
            Type type = argumentTypes[i];
            argStackIndex = AsmHelper.loadType(cv, argStackIndex, type);
            cv.visitFieldInsn(PUTFIELD, joinPointClassName, fieldName, type.getDescriptor());
        }

        // caller is arg<last>
        cv.visitVarInsn(ALOAD, joinPointInstanceIndex);
        cv.visitVarInsn(ALOAD, argStackIndex++);
        cv.visitFieldInsn(PUTFIELD, joinPointClassName, CALLER_INSTANCE_FIELD_NAME, callerClassSignature);

        // callee is arg0 or null
        cv.visitVarInsn(ALOAD, joinPointInstanceIndex);
        if (argStartIndex > 0) {
            cv.visitVarInsn(ALOAD, 0);
        } else {
            cv.visitInsn(ACONST_NULL);
        }
        cv.visitFieldInsn(PUTFIELD, joinPointClassName, CALLEE_INSTANCE_FIELD_NAME, calleeClassSignature);
    }

    /**
     * Create the proceed() method.
     *
     * @param cw
     * @param requiresRtti
     * @param calleeClassName
     * @param calleeClassSignature
     * @param calleeMemberName
     * @param calleeMemberSignature
     * @param calleeMemberModifiers
     * @param joinPointClassName
     * @param joinPointType
     * @param fieldNames
     * @param joinPointSequence
     * @param aroundAdvice
     * @param signatureCflowExprStruct
     */
    private static void createProceedMethod(
            final ClassWriter cw,
            final boolean requiresRtti,
            final String calleeClassName,
            final String calleeClassSignature,
            final String calleeMemberName,
            final String calleeMemberSignature,
            final int calleeMemberModifiers,
            final String joinPointClassName,
            final int joinPointType,
            final String[] fieldNames,
            final int joinPointSequence,
            final AdviceMethodInfo[] aroundAdvice,
            final JoinPointInfo signatureCflowExprStruct) {
        CodeVisitor cv = cw.visitMethod(
                ACC_PUBLIC | ACC_FINAL,
                PROCEED_METHOD_NAME,
                PROCEED_METHOD_SIGNATURE,
                new String[]{
                    THROWABLE_CLASS_NAME
                },
                null
        );
        Type[] argumentTypes = getJoinPointArgumentTypes(joinPointType, calleeMemberSignature);
        incrementStackFrameCounter(cv, joinPointClassName);

        // set up the labels
        Label tryLabel = new Label();
        Label defaultCaseLabel = new Label();
        Label gotoLabel = new Label();
        Label handlerLabel = new Label();
        Label endLabel = new Label();
        int nrOfCases = aroundAdvice.length;
        Label[] caseLabels = new Label[nrOfCases];
        Label[] returnLabels = new Label[nrOfCases];
        int[] caseNumbers = new int[nrOfCases];
        for (int i = 0; i < caseLabels.length; i++) {
            caseLabels[i] = new Label();
            caseNumbers[i] = i;
        }
        for (int i = 0; i < returnLabels.length; i++) {
            returnLabels[i] = new Label();
        }

        // start try-catch block
        cv.visitLabel(tryLabel);

        // start the switch block and set the stackframe as the param to the switch
        cv.visitVarInsn(ALOAD, 0);
        cv.visitFieldInsn(GETFIELD, joinPointClassName, STACK_FRAME_FIELD_NAME, I);
        cv.visitLookupSwitchInsn(defaultCaseLabel, caseNumbers, caseLabels);

        // add one case for each around advice invocation
        for (int i = 0; i < aroundAdvice.length; i++) {
            cv.visitLabel(caseLabels[i]);

            // gather advice info
            AdviceMethodInfo adviceInfo = aroundAdvice[i];

            // get the aspect instance
            cv.visitFieldInsn(
                    GETSTATIC, joinPointClassName,
                    AROUND_ADVICE_FIELD_PREFIX + i,
                    adviceInfo.aspectClassSignature
            );

            // load the arguments to the advice from the join point instance plus build up the advice method signature
            int[] argIndexes = adviceInfo.adviceMethodArgIndexes;
            // if empty, we consider for now that we have to push JoinPoint for old advice with JoinPoint as sole arg
            if (argIndexes.length <= 0) {
                cv.visitVarInsn(ALOAD, 0);
            }
            for (int j = 0; j < argIndexes.length; j++) {
                int argIndex = argIndexes[j];
                if (argIndex != -1) {
                    Type argumentType = argumentTypes[argIndex];
                    cv.visitVarInsn(ALOAD, 0);
                    cv.visitFieldInsn(
                            GETFIELD,
                            joinPointClassName,
                            ARGUMENT_FIELD + argIndex,
                            argumentType.getDescriptor()
                    );
                } else {
                    cv.visitVarInsn(ALOAD, 0);
                }
            }

            // invoke the advice method
            cv.visitMethodInsn(
                    INVOKEVIRTUAL,
                    adviceInfo.aspectClassName,
                    adviceInfo.adviceMethod.getName(),
                    Type.getMethodDescriptor(adviceInfo.adviceMethod)
            );
            cv.visitVarInsn(ASTORE, 1);
            cv.visitLabel(returnLabels[i]);
            decrementStackFrameCounter(cv, joinPointClassName);
            cv.visitVarInsn(ALOAD, 1);
            cv.visitInsn(ARETURN);
        }

        // invoke the target join point in the default case
        cv.visitLabel(defaultCaseLabel);

        AsmHelper.prepareWrappingOfPrimitiveType(cv, Type.getReturnType(calleeMemberSignature));

        createJoinPointInvocation(
                cv,
                joinPointType,
                calleeMemberName,
                joinPointSequence,
                calleeMemberSignature,
                calleeMemberModifiers,
                calleeClassName,
                calleeClassSignature,
                joinPointClassName,
                fieldNames
        );

        final Type returnType = Type.getReturnType(calleeMemberSignature);
        AsmHelper.wrapPrimitiveType(cv, returnType);
        cv.visitVarInsn(ASTORE, 1);
        cv.visitLabel(gotoLabel);

        decrementStackFrameCounter(cv, joinPointClassName);

        if (requiresRtti) {
            // add the return value to the RTTI instance
            if (joinPointType == JoinPointType.METHOD_EXECUTION || joinPointType == JoinPointType.METHOD_CALL) {
                if (returnType.getSort() != Type.VOID) {
                    cv.visitVarInsn(ALOAD, 0);
                    cv.visitFieldInsn(
                            GETFIELD, joinPointClassName,
                            RTTI_INSTANCE_FIELD_NAME, METHOD_RTTI_IMPL_CLASS_SIGNATURE
                    );
                    cv.visitVarInsn(ALOAD, 1);
                    cv.visitMethodInsn(
                            INVOKEVIRTUAL, METHOD_RTTI_IMPL_CLASS_NAME,
                            SET_RETURN_VALUE_METHOD_NAME, SET_RETURN_VALUE_METHOD_SIGNATURE
                    );
                }
            }
        }

        cv.visitVarInsn(ALOAD, 1);
        cv.visitInsn(ARETURN);

        // finally clause
        cv.visitLabel(handlerLabel);
        cv.visitVarInsn(ASTORE, 2);
        cv.visitLabel(endLabel);
        decrementStackFrameCounter(cv, joinPointClassName);
        cv.visitVarInsn(ALOAD, 2);
        cv.visitInsn(ATHROW);

        // set up the label table
        cv.visitTryCatchBlock(tryLabel, returnLabels[0], handlerLabel, null);
        for (int i = 1; i < caseLabels.length; i++) {
            Label caseLabel = caseLabels[i];
            Label returnLabel = returnLabels[i];
            cv.visitTryCatchBlock(caseLabel, returnLabel, handlerLabel, null);
        }
        cv.visitTryCatchBlock(defaultCaseLabel, gotoLabel, handlerLabel, null);
        cv.visitTryCatchBlock(handlerLabel, endLabel, handlerLabel, null);
        cv.visitMaxs(0, 0);
    }

    /**
     * Creates the deepCopy method.
     *
     * @param cw
     * @param joinPointClassName
     */
    private static void createDeepCopyMethod(
            final ClassWriter cw,
            final String joinPointClassName,
            final String[] fieldNames,
            final Type[] argumentTypes,
            final String callerClassName,
            final String calleeClassName) {

        CodeVisitor cv = cw.visitMethod(ACC_PUBLIC, DEEP_COPY_METHOD_NAME, DEEP_COPY_METHOD_SIGNATURE, null, null);

        // create a new join point instance
        cv.visitTypeInsn(NEW, joinPointClassName);
        cv.visitInsn(DUP);
        int joinPointCloneIndex = 1;
        cv.visitMethodInsn(INVOKESPECIAL, joinPointClassName, INIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE);
        cv.visitVarInsn(ASTORE, joinPointCloneIndex);

        // set stack frame index
        cv.visitVarInsn(ALOAD, joinPointCloneIndex);
        cv.visitVarInsn(ALOAD, 0);
        cv.visitFieldInsn(GETFIELD, joinPointClassName, STACK_FRAME_FIELD_NAME, I);
        cv.visitFieldInsn(PUTFIELD, joinPointClassName, STACK_FRAME_FIELD_NAME, I);

        // set callee
        cv.visitVarInsn(ALOAD, joinPointCloneIndex);
        cv.visitVarInsn(ALOAD, 0);
        cv.visitFieldInsn(GETFIELD, joinPointClassName, CALLEE_INSTANCE_FIELD_NAME, calleeClassName);
        cv.visitFieldInsn(PUTFIELD, joinPointClassName, CALLEE_INSTANCE_FIELD_NAME, calleeClassName);

        // set caller
        cv.visitVarInsn(ALOAD, joinPointCloneIndex);
        cv.visitVarInsn(ALOAD, 0);
        cv.visitFieldInsn(GETFIELD, joinPointClassName, CALLER_INSTANCE_FIELD_NAME, callerClassName);
        cv.visitFieldInsn(PUTFIELD, joinPointClassName, CALLER_INSTANCE_FIELD_NAME, callerClassName);

        // set the arguments
        for (int i = 0; i < fieldNames.length; i++) {
            String fieldName = fieldNames[i];
            cv.visitVarInsn(ALOAD, joinPointCloneIndex);
            cv.visitVarInsn(ALOAD, 0);
            cv.visitFieldInsn(GETFIELD, joinPointClassName, fieldName, argumentTypes[i].toString());
            cv.visitFieldInsn(PUTFIELD, joinPointClassName, fieldName, argumentTypes[i].toString());
        }

        // FIXME handle the RTTI, create a NEW RTTI instance, DO NOT reference old one

        cv.visitVarInsn(ALOAD, joinPointCloneIndex);
        cv.visitInsn(ARETURN);
        cv.visitMaxs(0, 0);
    }

    /**
     * Adds before advice invocations.
     *
     * @param cv
     * @param isStaticJoinPoint
     * @param advices
     * @param joinPointClassName
     * @param argumentTypes
     * @param argStartIndex          index on stack of first target method arg (0 or 1, depends of static target or
     *                               not)
     * @param joinPointInstanceIndex >= 0 if STATIC_JOIN_POINT is NOT to be used (around advice)
     */
    private static void addBeforeAdviceInvocations(
            final CodeVisitor cv,
            final boolean isStaticJoinPoint,
            final AdviceMethodInfo[] advices,
            final String joinPointClassName,
            final Type[] argumentTypes,
            final int argStartIndex,
            final int joinPointInstanceIndex) {
        for (int i = 0; i < advices.length; i++) {
            AdviceMethodInfo advice = advices[i];

            //get the aspect instance
            cv.visitFieldInsn(GETSTATIC, joinPointClassName, advice.aspectFieldName, advice.aspectClassSignature);

            // load the arguments that should be passed to the advice
            int[] argIndexes = advice.adviceMethodArgIndexes;
            // if empty, we consider for now that we have to push JoinPoint for old advice with JoinPoint as sole arg
            if (argIndexes.length == 0) {
                loadJoinPointInstance(cv, isStaticJoinPoint, joinPointInstanceIndex, joinPointClassName);
            }
            for (int j = 0; j < argIndexes.length; j++) {
                int argIndex = argIndexes[j];
                if (argIndex != -1) {
                    Type argumentType = argumentTypes[argIndex];
                    int argStackIndex = AsmHelper.getRegisterIndexOf(argumentTypes, argIndex) + argStartIndex;
                    AsmHelper.loadType(cv, argStackIndex, argumentType);
                } else {
                    loadJoinPointInstance(cv, isStaticJoinPoint, joinPointInstanceIndex, joinPointClassName);
                }
            }
            cv.visitMethodInsn(
                    INVOKEVIRTUAL,
                    advice.aspectClassName,
                    advice.adviceMethod.getName(),
                    Type.getMethodDescriptor(advice.adviceMethod)
            );
        }
    }

    /**
     * Adds after advice invocations.
     *
     * @param cv
     * @param isStaticJoinPoint
     * @param advices
     * @param joinPointClassName
     * @param argumentTypes
     * @param argStartIndex          index on stack of first target method arg (0 or 1, depends of static target or
     *                               not)
     * @param joinPointInstanceIndex >= 0 if STATIC_JOIN_POINT is NOT to be used (around advice)
     */
    private static void addAfterFinallyAdviceInvocations(
            final CodeVisitor cv,
            final boolean isStaticJoinPoint,
            final AdviceMethodInfo[] advices,
            final String joinPointClassName,
            final Type[] argumentTypes,
            final int argStartIndex,
            final int joinPointInstanceIndex) {
        // add after advice in reverse order
        for (int i = advices.length - 1; i >= 0; i--) {
            AdviceMethodInfo advice = advices[i];
            addAfterAdviceInvocation(
                    cv, isStaticJoinPoint, advice, advice.adviceMethod, advice.aspectFieldName,
                    advice.aspectClassName, advice.aspectClassSignature, joinPointClassName,
                    joinPointInstanceIndex, argumentTypes, argStartIndex
            );
        }
    }

    /**
     * Adds after returning advice invocations.
     *
     * @param cv
     * @param isStaticJoinPoint
     * @param returnType
     * @param adviceInfos
     * @param joinPointClassName
     * @param argumentTypes
     * @param argStartIndex          index on stack of first target method arg (0 or 1, depends of static target or
     *                               not)
     * @param joinPointInstanceIndex >= 0 if STATIC_JOIN_POINT is NOT to be used (around advice)
     */
    private static void addAfterReturningAdviceInvocations(
            final CodeVisitor cv,
            final boolean isStaticJoinPoint,
            final Type returnType,
            final AdviceMethodInfo[] adviceInfos,
            final String joinPointClassName,
            final Type[] argumentTypes,
            final int argStartIndex,
            final int joinPointInstanceIndex) {
        final int returnValueIndex = joinPointInstanceIndex + 1;
        boolean hasPoppedReturnValueFromStack = false;
        for (int i = adviceInfos.length - 1; i >= 0; i--) {
            AdviceMethodInfo advice = adviceInfos[i];
            String specialArgTypeDesc = advice.specialArgumentType;
            if (AsmHelper.isPrimitive(returnType)) {
                if (specialArgTypeDesc.equals(returnType.getDescriptor())) {
                    addAfterAdviceInvocation(
                            cv, isStaticJoinPoint, advice, advice.adviceMethod, advice.aspectFieldName,
                            advice.aspectClassName, advice.aspectClassSignature, joinPointClassName,
                            joinPointInstanceIndex, argumentTypes, argStartIndex
                    );
                }
            } else {
                // need the return value in instanceof operation
                if (isStaticJoinPoint && !hasPoppedReturnValueFromStack) {
                    AsmHelper.storeType(cv, returnValueIndex, returnType);
                    hasPoppedReturnValueFromStack = true;
                }
                cv.visitVarInsn(ALOAD, returnValueIndex);
                cv.visitTypeInsn(INSTANCEOF, specialArgTypeDesc);
                Label label = new Label();
                cv.visitJumpInsn(IFEQ, label);
                addAfterAdviceInvocation(
                        cv, isStaticJoinPoint, advice, advice.adviceMethod, advice.aspectFieldName,
                        advice.aspectClassName, advice.aspectClassSignature, joinPointClassName,
                        joinPointInstanceIndex, argumentTypes, argStartIndex
                );
                cv.visitLabel(label);
            }
        }
        // need the return value in return operation
        if (isStaticJoinPoint && hasPoppedReturnValueFromStack) {
            cv.visitVarInsn(ALOAD, returnValueIndex);
        }
    }

    /**
     * Adds a single generic after advice invocation.
     *
     * @param cv
     * @param isStaticJoinPoint
     * @param advice
     * @param adviceMethod
     * @param aspectFieldName
     * @param aspectClassName
     * @param aspectClassSignature
     * @param joinPointClassName
     * @param joinPointInstanceIndex
     * @param argumentTypes
     * @param argStartIndex
     */
    private static void addAfterAdviceInvocation(
            final CodeVisitor cv,
            final boolean isStaticJoinPoint,
            final AdviceMethodInfo advice,
            final Method adviceMethod,
            final String aspectFieldName,
            final String aspectClassName,
            final String aspectClassSignature,
            final String joinPointClassName,
            final int joinPointInstanceIndex,
            final Type[] argumentTypes,
            final int argStartIndex) {
        // get the aspect instance
        cv.visitFieldInsn(GETSTATIC, joinPointClassName, aspectFieldName, aspectClassSignature);

        // load the arguments that should be passed to the advice
        int[] argIndexes = advice.adviceMethodArgIndexes;
        // if empty, we consider for now that we have to push JoinPoint for old advice with JoinPoint as sole arg
        if (argIndexes.length == 0) {
            loadJoinPointInstance(cv, isStaticJoinPoint, joinPointInstanceIndex, joinPointClassName);
        }
        for (int j = 0; j < argIndexes.length; j++) {
            int argIndex = argIndexes[j];
            if (argIndex != -1) {
                Type argumentType = argumentTypes[argIndex];
                int argStackIndex = AsmHelper.getRegisterIndexOf(argumentTypes, argIndex) + argStartIndex;
                AsmHelper.loadType(cv, argStackIndex, argumentType);
            } else {
                loadJoinPointInstance(cv, isStaticJoinPoint, joinPointInstanceIndex, joinPointClassName);
            }
        }
        cv.visitMethodInsn(
                INVOKEVIRTUAL,
                aspectClassName,
                adviceMethod.getName(),
                Type.getMethodDescriptor(adviceMethod)
        );
    }

    /**
     * Optimized implementation that does not retrieve the parameters from the join point instance but is passed
     * directly to the method from the input parameters in the 'invoke' method. Can only be used if no around advice
     * exists.
     *
     * @param cv
     * @param joinPointType
     * @param joinPointSequence
     * @param calleeClassName
     * @param calleeMemberName
     * @param calleeMemberSignature
     * @param calleeMemberModifiers
     * @param argumentTypes
     * @param argStartIndex         index on stack of first target method arg (0 or 1, depends of static target or not)
     */
    private static void createInlinedJoinPointInvocation(
            final CodeVisitor cv,
            final int joinPointType,
            final int joinPointSequence,
            final String calleeClassName,
            final String calleeMemberName,
            final String calleeMemberSignature,
            final int calleeMemberModifiers,
            final Type[] argumentTypes,
            final int argStartIndex) {

        // load the target instance (arg0 else not available for static target)
        if (!Modifier.isStatic(calleeMemberModifiers)) {
            cv.visitVarInsn(ALOAD, 0);
        }

        String joinPointName = null; // can be prefixed
        switch (joinPointType) {
            case JoinPointType.METHOD_EXECUTION:
                loadArgumentMemberFields(cv, argStartIndex, argumentTypes);
                joinPointName = TransformationUtil.getPrefixedOriginalMethodName(
                        calleeMemberName,
                        joinPointSequence,
                        calleeClassName
                );
                if (Modifier.isStatic(calleeMemberModifiers)) {
                    cv.visitMethodInsn(INVOKESTATIC, calleeClassName, joinPointName, calleeMemberSignature);
                } else {
                    cv.visitMethodInsn(INVOKEVIRTUAL, calleeClassName, joinPointName, calleeMemberSignature);
                }
                break;

            case JoinPointType.METHOD_CALL:
                loadArgumentMemberFields(cv, argStartIndex, argumentTypes);

                // call the package private wrapper method if target method is not public
                if (!Modifier.isPublic(calleeMemberModifiers)) {
                    joinPointName = TransformationUtil.getWrapperMethodName(
                            calleeMemberName,
                            joinPointSequence,
                            calleeClassName,
                            INVOKE_WRAPPER_METHOD_PREFIX
                    );
                } else {
                    joinPointName = calleeMemberName;
                }
                if (Modifier.isStatic(calleeMemberModifiers)) {
                    cv.visitMethodInsn(INVOKESTATIC, calleeClassName, joinPointName, calleeMemberSignature);
                } else if (Modifier.isAbstract(calleeMemberModifiers)) {
                    // AW-253
                    cv.visitMethodInsn(INVOKEINTERFACE, calleeClassName, joinPointName, calleeMemberSignature);
                } else {
                    cv.visitMethodInsn(INVOKEVIRTUAL, calleeClassName, joinPointName, calleeMemberSignature);
                }
                break;

            case JoinPointType.CONSTRUCTOR_CALL:
                throw new UnsupportedOperationException("CMH - join point type is not supported: CONSTRUCTOR_CALL");

            case JoinPointType.CONSTRUCTOR_EXECUTION:
                joinPointName =
                TransformationUtil.getConstructorBodyMethodSignature(calleeMemberSignature, calleeClassName);
                cv.visitMethodInsn(INVOKESTATIC, calleeClassName, ASPECTWERKZ_PREFIX, joinPointName);
                break;

            case JoinPointType.FIELD_SET:
                loadArgumentMemberFields(cv, argStartIndex, argumentTypes);
                joinPointName = calleeMemberName;
                joinPointName = TransformationUtil.getWrapperMethodName(
                        joinPointName,
                        joinPointSequence,
                        calleeClassName,
                        PUTFIELD_WRAPPER_METHOD_PREFIX
                );
                StringBuffer sig = new StringBuffer();
                sig.append('(');
                sig.append(calleeMemberSignature);
                sig.append(')');
                sig.append('V');
                if (Modifier.isStatic(calleeMemberModifiers)) {
                    cv.visitMethodInsn(INVOKESTATIC, calleeClassName, joinPointName, sig.toString());
                } else {
                    cv.visitMethodInsn(INVOKEVIRTUAL, calleeClassName, joinPointName, sig.toString());
                }
                AsmHelper.addDefaultValue(cv, argumentTypes[0]);
                break;

            case JoinPointType.FIELD_GET:
                joinPointName = TransformationUtil.getWrapperMethodName(
                        calleeMemberName,
                        joinPointSequence,
                        calleeClassName,
                        GETFIELD_WRAPPER_METHOD_PREFIX
                );
                StringBuffer getFieldWrapperDesc = new StringBuffer();
                getFieldWrapperDesc.append('(');
                getFieldWrapperDesc.append(')');
                getFieldWrapperDesc.append(calleeMemberSignature);
                if (Modifier.isStatic(calleeMemberModifiers)) {
                    cv.visitMethodInsn(INVOKESTATIC, calleeClassName, joinPointName, getFieldWrapperDesc.toString());
                } else {
                    cv.visitMethodInsn(INVOKEVIRTUAL, calleeClassName, joinPointName, getFieldWrapperDesc.toString());
                }
                break;

            case JoinPointType.HANDLER:
                throw new UnsupportedOperationException("CMH - join point type is not supported: HANDLER");

            case JoinPointType.STATIC_INITALIZATION:
                throw new UnsupportedOperationException(
                        "CMH - join point type is not supported: STATIC_INITALIZATION"
                );
        }
    }

    /**
     * Creates a call to the target join point, the parameter(s) to the join point are retrieved from the invocation
     * local join point instance.
     *
     * @param cv
     * @param joinPointType
     * @param calleeMemberName
     * @param joinPointSequence
     * @param calleeMemberSignature
     * @param calleeMemberModifiers
     * @param calleeClassName
     * @param calleeClassSignature
     * @param joinPointClassName
     * @param fieldNames
     */
    private static void createJoinPointInvocation(
            final CodeVisitor cv,
            final int joinPointType,
            final String calleeMemberName,
            final int joinPointSequence,
            final String calleeMemberSignature,
            final int calleeMemberModifiers,
            final String calleeClassName,
            final String calleeClassSignature,
            final String joinPointClassName,
            final String[] fieldNames) {

        // load the target instance member field unless calleeMember is static
        if (!Modifier.isStatic(calleeMemberModifiers)) {
            cv.visitVarInsn(ALOAD, 0);
            cv.visitFieldInsn(GETFIELD, joinPointClassName, CALLEE_INSTANCE_FIELD_NAME, calleeClassSignature);
        }

        Type[] argumentTypes = getJoinPointArgumentTypes(joinPointType, calleeMemberSignature);

        String joinPointName;
        switch (joinPointType) {
            case JoinPointType.METHOD_EXECUTION:
                loadArguments(cv, fieldNames, argumentTypes, joinPointClassName);
                joinPointName = TransformationUtil.getPrefixedOriginalMethodName(
                        calleeMemberName,
                        joinPointSequence,
                        calleeClassName
                );
                if (Modifier.isStatic(calleeMemberModifiers)) {
                    cv.visitMethodInsn(INVOKESTATIC, calleeClassName, joinPointName, calleeMemberSignature);
                } else {
                    cv.visitMethodInsn(INVOKEVIRTUAL, calleeClassName, joinPointName, calleeMemberSignature);
                }
                break;

            case JoinPointType.METHOD_CALL:
                loadArguments(cv, fieldNames, argumentTypes, joinPointClassName);
                if (!Modifier.isPublic(calleeMemberModifiers)) {
                    joinPointName = TransformationUtil.getWrapperMethodName(
                            calleeMemberName,
                            joinPointSequence,
                            calleeClassName,
                            INVOKE_WRAPPER_METHOD_PREFIX
                    );
                } else {
                    joinPointName = calleeMemberName;
                }
                if (Modifier.isStatic(calleeMemberModifiers)) {
                    cv.visitMethodInsn(INVOKESTATIC, calleeClassName, joinPointName, calleeMemberSignature);
                } else if (Modifier.isAbstract(calleeMemberModifiers)) {
                    // AW-253
                    cv.visitMethodInsn(INVOKEINTERFACE, calleeClassName, joinPointName, calleeMemberSignature);
                } else {
                    cv.visitMethodInsn(INVOKEVIRTUAL, calleeClassName, joinPointName, calleeMemberSignature);
                }
                break;

            case JoinPointType.CONSTRUCTOR_CALL:
                throw new UnsupportedOperationException("CMH - join point type is not supported: CONSTRUCTOR_CALL");

            case JoinPointType.CONSTRUCTOR_EXECUTION:
                joinPointName =
                TransformationUtil.getConstructorBodyMethodSignature(calleeMemberSignature, calleeClassName);
                cv.visitMethodInsn(INVOKESTATIC, calleeClassName, ASPECTWERKZ_PREFIX, joinPointName);
                break;

            case JoinPointType.FIELD_SET:
                loadArguments(cv, fieldNames, argumentTypes, joinPointClassName);
                joinPointName = TransformationUtil.getWrapperMethodName(
                        calleeMemberName,
                        joinPointSequence,
                        calleeClassName,
                        PUTFIELD_WRAPPER_METHOD_PREFIX
                );
                StringBuffer putFieldWrapperDesc = new StringBuffer();
                putFieldWrapperDesc.append('(');
                putFieldWrapperDesc.append(calleeMemberSignature);
                putFieldWrapperDesc.append(')');
                putFieldWrapperDesc.append('V');
                if (Modifier.isStatic(calleeMemberModifiers)) {
                    cv.visitMethodInsn(INVOKESTATIC, calleeClassName, joinPointName, putFieldWrapperDesc.toString());
                } else {
                    cv.visitMethodInsn(INVOKEVIRTUAL, calleeClassName, joinPointName, putFieldWrapperDesc.toString());
                }
                AsmHelper.addDefaultValue(cv, argumentTypes[0]);
                break;

            case JoinPointType.FIELD_GET:
                joinPointName = TransformationUtil.getWrapperMethodName(
                        calleeMemberName,
                        joinPointSequence,
                        calleeClassName,
                        GETFIELD_WRAPPER_METHOD_PREFIX
                );
                StringBuffer getFieldWrapperDesc = new StringBuffer();
                getFieldWrapperDesc.append('(');
                getFieldWrapperDesc.append(')');
                getFieldWrapperDesc.append(calleeMemberSignature);
                if (Modifier.isStatic(calleeMemberModifiers)) {
                    cv.visitMethodInsn(INVOKESTATIC, calleeClassName, joinPointName, getFieldWrapperDesc.toString());
                } else {
                    cv.visitMethodInsn(INVOKEVIRTUAL, calleeClassName, joinPointName, getFieldWrapperDesc.toString());
                }
                break;

            case JoinPointType.HANDLER:
                throw new UnsupportedOperationException("CMH - join point type is not supported: HANDLER");

            case JoinPointType.STATIC_INITALIZATION:
                throw new UnsupportedOperationException(
                        "CMH - join point type is not supported: STATIC_INITALIZATION"
                );
        }
    }

    /**
     * Adds the parameters to the RTTI instance, builds up an Object array of the arguments if needed.
     *
     * @param cv
     * @param requiresRtti
     * @param joinPointType
     * @param joinPointInstanceIndex
     * @param fieldNames
     * @param argStartIndex
     * @param argumentTypes
     * @param joinPointClassName
     */
    private static void addParametersToRttiInstance(
            final CodeVisitor cv,
            final boolean requiresRtti,
            final boolean isStaticJoinPoint,
            final int joinPointType,
            final int joinPointInstanceIndex,
            final String[] fieldNames,
            final int argStartIndex,
            final Type[] argumentTypes,
            final String joinPointClassName) {
        if (!requiresRtti) {
            return;
        }

        // build up the Object array of arguments and add it to the RTTI
        int arrayInstanceIndex = joinPointInstanceIndex + 1;
        int index = argStartIndex;

        if (argumentTypes.length == 0) {
            return;
        }
        switch (joinPointType) {
            case JoinPointType.METHOD_EXECUTION:
            case JoinPointType.METHOD_CALL:
                AsmHelper.loadIntegerConstant(cv, argumentTypes.length);
                cv.visitTypeInsn(ANEWARRAY, OBJECT_CLASS_NAME);
                cv.visitVarInsn(ASTORE, arrayInstanceIndex);

                for (int i = 0; i < argumentTypes.length; i++) {
                    cv.visitVarInsn(ALOAD, arrayInstanceIndex);
                    AsmHelper.loadIntegerConstant(cv, i);
                    AsmHelper.prepareWrappingOfPrimitiveType(cv, argumentTypes[i]);
                    index = AsmHelper.loadType(cv, index, argumentTypes[i]);
                    AsmHelper.wrapPrimitiveType(cv, argumentTypes[i]);
                    cv.visitInsn(AASTORE);
                }

                loadJoinPointInstance(cv, isStaticJoinPoint, joinPointInstanceIndex, joinPointClassName);

                cv.visitFieldInsn(
                        GETFIELD, joinPointClassName, RTTI_INSTANCE_FIELD_NAME,
                        METHOD_RTTI_IMPL_CLASS_SIGNATURE
                );
                cv.visitVarInsn(ALOAD, arrayInstanceIndex);
                cv.visitMethodInsn(
                        INVOKEVIRTUAL, METHOD_RTTI_IMPL_CLASS_NAME,
                        SET_PARAMETER_VALUES_METHOD_NAME, SET_PARAMETER_VALUES_METHOD_SIGNATURE
                );
                break;

            case JoinPointType.CONSTRUCTOR_CALL:
            case JoinPointType.CONSTRUCTOR_EXECUTION:
                AsmHelper.loadIntegerConstant(cv, fieldNames.length);
                cv.visitTypeInsn(ANEWARRAY, OBJECT_CLASS_NAME);
                cv.visitVarInsn(ASTORE, arrayInstanceIndex);

                for (int i = 0; i < argumentTypes.length; i++) {
                    cv.visitVarInsn(ALOAD, arrayInstanceIndex);
                    AsmHelper.loadIntegerConstant(cv, i);
                    AsmHelper.prepareWrappingOfPrimitiveType(cv, argumentTypes[i]);
                    index = AsmHelper.loadType(cv, index, argumentTypes[i]);
                    AsmHelper.wrapPrimitiveType(cv, argumentTypes[i]);
                    cv.visitInsn(AASTORE);
                }

                loadJoinPointInstance(cv, isStaticJoinPoint, joinPointInstanceIndex, joinPointClassName);

                cv.visitFieldInsn(
                        GETFIELD, joinPointClassName, RTTI_INSTANCE_FIELD_NAME,
                        CONSTRUCTOR_RTTI_IMPL_CLASS_SIGNATURE
                );
                cv.visitVarInsn(ALOAD, arrayInstanceIndex);
                cv.visitMethodInsn(
                        INVOKEVIRTUAL, CONSTRUCTOR_RTTI_IMPL_CLASS_NAME,
                        SET_PARAMETER_VALUES_METHOD_NAME, SET_PARAMETER_VALUES_METHOD_SIGNATURE
                );
                break;

            case JoinPointType.FIELD_SET:
            case JoinPointType.FIELD_GET:
                final Type fieldType = argumentTypes[0];
                if (AsmHelper.isPrimitive(fieldType)) {
                    AsmHelper.prepareWrappingOfPrimitiveType(cv, fieldType);
                    index = AsmHelper.loadType(cv, index, fieldType);
                    AsmHelper.wrapPrimitiveType(cv, fieldType);
                    cv.visitVarInsn(ASTORE, arrayInstanceIndex);
                } else {
                    arrayInstanceIndex = index;
                }

                loadJoinPointInstance(cv, isStaticJoinPoint, joinPointInstanceIndex, joinPointClassName);

                cv.visitFieldInsn(
                        GETFIELD, joinPointClassName, RTTI_INSTANCE_FIELD_NAME,
                        FIELD_RTTI_IMPL_CLASS_SIGNATURE
                );
                cv.visitVarInsn(ALOAD, arrayInstanceIndex);
                cv.visitMethodInsn(
                        INVOKEVIRTUAL, FIELD_RTTI_IMPL_CLASS_NAME,
                        SET_FIELD_VALUE_METHOD_NAME, SET_FIELD_VALUE_METHOD_SIGNATURE
                );
                break;

            case JoinPointType.HANDLER:
                AsmHelper.loadType(cv, index, argumentTypes[0]);

                loadJoinPointInstance(cv, isStaticJoinPoint, joinPointInstanceIndex, joinPointClassName);

                cv.visitFieldInsn(
                        GETFIELD, joinPointClassName, RTTI_INSTANCE_FIELD_NAME,
                        HANDLER_RTTI_IMPL_CLASS_SIGNATURE
                );
                cv.visitVarInsn(ALOAD, index);
                cv.visitMethodInsn(
                        INVOKEVIRTUAL, HANDLER_RTTI_IMPL_CLASS_NAME,
                        SET_PARAMETER_VALUE_METHOD_NAME, SET_PARAMETER_VALUE_METHOD_SIGNATURE
                );
                break;

            case JoinPointType.STATIC_INITALIZATION:
                break;
        }
    }

    /**
     * Adds the return value to the RTTI instance.
     *
     * @param cv
     * @param requiresRtti
     * @param isStaticJoinPoint
     * @param joinPointType
     * @param returnValueIndex
     * @param joinPointInstanceIndex
     * @param joinPointClassName
     */
    private static void addReturnValueToRttiInstance(
            final CodeVisitor cv,
            final boolean requiresRtti,
            final boolean isStaticJoinPoint,
            final int joinPointType,
            final int returnValueIndex,
            final Type returnType,
            final int joinPointInstanceIndex,
            final String joinPointClassName) {

        if (!requiresRtti) {
            return;
        }

        if (returnType.getSort() != Type.VOID) {
            if (joinPointType == JoinPointType.METHOD_EXECUTION || joinPointType == JoinPointType.METHOD_CALL) {
                boolean hasPoppedReturnValueFromStack = false;
                if (isStaticJoinPoint && !hasPoppedReturnValueFromStack) {
                    AsmHelper.storeType(cv, returnValueIndex, returnType);
                    hasPoppedReturnValueFromStack = true;
                }

                int index = returnValueIndex;
                int returnValueIndex2 = AsmHelper.incrementIndex(returnValueIndex, returnType);

                if (AsmHelper.isPrimitive(returnType)) {
                    AsmHelper.prepareWrappingOfPrimitiveType(cv, returnType);
                    index = AsmHelper.loadType(cv, index, returnType);
                    AsmHelper.wrapPrimitiveType(cv, returnType);
                    cv.visitVarInsn(ASTORE, returnValueIndex2);
                } else {
                    returnValueIndex2 = index;
                }

                loadJoinPointInstance(cv, isStaticJoinPoint, joinPointInstanceIndex, joinPointClassName);

                cv.visitFieldInsn(
                        GETFIELD, joinPointClassName,
                        RTTI_INSTANCE_FIELD_NAME, METHOD_RTTI_IMPL_CLASS_SIGNATURE
                );
                cv.visitVarInsn(ALOAD, returnValueIndex2);
                cv.visitMethodInsn(
                        INVOKEVIRTUAL, METHOD_RTTI_IMPL_CLASS_NAME,
                        SET_RETURN_VALUE_METHOD_NAME, SET_RETURN_VALUE_METHOD_SIGNATURE
                );

                // need the return value in return operation
                if (isStaticJoinPoint && hasPoppedReturnValueFromStack) {
                    AsmHelper.loadType(cv, returnValueIndex, returnType);
                }
            }
        }
    }

    /**
     * Loads the join point instance, takes static/non-static join point access into account.
     *
     * @param cv
     * @param isStaticJoinPoint
     * @param joinPointInstanceIndex
     * @param joinPointClassName
     */
    private static void loadJoinPointInstance(
            final CodeVisitor cv,
            final boolean isStaticJoinPoint,
            final int joinPointInstanceIndex,
            final String joinPointClassName) {
        if (isStaticJoinPoint) {
            cv.visitFieldInsn(
                    GETSTATIC, joinPointClassName,
                    STATIC_JOIN_POINT_INSTANCE_FIELD_NAME,
                    L + joinPointClassName + SEMICOLON
            );
        } else {
            cv.visitVarInsn(ALOAD, joinPointInstanceIndex);
        }
    }

    /**
     * Loads the argument member fields.
     *
     * @param cv
     * @param argStartIndex
     * @param argumentTypes
     */
    private static void loadArgumentMemberFields(
            final CodeVisitor cv,
            final int argStartIndex,
            final Type[] argumentTypes) {
        int argStackIndex = argStartIndex;
        for (int index = 0; index < argumentTypes.length; index++) {
            Type argumentType = argumentTypes[index];
            argStackIndex = AsmHelper.loadType(cv, argStackIndex, argumentType);
        }
    }

    /**
     * Loads the arguments.
     *
     * @param cv
     * @param fieldNames
     * @param argumentTypes
     * @param joinPointClassName
     */
    private static void loadArguments(
            final CodeVisitor cv,
            final String[] fieldNames,
            final Type[] argumentTypes,
            final String joinPointClassName) {
        for (int i = 0; i < fieldNames.length; i++) {
            String fieldName = fieldNames[i];
            Type argumentType = argumentTypes[i];
            cv.visitVarInsn(ALOAD, 0);
            cv.visitFieldInsn(GETFIELD, joinPointClassName, fieldName, argumentType.getDescriptor());
        }
    }

    /**
     * Resets the stack frame counter.
     *
     * @param cv
     * @param className
     * @TODO: make use of this method
     */
    private static void resetStackFrameCounter(final CodeVisitor cv, final String className) {
        cv.visitVarInsn(ALOAD, 0);
        cv.visitInsn(ICONST_M1);
        cv.visitFieldInsn(PUTFIELD, className, STACK_FRAME_FIELD_NAME, I);
    }

    /**
     * Handles the incrementation of the stack frame.
     *
     * @param cv
     * @param className
     */
    private static void incrementStackFrameCounter(final CodeVisitor cv, final String className) {
        cv.visitVarInsn(ALOAD, 0);
        cv.visitInsn(DUP);
        cv.visitFieldInsn(GETFIELD, className, STACK_FRAME_FIELD_NAME, I);
        cv.visitInsn(ICONST_1);
        cv.visitInsn(IADD);
        cv.visitFieldInsn(PUTFIELD, className, STACK_FRAME_FIELD_NAME, I);
    }

    /**
     * Decrements the stack frame counter.
     *
     * @param cv
     * @param joinPointClassName
     */
    private static void decrementStackFrameCounter(final CodeVisitor cv, final String joinPointClassName) {
//        cv.visitVarInsn(ALOAD, 0);
//        cv.visitInsn(DUP);
//        cv.visitFieldInsn(GETFIELD, joinPointClassName, STACK_FRAME_FIELD_NAME, I);
//        cv.visitInsn(ICONST_1);
//        cv.visitInsn(ISUB);
//        cv.visitFieldInsn(PUTFIELD, joinPointClassName, STACK_FRAME_FIELD_NAME, I);
    }

    /**
     * Returns the join points return type.
     *
     * @param joinPointType
     * @param calleeMemberSignature
     * @return
     */
    private static Type getJoinPointReturnType(final int joinPointType, final String calleeMemberSignature) {
        Type returnType;
        switch (joinPointType) {
            case JoinPointType.HANDLER:
            case JoinPointType.METHOD_EXECUTION:
            case JoinPointType.METHOD_CALL:
            case JoinPointType.CONSTRUCTOR_EXECUTION:
            case JoinPointType.CONSTRUCTOR_CALL:
                returnType = Type.getReturnType(calleeMemberSignature);
                break;
            case JoinPointType.FIELD_SET:
            case JoinPointType.FIELD_GET:
                returnType = Type.getType(calleeMemberSignature);
                break;
            case JoinPointType.STATIC_INITALIZATION:
                throw new UnsupportedOperationException("static init not supported yet");
            default:
                throw new IllegalArgumentException("Illegal join point type: " + joinPointType);
        }
        return returnType;
    }

    /**
     * Returns the join points argument type(s).
     *
     * @param joinPointType
     * @param calleeMemberSignature
     * @return
     */
    private static Type[] getJoinPointArgumentTypes(final int joinPointType, final String calleeMemberSignature) {
        Type[] argumentTypes;
        switch (joinPointType) {
            case JoinPointType.HANDLER:
            case JoinPointType.METHOD_EXECUTION:
            case JoinPointType.METHOD_CALL:
            case JoinPointType.CONSTRUCTOR_EXECUTION:
            case JoinPointType.CONSTRUCTOR_CALL:
                argumentTypes = Type.getArgumentTypes(calleeMemberSignature);
                break;
            case JoinPointType.FIELD_SET:
            case JoinPointType.FIELD_GET:
                argumentTypes = new Type[]{Type.getType(calleeMemberSignature)};
                break;
            case JoinPointType.STATIC_INITALIZATION:
                throw new UnsupportedOperationException("static init not supported yet");
            default:
                throw new IllegalArgumentException("Illegal join point type: " + joinPointType);
        }
        return argumentTypes;
    }

    /**
     * Creates utility methods for the join point (getter, setters etc.).
     *
     * @param cw
     * @param requiresRtti
     * @param joinPointClassName
     * @param callerClassSignature
     * @param calleeClassSignature
     */
    private static void createUtilityMethods(
            final ClassWriter cw,
            final boolean requiresRtti,
            final int joinPointType,
            final String joinPointClassName,
            final String callerClassSignature,
            final String calleeClassSignature) {
        CodeVisitor cv;

        // getSignature
        {
            cv = cw.visitMethod(
                    ACC_PUBLIC,
                    GET_SIGNATURE_METHOD_NAME,
                    GET_SIGNATURE_METHOD_SIGNATURE,
                    null,
                    null
            );
            switch (joinPointType) {
                case JoinPointType.METHOD_EXECUTION:
                case JoinPointType.METHOD_CALL:
                    cv.visitFieldInsn(
                            GETSTATIC, joinPointClassName,
                            SIGNATURE_FIELD_NAME, METHOD_SIGNATURE_IMPL_CLASS_SIGNATURE
                    );
                    break;
                case JoinPointType.CONSTRUCTOR_EXECUTION:
                case JoinPointType.CONSTRUCTOR_CALL:
                    cv.visitFieldInsn(
                            GETSTATIC, joinPointClassName,
                            SIGNATURE_FIELD_NAME, CONSTRUCTOR_SIGNATURE_IMPL_CLASS_SIGNATURE
                    );
                    break;
                case JoinPointType.FIELD_SET:
                case JoinPointType.FIELD_GET:
                    cv.visitFieldInsn(
                            GETSTATIC, joinPointClassName,
                            SIGNATURE_FIELD_NAME, FIELD_SIGNATURE_IMPL_CLASS_SIGNATURE
                    );
                    break;
                case JoinPointType.HANDLER:
                    cv.visitFieldInsn(
                            GETSTATIC, joinPointClassName,
                            SIGNATURE_FIELD_NAME, HANDLER_SIGNATURE_IMPL_CLASS_SIGNATURE
                    );
                    break;
                case JoinPointType.STATIC_INITALIZATION:
                    throw new UnsupportedOperationException("static init not supported yet");
                default:
                    throw new IllegalArgumentException("Illegal join point type: " + joinPointType);
            }
            cv.visitInsn(ARETURN);
            cv.visitMaxs(0, 0);
        }

        // addMetaData
        {
            cv = cw.visitMethod(ACC_PUBLIC, ADD_META_DATA_METHOD_NAME, ADD_META_DATA_METHOD_SIGNATURE, null, null);
            cv.visitFieldInsn(GETSTATIC, joinPointClassName, META_DATA_FIELD_NAME, MAP_CLASS_SIGNATURE);
            cv.visitVarInsn(ALOAD, 1);
            cv.visitVarInsn(ALOAD, 2);
            cv.visitMethodInsn(
                    INVOKEINTERFACE,
                    MAP_CLASS_NAME,
                    PUT_METHOD_NAME,
                    PUT_METHOD_SIGNATURE
            );
            cv.visitInsn(POP);
            cv.visitInsn(RETURN);
            cv.visitMaxs(0, 0);
        }

        // getMetaData
        {
            cv = cw.visitMethod(ACC_PUBLIC, GET_META_DATA_METHOD_NAME, GET_META_DATA_METHOD_SIGNATURE, null, null);
            cv.visitFieldInsn(GETSTATIC, joinPointClassName, META_DATA_FIELD_NAME, MAP_CLASS_SIGNATURE);
            cv.visitVarInsn(ALOAD, 1);
            cv.visitMethodInsn(INVOKEINTERFACE, MAP_CLASS_NAME, GET_METHOD_NAME, GET_METHOD_SIGNATURE);
            cv.visitInsn(ARETURN);
            cv.visitMaxs(0, 0);
        }

        // getCallee
        {
            cv = cw.visitMethod(
                    ACC_PUBLIC,
                    GET_CALLEE_METHOD_NAME,
                    NO_PARAMS_SIGNATURE + OBJECT_CLASS_SIGNATURE,
                    null, null
            );
            cv.visitVarInsn(ALOAD, 0);
            cv.visitFieldInsn(GETFIELD, joinPointClassName, CALLEE_INSTANCE_FIELD_NAME, calleeClassSignature);
            cv.visitInsn(ARETURN);
            cv.visitMaxs(0, 0);
        }

        // getCaller
        {
            cv = cw.visitMethod(
                    ACC_PUBLIC,
                    GET_CALLER_METHOD_NAME,
                    NO_PARAMS_SIGNATURE + OBJECT_CLASS_SIGNATURE,
                    null, null
            );
            cv.visitVarInsn(ALOAD, 0);
            cv.visitFieldInsn(GETFIELD, joinPointClassName, CALLER_INSTANCE_FIELD_NAME, callerClassSignature);
            cv.visitInsn(ARETURN);
            cv.visitMaxs(0, 0);
        }

        // getTarget
        {
            cv = cw.visitMethod(
                    ACC_PUBLIC,
                    GET_TARGET_METHOD_NAME,
                    NO_PARAMS_SIGNATURE + OBJECT_CLASS_SIGNATURE,
                    null, null
            );
            cv.visitVarInsn(ALOAD, 0);
            cv.visitFieldInsn(GETFIELD, joinPointClassName, CALLEE_INSTANCE_FIELD_NAME, calleeClassSignature);
            cv.visitInsn(ARETURN);
            cv.visitMaxs(0, 0);
        }

        // getThis
        {
            cv = cw.visitMethod(
                    ACC_PUBLIC,
                    GET_THIS_METHOD_NAME,
                    NO_PARAMS_SIGNATURE + OBJECT_CLASS_SIGNATURE,
                    null, null
            );
            cv.visitVarInsn(ALOAD, 0);
            cv.visitFieldInsn(GETFIELD, joinPointClassName, CALLER_INSTANCE_FIELD_NAME, callerClassSignature);
            cv.visitInsn(ARETURN);
            cv.visitMaxs(0, 0);
        }

        // getCallerClass
        {
            cv =
            cw.visitMethod(
                    ACC_PUBLIC,
                    GET_CALLER_CLASS_METHOD_NAME,
                    GET_CALLER_CLASS_METHOD_SIGNATURE,
                    null,
                    null
            );
            cv.visitFieldInsn(GETSTATIC, joinPointClassName, TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);
            cv.visitInsn(ARETURN);
            cv.visitMaxs(0, 0);
        }

        // getTargetClass
        {
            cv =
            cw.visitMethod(
                    ACC_PUBLIC,
                    GET_TARGET_CLASS_METHOD_NAME,
                    GET_TARGET_CLASS_METHOD_SIGNATURE,
                    null,
                    null
            );
            cv.visitFieldInsn(GETSTATIC, joinPointClassName, TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);
            cv.visitInsn(ARETURN);
            cv.visitMaxs(0, 0);
        }

        // getRtti
        {
            cv = cw.visitMethod(ACC_PUBLIC, GET_RTTI_METHOD_NAME, GET_RTTI_METHOD_SIGNATURE, null, null);
            if (requiresRtti) {
                switch (joinPointType) {
                    case JoinPointType.METHOD_EXECUTION:
                    case JoinPointType.METHOD_CALL:
                        cv.visitVarInsn(ALOAD, 0);
                        cv.visitFieldInsn(
                                GETFIELD, joinPointClassName,
                                RTTI_INSTANCE_FIELD_NAME,
                                METHOD_RTTI_IMPL_CLASS_SIGNATURE
                        );
                        break;
                    case JoinPointType.CONSTRUCTOR_EXECUTION:
                    case JoinPointType.CONSTRUCTOR_CALL:
                        cv.visitVarInsn(ALOAD, 0);
                        cv.visitFieldInsn(
                                GETFIELD, joinPointClassName,
                                RTTI_INSTANCE_FIELD_NAME,
                                CONSTRUCTOR_RTTI_IMPL_CLASS_SIGNATURE
                        );
                        break;
                    case JoinPointType.FIELD_SET:
                    case JoinPointType.FIELD_GET:
                        cv.visitVarInsn(ALOAD, 0);
                        cv.visitFieldInsn(
                                GETFIELD, joinPointClassName,
                                RTTI_INSTANCE_FIELD_NAME,
                                FIELD_RTTI_IMPL_CLASS_SIGNATURE
                        );
                        break;
                    case JoinPointType.HANDLER:
                        cv.visitVarInsn(ALOAD, 0);
                        cv.visitFieldInsn(
                                GETFIELD, joinPointClassName,
                                RTTI_INSTANCE_FIELD_NAME,
                                HANDLER_RTTI_IMPL_CLASS_SIGNATURE
                        );
                        break;
                    case JoinPointType.STATIC_INITALIZATION:
                        throw new UnsupportedOperationException("static init not supported yet");
                    default:
                        throw new IllegalArgumentException("Illegal join point type: " + joinPointType);
                }
            } else {
                cv.visitInsn(ACONST_NULL);
            }
            cv.visitInsn(ARETURN);
            cv.visitMaxs(0, 0);
        }

        // getType
        {
            // FIXME should return Enum
            // TODO remove?  needed?
            cv = cw.visitMethod(ACC_PUBLIC, GET_TYPE_METHOD_NAME, GET_TYPE_METHOD_SIGNATURE, null, null);
            cv.visitInsn(ACONST_NULL);
            cv.visitInsn(ARETURN);
            cv.visitMaxs(0, 0);
        }

        // TODO remove, should not be in 2.0
        // reset
        {
            cv = cw.visitMethod(ACC_PUBLIC, RESET_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE, null, null);
            cv.visitInsn(RETURN);
            cv.visitMaxs(0, 0);
        }

        // TODO remove, should not be in 2.0
        // setTarget
        {
            cv = cw.visitMethod(ACC_PUBLIC, "setTarget", "(Ljava/lang/Object;)V", null, null);
            cv.visitInsn(RETURN);
            cv.visitMaxs(0, 0);
        }
    }

    /**
     * Build up the signature of the 'invoke' methods.
     *
     * @param argumentTypes
     * @param targetClassSignature
     * @param thisClassSignature
     * @param returnType
     * @param calleeMemberModifiers
     * @return
     */
    private static String buildInvokeMethodSignature(
            final Type[] argumentTypes,
            final String targetClassSignature,
            final String thisClassSignature,
            final Type returnType,
            final int calleeMemberModifiers) {
        StringBuffer invokeDescBuf = new StringBuffer();
        invokeDescBuf.append('(');
        if (!Modifier.isStatic(calleeMemberModifiers)) {
            // callee
            invokeDescBuf.append(targetClassSignature);
        }
        for (int i = 0; i < argumentTypes.length; i++) {
            Type type = argumentTypes[i];
            invokeDescBuf.append(type.getDescriptor());
        }
        // caller
        invokeDescBuf.append(thisClassSignature);
        invokeDescBuf.append(')');
        invokeDescBuf.append(returnType.getDescriptor());
        return invokeDescBuf.toString();
    }

    /**
     * Checks if at least one of the methods requires the creation of a RTTI instance.
     *
     * @param adviceMethodInfos
     * @return
     */
    private static boolean requiresRtti(final AdviceMethodInfo[] adviceMethodInfos) {
        for (int i = 0; i < adviceMethodInfos.length; i++) {
            Method method = adviceMethodInfos[i].adviceMethod;
            Class[] parameterTypes = method.getParameterTypes();
            for (int j = 0; j < parameterTypes.length; j++) {
                Class parameterType = parameterTypes[j];
                if (parameterType.getName().equals(JOIN_POINT_CLASS_JAVA_NAME)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns an array with AdviceMethodInfo instances for the specific advice type.
     *
     * @param adviceInfos
     * @param aspectFieldPrefix
     * @return
     */
    private static AdviceMethodInfo[] getAdviceMethodInfos(
            final AdviceInfo[] adviceInfos,
            final String aspectFieldPrefix) {
        final AdviceMethodInfo[] adviceMethodInfos = new AdviceMethodInfo[adviceInfos.length];
        for (int i = 0; i < adviceInfos.length; i++) {
            AdviceInfo advice = adviceInfos[i];
            AdviceMethodInfo adviceMethodInfo = new AdviceMethodInfo();
            adviceMethodInfos[i] = adviceMethodInfo;
            AspectContainer container = advice.getAspectManager().getAspectContainer(advice.getAspectIndex());
            String aspectClassName = container.getCrossCuttingInfo().getAspectClass().getName().replace('.', '/');
            adviceMethodInfo.adviceInfo = advice;
            adviceMethodInfo.adviceMethod = container.getAdvice(advice.getMethodIndex());
            adviceMethodInfo.aspectClassName = aspectClassName;
            adviceMethodInfo.aspectFieldName = aspectFieldPrefix + i;
            adviceMethodInfo.aspectClassSignature = L + aspectClassName + SEMICOLON;
            adviceMethodInfo.specialArgumentType = advice.getSpecialArgumentType();
            adviceMethodInfo.adviceMethodArgIndexes = advice.getMethodToArgIndexes();
        }
        return adviceMethodInfos;
    }

    /**
     * Struct with join point info.
     *
     * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
     */
    private static class JoinPointInfo {
        public Signature signature = null;
        public Rtti rtti = null;
        public List cflowExpressions = null;
        public ExpressionContext expressionContext = null;
    }

    /**
     * Struct with advice method info.
     *
     * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
     */
    private static class AdviceMethodInfo {
        public AdviceInfo adviceInfo = null;
        public Method adviceMethod = null;
        public int[] adviceMethodArgIndexes = null;
        public String aspectClassName = null;
        public String aspectFieldName = null;
        public String aspectClassSignature = null;
        public String specialArgumentType = null;
    }
}