/**************************************************************************************
 * 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.joinpoint.management;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.codehaus.aspectwerkz.ConstructorTuple;
import org.codehaus.aspectwerkz.IndexTuple;
import org.codehaus.aspectwerkz.MethodTuple;
import org.codehaus.aspectwerkz.System;
import org.codehaus.aspectwerkz.SystemLoader;
import org.codehaus.aspectwerkz.definition.expression.PointcutType;
import org.codehaus.aspectwerkz.definition.expression.Expression;
import org.codehaus.aspectwerkz.joinpoint.JoinPoint;
import org.codehaus.aspectwerkz.joinpoint.CodeRtti;
import org.codehaus.aspectwerkz.joinpoint.FieldRtti;
import org.codehaus.aspectwerkz.joinpoint.CatchClauseRtti;
import org.codehaus.aspectwerkz.joinpoint.Rtti;
import org.codehaus.aspectwerkz.joinpoint.impl.FieldSignatureImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.CatchClauseSignatureImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.ConstructorSignatureImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.MethodSignatureImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.MethodRttiImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.ConstructorRttiImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.CatchClauseRttiImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.FieldRttiImpl;
import org.codehaus.aspectwerkz.metadata.ClassMetaData;
import org.codehaus.aspectwerkz.metadata.ReflectionMetaDataMaker;

/**
 * Manages the join points, invokes the correct advice chains, handles redeployment, JIT compilation etc. Each advised
 * class' instance holds one instance of this class.
 *
 * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér</a>
 */
public class JoinPointManager {

    /**
     * The JIT compilation boundry for nr of method invocations before optimizing a certain method.
     */
    private static final long JIT_COMPILATION_BOUNDRY;

    /**
     * @TODO: document JVM options
     * <p/>
     * Turns on/off the JIT compiler.
     */
    private static final boolean ENABLE_JIT_COMPILATION;

    static {
        String noJIT = java.lang.System.getProperty("aspectwerkz.jit.off");
        if ((noJIT != null && ("true".equalsIgnoreCase(noJIT) || "yes".equalsIgnoreCase(noJIT)))) {
            ENABLE_JIT_COMPILATION = false;
        }
        else {
            ENABLE_JIT_COMPILATION = true;
        }
        String boundry = java.lang.System.getProperty("aspectwerkz.jit.boundry");
        if (boundry != null) {
            JIT_COMPILATION_BOUNDRY = new Long(boundry).longValue();
        }
        else {
            JIT_COMPILATION_BOUNDRY = 1L;
        }
    }

    /**
     * Block size of the join point index repository grow algorithm.
     */
    private static final int JOIN_POINT_INDEX_GROW_BLOCK = 10;

    private static final List EMTPY_ARRAY_LIST = new ArrayList();
    private static final Map s_managers = new HashMap();
    private static final JoinPointRegistry s_registry = new JoinPointRegistry();

    private final String m_uuid;
    private final System m_system;
    private final Class m_targetClass;
    private final int m_classHash;
    private final ClassMetaData m_targetClassMetaData;

    private ThreadLocal[] m_joinPoints = new ThreadLocal[0];

    /**
     * Returns the join point manager for a specific class.
     *
     * @param targetClass
     * @param uuid
     * @return the join point manager instance for this class
     */
    public final static JoinPointManager getJoinPointManager(final Class targetClass, final String uuid) {
        if (s_managers.containsKey(targetClass)) {
            return (JoinPointManager)s_managers.get(targetClass);
        }
        else {
            JoinPointManager manager = new JoinPointManager(targetClass, uuid);
            s_managers.put(targetClass, manager);
            return manager;
        }
    }

    /**
     * Returs the join point registry.
     *
     * @return the join point registry
     */
    public static JoinPointRegistry getJoinPointRegistry() {
        return s_registry;
    }

    /**
     * Checks if a join point is advised, this does not mean that it has any advices attached to it.
     * <p/>
     * This method should be used by inserting a check in the wrapper/proxy method similar to this:
     * <pre>
     *     if (___AW_joinPointManager.hasAdvices(joinPointHash)) {
     *          // execute the advice chain
     *     }
     *     else {
     *          // invoke the prefixed target method
     *     }
     * </pre>
     *
     * @param joinPointHash
     * @return
     */
    public boolean isAdvised(final int joinPointHash) {
        // TODO: impl.
        return true;
//        return s_registry.getStateForJoinPoint(m_classHash, joinPointHash) > JoinPointState.NOT_ADVISED;
    }

    /**
     * Proceeds with the invocation of the join point, passing on the method hash, the parameter values and the target
     * instance.
     *
     * @param methodHash
     * @param joinPointIndex
     * @param parameters
     * @param targetInstance
     * @param joinPointType
     * @return
     * @throws Throwable
     */
    public final Object proceedWithExecutionJoinPoint(
            final int methodHash,
            final int joinPointIndex,
            final Object[] parameters,
            final Object targetInstance,
            final int joinPointType) throws Throwable {

        ThreadLocal threadLocal = null;
        synchronized (m_joinPoints) {
            if (joinPointIndex >= m_joinPoints.length || m_joinPoints[joinPointIndex] == null) {
                s_registry.registerJoinPoint(
                        joinPointType, methodHash, null,
                        m_classHash, m_targetClass, m_targetClassMetaData, m_system
                );

                threadLocal = new ThreadLocal();

                if (m_joinPoints.length <= joinPointIndex) {
                    ThreadLocal[] tmp = new ThreadLocal[joinPointIndex + JOIN_POINT_INDEX_GROW_BLOCK];
                    java.lang.System.arraycopy(m_joinPoints, 0, tmp, 0, m_joinPoints.length);
                    m_joinPoints = new ThreadLocal[joinPointIndex + JOIN_POINT_INDEX_GROW_BLOCK];
                    java.lang.System.arraycopy(tmp, 0, m_joinPoints, 0, tmp.length);
                }
                m_joinPoints[joinPointIndex] = threadLocal;
            }
            else {
                threadLocal = m_joinPoints[joinPointIndex];
            }
        }

        JoinPointInfo joinPointInfo = (JoinPointInfo)threadLocal.get();
        if (joinPointInfo == null) {
            joinPointInfo = new JoinPointInfo();
            threadLocal.set(joinPointInfo);
        }

        //TODO: ALEX AVCF - JIT breaks cflow - dig that
        if (ENABLE_JIT_COMPILATION && !joinPointInfo.isJitCompiled) {
            handleJitCompilation(
                    methodHash, joinPointType, PointcutType.EXECUTION, joinPointInfo,
                    m_targetClass, m_targetClass, targetInstance, targetInstance
            );
        }

        JoinPoint joinPoint = joinPointInfo.joinPoint;

        // if null or redefined -> create a new join point and cache it
        if (joinPoint == null) {
            Map pointcutTypeToAdvicesMap = s_registry.getAdvicesForJoinPoint(m_classHash, methodHash);

            AdviceContainer[] adviceIndexes = null;
            switch (joinPointType) {
                case JoinPointType.METHOD_EXECUTION:
                    adviceIndexes = (AdviceContainer[])pointcutTypeToAdvicesMap.get(PointcutType.EXECUTION);
                    joinPoint = createMethodJoinPoint(
                            methodHash, joinPointType, m_targetClass, adviceIndexes, targetInstance, targetInstance
                    );
                    break;

                case JoinPointType.CONSTRUCTOR_EXECUTION:
                    adviceIndexes = (AdviceContainer[])pointcutTypeToAdvicesMap.get(PointcutType.EXECUTION);
                    joinPoint = createConstructorJoinPoint(
                            methodHash, joinPointType, m_targetClass, adviceIndexes, targetInstance, targetInstance
                    );
                    break;

                default:
                    throw new RuntimeException("join point type not valid");
            }

            // set the join point
            joinPointInfo.joinPoint = joinPoint;

            // update the state
            if (adviceIndexes.length == 0) {
                joinPointInfo.state = JoinPointState.ADVISED;
            }
            else {
                joinPointInfo.state = JoinPointState.HAS_ADVICES;
            }
        }

        // set the RTTI
        ((JoinPointBase)joinPoint).setTargetInstance(targetInstance);
        if (parameters != null) {
            ((CodeRtti)joinPoint.getRtti()).setParameterValues(parameters);
        }

        return ((JoinPointBase)joinPoint).proceed();
    }

    /**
     * Proceeds with the invocation of the join point, passing on the method hash, the parameter values and the target
     * instance.
     *
     * @param methodHash
     * @param joinPointIndex
     * @param parameters
     * @param targetInstance
     * @param declaringClass
     * @param joinPointType
     * @return the result from the method invocation
     * @throws Throwable
     */
    public final Object proceedWithCallJoinPoint(
            final int methodHash,
            final int joinPointIndex,
            final Object[] parameters,
            final Object targetInstance,
            final Class declaringClass,
            final int joinPointType) throws Throwable {

        ThreadLocal threadLocal = null;
        synchronized (m_joinPoints) {
            if (joinPointIndex >= m_joinPoints.length || m_joinPoints[joinPointIndex] == null) {
                s_registry.registerJoinPoint(
                        joinPointType, methodHash, null, m_classHash, declaringClass,
                        ReflectionMetaDataMaker.createClassMetaData(declaringClass), m_system
                );

                threadLocal = new ThreadLocal();

                if (m_joinPoints.length <= joinPointIndex) {
                    ThreadLocal[] tmp = new ThreadLocal[joinPointIndex + JOIN_POINT_INDEX_GROW_BLOCK];
                    java.lang.System.arraycopy(m_joinPoints, 0, tmp, 0, m_joinPoints.length);
                    m_joinPoints = new ThreadLocal[joinPointIndex + JOIN_POINT_INDEX_GROW_BLOCK];
                    java.lang.System.arraycopy(tmp, 0, m_joinPoints, 0, tmp.length);
                }
                m_joinPoints[joinPointIndex] = threadLocal;
            }
            else {
                threadLocal = m_joinPoints[joinPointIndex];
            }
        }

        JoinPointInfo joinPointInfo = (JoinPointInfo)threadLocal.get();
        if (joinPointInfo == null) {
            joinPointInfo = new JoinPointInfo();
            threadLocal.set(joinPointInfo);
        }

        // TODO: make diff between target and this instances
        if (ENABLE_JIT_COMPILATION && !joinPointInfo.isJitCompiled) {
            handleJitCompilation(
                    methodHash, joinPointType, PointcutType.CALL, joinPointInfo,
                    declaringClass, m_targetClass, targetInstance, targetInstance
            );
        }

        JoinPoint joinPoint = joinPointInfo.joinPoint;

        // if null or redefined -> create a new join point and cache it
        if (joinPoint == null) {
            Map pointcutTypeToAdvicesMap = s_registry.getAdvicesForJoinPoint(m_classHash, methodHash);
            AdviceContainer[] adviceIndexes = null;
            switch (joinPointType) {
                case JoinPointType.METHOD_CALL:
                    adviceIndexes = (AdviceContainer[])pointcutTypeToAdvicesMap.get(PointcutType.CALL);

                    // TODO: make diff between target and this instances
                    joinPoint = createMethodJoinPoint(
                            methodHash, joinPointType, declaringClass, adviceIndexes, targetInstance, targetInstance
                    );
                    break;

                case JoinPointType.CONSTRUCTOR_CALL:
                    adviceIndexes = (AdviceContainer[])pointcutTypeToAdvicesMap.get(PointcutType.CALL);

                    // TODO: make diff between target and this instances
                    joinPoint = createConstructorJoinPoint(
                            methodHash, joinPointType, declaringClass, adviceIndexes, targetInstance, targetInstance
                    );
                    break;

                default:
                    throw new RuntimeException("join point type not valid");
            }

            // set the join point
            joinPointInfo.joinPoint = joinPoint;

            // update the state
            if (adviceIndexes.length == 0) {
                joinPointInfo.state = JoinPointState.ADVISED;
            }
            else {
                joinPointInfo.state = JoinPointState.HAS_ADVICES;
            }
        }

        ((JoinPointBase)joinPoint).setTargetInstance(targetInstance);
        if (parameters != null) {
            ((CodeRtti)joinPoint.getRtti()).setParameterValues(parameters);
        }

        return ((JoinPointBase)joinPoint).proceed();
    }

    /**
     * Proceeds with the invocation of the join point, passing on the method hash, the parameter values and the target
     * instance.
     *
     * @param fieldHash
     * @param joinPointIndex
     * @param fieldValue     as the first arg in an Object array
     * @param targetInstance
     * @param declaringClass
     * @param fieldSignature
     * @throws Throwable
     */
    public final void proceedWithSetJoinPoint(
            final int fieldHash,
            final int joinPointIndex,
            final Object[] fieldValue,
            final Object targetInstance,
            final Class declaringClass,
            final String fieldSignature) throws Throwable {

        ThreadLocal threadLocal = null;
        synchronized (m_joinPoints) {
            if (joinPointIndex >= m_joinPoints.length || m_joinPoints[joinPointIndex] == null) {
                s_registry.registerJoinPoint(
                        JoinPointType.FIELD_SET, fieldHash, fieldSignature, m_classHash, declaringClass,
                        ReflectionMetaDataMaker.createClassMetaData(declaringClass), m_system
                );

                threadLocal = new ThreadLocal();

                if (m_joinPoints.length <= joinPointIndex) {
                    ThreadLocal[] tmp = new ThreadLocal[joinPointIndex + JOIN_POINT_INDEX_GROW_BLOCK];
                    java.lang.System.arraycopy(m_joinPoints, 0, tmp, 0, m_joinPoints.length);
                    m_joinPoints = new ThreadLocal[joinPointIndex + JOIN_POINT_INDEX_GROW_BLOCK];
                    java.lang.System.arraycopy(tmp, 0, m_joinPoints, 0, tmp.length);
                }
                m_joinPoints[joinPointIndex] = threadLocal;
            }
            else {
                threadLocal = m_joinPoints[joinPointIndex];
            }
        }

        JoinPointInfo joinPointInfo = (JoinPointInfo)threadLocal.get();
        if (joinPointInfo == null) {
            joinPointInfo = new JoinPointInfo();
            threadLocal.set(joinPointInfo);
        }

        if (ENABLE_JIT_COMPILATION && !joinPointInfo.isJitCompiled) {
            handleJitCompilation(
                    fieldHash, JoinPointType.FIELD_SET, PointcutType.SET, joinPointInfo,
                    declaringClass, m_targetClass, targetInstance, targetInstance
            );
        }

        JoinPoint joinPoint = joinPointInfo.joinPoint;

        // if null or redefined -> create a new join point and cache it
        if (joinPoint == null) {
            Map pointcutTypeToAdvicesMap = s_registry.getAdvicesForJoinPoint(m_classHash, fieldHash);

            AdviceContainer[] adviceIndexes = null;
            adviceIndexes = (AdviceContainer[])pointcutTypeToAdvicesMap.get(PointcutType.SET);
            joinPoint = createFieldJoinPoint(
                    fieldHash, fieldSignature, JoinPointType.FIELD_SET, m_targetClass,
                    adviceIndexes, targetInstance, targetInstance
            );

            // set the join point
            joinPointInfo.joinPoint = joinPoint;

            // update the state
            if (adviceIndexes.length == 0) {
                joinPointInfo.state = JoinPointState.ADVISED;
            }
            else {
                joinPointInfo.state = JoinPointState.HAS_ADVICES;
            }
        }

        // intialize the join point before each usage
        ((JoinPointBase)joinPoint).setTargetInstance(targetInstance);
        if (fieldValue[0] != null) {
            ((FieldRtti)joinPoint.getRtti()).setFieldValue(fieldValue[0]); // array due to sucky javassist field handling
        }
        ((JoinPointBase)joinPoint).proceed();
    }

    /**
     * Proceeds with the invocation of the join point, passing on the method hash, the parameter values and the target
     * instance.
     *
     * @param fieldHash
     * @param joinPointIndex
     * @param targetInstance
     * @param declaringClass
     * @param fieldSignature
     * @throws Throwable
     */
    public final Object proceedWithGetJoinPoint(
            final int fieldHash,
            final int joinPointIndex,
            final Object targetInstance,
            final Class declaringClass,
            final String fieldSignature) throws Throwable {

        ThreadLocal threadLocal = null;
        synchronized (m_joinPoints) {
            if (joinPointIndex >= m_joinPoints.length || m_joinPoints[joinPointIndex] == null) {
                s_registry.registerJoinPoint(
                        JoinPointType.FIELD_GET, fieldHash, fieldSignature, m_classHash, declaringClass,
                        ReflectionMetaDataMaker.createClassMetaData(declaringClass), m_system
                );

                threadLocal = new ThreadLocal();

                if (m_joinPoints.length <= joinPointIndex) {
                    ThreadLocal[] tmp = new ThreadLocal[joinPointIndex + JOIN_POINT_INDEX_GROW_BLOCK];
                    java.lang.System.arraycopy(m_joinPoints, 0, tmp, 0, m_joinPoints.length);
                    m_joinPoints = new ThreadLocal[joinPointIndex + JOIN_POINT_INDEX_GROW_BLOCK];
                    java.lang.System.arraycopy(tmp, 0, m_joinPoints, 0, tmp.length);
                }
                m_joinPoints[joinPointIndex] = threadLocal;
            }
            else {
                threadLocal = m_joinPoints[joinPointIndex];
            }
        }

        JoinPointInfo joinPointInfo = (JoinPointInfo)threadLocal.get();
        if (joinPointInfo == null) {
            joinPointInfo = new JoinPointInfo();
            threadLocal.set(joinPointInfo);
        }

        if (ENABLE_JIT_COMPILATION && !joinPointInfo.isJitCompiled) {
            handleJitCompilation(
                    fieldHash, JoinPointType.FIELD_GET, PointcutType.GET, joinPointInfo,
                    declaringClass, m_targetClass, targetInstance, targetInstance
            );
        }

        JoinPoint joinPoint = joinPointInfo.joinPoint;

        // if null or redefined -> create a new join point and cache it
        if (joinPoint == null) {
            Map pointcutTypeToAdvicesMap = s_registry.getAdvicesForJoinPoint(m_classHash, fieldHash);

            AdviceContainer[] adviceIndexes = null;
            adviceIndexes = (AdviceContainer[])pointcutTypeToAdvicesMap.get(PointcutType.GET);
            joinPoint = createFieldJoinPoint(
                    fieldHash, fieldSignature, JoinPointType.FIELD_GET, m_targetClass,
                    adviceIndexes, targetInstance, targetInstance
            );

            // set the join point
            joinPointInfo.joinPoint = joinPoint;

            // update the state
            if (adviceIndexes.length == 0) {
                joinPointInfo.state = JoinPointState.ADVISED;
            }
            else {
                joinPointInfo.state = JoinPointState.HAS_ADVICES;
            }
        }

        // intialize the join point before each usage
        ((JoinPointBase)joinPoint).setTargetInstance(targetInstance);

        return ((JoinPointBase)joinPoint).proceed();
    }

    /**
     * Proceeds with the invocation of the join point, passing on the method hash, the parameter values and the target
     * instance.
     *
     * @param handlerHash
     * @param joinPointIndex
     * @param exceptionInstance
     * @param targetInstance
     * @param handlerSignature
     * @throws Throwable
     */
    public final void proceedWithHandlerJoinPoint(
            final int handlerHash,
            final int joinPointIndex,
            final Object exceptionInstance,
            final Object targetInstance,
            final String handlerSignature) throws Throwable {

        ThreadLocal threadLocal = null;
        synchronized (m_joinPoints) {
            if (joinPointIndex >= m_joinPoints.length || m_joinPoints[joinPointIndex] == null) {
                ClassMetaData exceptionMetaData = ReflectionMetaDataMaker.createClassMetaData(
                        exceptionInstance.getClass()
                );
                s_registry.registerJoinPoint(
                        JoinPointType.HANDLER, handlerHash, handlerSignature,
                        m_classHash, m_targetClass, exceptionMetaData, m_system
                );

                threadLocal = new ThreadLocal();

                if (m_joinPoints.length <= joinPointIndex) {
                    ThreadLocal[] tmp = new ThreadLocal[joinPointIndex + JOIN_POINT_INDEX_GROW_BLOCK];
                    java.lang.System.arraycopy(m_joinPoints, 0, tmp, 0, m_joinPoints.length);
                    m_joinPoints = new ThreadLocal[joinPointIndex + JOIN_POINT_INDEX_GROW_BLOCK];
                    java.lang.System.arraycopy(tmp, 0, m_joinPoints, 0, tmp.length);
                }
                m_joinPoints[joinPointIndex] = threadLocal;
            }
            else {
                threadLocal = m_joinPoints[joinPointIndex];
            }
        }

        JoinPointInfo joinPointInfo = (JoinPointInfo)threadLocal.get();
        if (joinPointInfo == null) {
            joinPointInfo = new JoinPointInfo();
            threadLocal.set(joinPointInfo);
        }

        if (ENABLE_JIT_COMPILATION && !joinPointInfo.isJitCompiled) {
            handleJitCompilation(
                    handlerHash, JoinPointType.HANDLER, PointcutType.HANDLER, joinPointInfo,
                    m_targetClass, m_targetClass, targetInstance, targetInstance
            );
        }

        JoinPoint joinPoint = joinPointInfo.joinPoint;

        // if null or redefined -> create a new join point and cache it
        if (joinPoint == null) {
            Map pointcutTypeToAdvicesMap = s_registry.getAdvicesForJoinPoint(m_classHash, handlerHash);

            AdviceContainer[] adviceIndexes = (AdviceContainer[])pointcutTypeToAdvicesMap.get(PointcutType.HANDLER);
            joinPoint = createCatchClauseJoinPoint(
                    exceptionInstance.getClass(), m_targetClass, handlerSignature,
                    adviceIndexes, targetInstance, targetInstance
            );

            // set the join point
            joinPointInfo.joinPoint = joinPoint;

            // update the state
            if (adviceIndexes.length == 0) {
                joinPointInfo.state = JoinPointState.ADVISED;
            }
            else {
                joinPointInfo.state = JoinPointState.HAS_ADVICES;
            }
        }

        // intialize the join point before each usage
        ((JoinPointBase)joinPoint).setTargetInstance(targetInstance);
        ((CatchClauseRtti)joinPoint.getRtti()).setParameterValue(exceptionInstance);

        ((JoinPointBase)joinPoint).proceed();
    }

    /**
     * Handles the Just-In-Time (JIT) compilation of the advice execution chains.
     *
     * @param joinPointHash
     * @param joinPointType
     * @param pointcutType
     * @param joinPointInfo
     * @param declaringClass
     * @param targetClass
     * @param thisInstance
     * @param targetInstance
     */
    private final void handleJitCompilation(
            final int joinPointHash,
            final int joinPointType,
            final PointcutType pointcutType,
            final JoinPointInfo joinPointInfo,
            final Class declaringClass,
            final Class targetClass,
            final Object thisInstance,
            final Object targetInstance) {
        joinPointInfo.invocations++;
        if (joinPointInfo.state == JoinPointState.REDEFINED) {
            joinPointInfo.invocations = 0L;
        }
        else if (joinPointInfo.invocations == JIT_COMPILATION_BOUNDRY) {
            Map advices = s_registry.getAdvicesForJoinPoint(m_classHash, joinPointHash);
            if (advices.containsKey(pointcutType)) {
                AdviceContainer[] adviceIndexes = (AdviceContainer[])advices.get(pointcutType);
                joinPointInfo.joinPoint = JitCompiler.compileJoinPoint(
                        joinPointHash, joinPointType, pointcutType, adviceIndexes,
                        declaringClass, targetClass, m_uuid, thisInstance, targetInstance
                );
                joinPointInfo.isJitCompiled = true;
            }
        }
    }

    /**
     * Create a method join point.
     *
     * @param methodHash
     * @param joinPointType
     * @param declaringClass
     * @param adviceIndexes
     * @param thisInstance
      * @param targetInstance
      * @return
     */
    private final MethodJoinPoint createMethodJoinPoint(
            final int methodHash,
            final int joinPointType,
            final Class declaringClass,
            final AdviceContainer[] adviceIndexes,
            final Object thisInstance,
            final Object targetInstance) {
        MethodTuple methodTuple = m_system.getAspectManager().getMethodTuple(declaringClass, methodHash);
        Class declaringType = methodTuple.getDeclaringClass();
        MethodSignatureImpl signature = new MethodSignatureImpl(declaringType, methodTuple);
        Rtti rtti = new MethodRttiImpl(signature, thisInstance, targetInstance);

        List cflowExpressions = m_system.getAspectManager().getCFlowExpressions(
                ReflectionMetaDataMaker.createClassMetaData(declaringClass),
                ReflectionMetaDataMaker.createMethodMetaData(methodTuple.getWrapperMethod()),
                null, PointcutType.EXECUTION//TODO CAN BE @CALL - see proceedWithCallJoinPoint
        );

//        // TODO: ALEX - cflow is a pain to debug
//        for (Iterator it = cflowExpressions.iterator(); it.hasNext();) {
//            java.lang.System.out.println("(Expression)(it.next() = " + ((Expression)(it.next())).getExpression());
//        }

        // TODO: cflow for before and after advices needed
        return new MethodJoinPoint(
                m_uuid, joinPointType, m_targetClass, signature, rtti, cflowExpressions,
                createAroundAdviceExecutor(adviceIndexes, joinPointType),
                createBeforeAdviceExecutor(adviceIndexes),
                createAfterAdviceExecutor(adviceIndexes)
        );
    }

    /**
     * Create a constructor join point.
     *
     * @param constructorHash
     * @param joinPointType
     * @param declaringClass
     * @param adviceIndexes
     * @param thisInstance
      * @param targetInstance
      * @return
     */
    private final JoinPoint createConstructorJoinPoint(
            final int constructorHash,
            final int joinPointType,
            final Class declaringClass,
            final AdviceContainer[] adviceIndexes,
            final Object thisInstance,
            final Object targetInstance) {
        ConstructorTuple constructorTuple = m_system.getAspectManager().getConstructorTuple(
                declaringClass, constructorHash
        );

        Class declaringType = constructorTuple.getDeclaringClass();
        ConstructorSignatureImpl signature = new ConstructorSignatureImpl(declaringType, constructorTuple);
        Rtti rtti = new ConstructorRttiImpl(signature, thisInstance, targetInstance);

        // TODO: enable cflow for constructors
//        List cflowExpressions = m_system.getAspectManager().getCFlowExpressions(
//                ReflectionMetaDataMaker.createClassMetaData(declaringClass),
//                ReflectionMetaDataMaker.createConstructorMetaData(constructor)
//        );
        return new ConstructorJoinPoint(
                m_uuid, joinPointType, m_targetClass, signature, rtti, EMTPY_ARRAY_LIST,
                createAroundAdviceExecutor(adviceIndexes, joinPointType),
                createBeforeAdviceExecutor(adviceIndexes),
                createAfterAdviceExecutor(adviceIndexes)
        );
    }

    /**
     * Create a field join point.
     *
     * @param fieldHash
     * @param fieldSignature
     * @param joinPointType
     * @param declaringClass
     * @param adviceIndexes
     * @param thisInstance
      * @param targetInstance
      * @return
     */
    private final JoinPoint createFieldJoinPoint(
            final int fieldHash,
            final String fieldSignature,
            final int joinPointType,
            final Class declaringClass,
            final AdviceContainer[] adviceIndexes,
            final Object thisInstance,
            final Object targetInstance) {

        Field field = m_system.getAspectManager().getField(declaringClass, fieldHash);
        FieldSignatureImpl signature = new FieldSignatureImpl(declaringClass, field);
        Rtti rtti = new FieldRttiImpl(signature, thisInstance, targetInstance);

        // TODO: enable cflow for field set get pointcuts
//        List cflowExpressions = new ArrayList();
//                m_system.getAspectManager().getCFlowExpressions(
//                 ReflectionMetaDataMaker.createClassMetaData(declaringClass),
//                 ReflectionMetaDataMaker.createFieldMetaData(fieldSignature)
//         );
        return new FieldJoinPoint(
                m_uuid, joinPointType, m_targetClass, signature, rtti, EMTPY_ARRAY_LIST,
                createAroundAdviceExecutor(adviceIndexes, joinPointType),
                createBeforeAdviceExecutor(adviceIndexes),
                createAfterAdviceExecutor(adviceIndexes)
        );
    }

    /**
     * Create a catch clause join point.
     *
     * @param exceptionClass
     * @param declaringClass
     * @param catchClauseSignature
     * @param adviceIndexes
     * @param thisInstance
      * @param targetInstance
      * @return
     */
    private final JoinPoint createCatchClauseJoinPoint(
            final Class exceptionClass,
            final Class declaringClass,
            final String catchClauseSignature,
            final AdviceContainer[] adviceIndexes,
            final Object thisInstance,
            final Object targetInstance) {
        CatchClauseSignatureImpl signature = new CatchClauseSignatureImpl(
                exceptionClass, declaringClass, catchClauseSignature
        );
        Rtti rtti = new CatchClauseRttiImpl(signature, thisInstance, targetInstance);       

        // TODO: enable cflow for catch clauses
//        List cflowExpressions = m_system.getAspectManager().getCFlowExpressions(
//                ReflectionMetaDataMaker.createClassMetaData(declaringClass),
//                ReflectionMetaDataMaker.createCatchClauseMetaData(signature)
//        );
        return new CatchClauseJoinPoint(
                m_uuid, m_targetClass, signature, rtti, EMTPY_ARRAY_LIST,
                createAroundAdviceExecutor(adviceIndexes, JoinPointType.HANDLER),
                createBeforeAdviceExecutor(adviceIndexes),
                createAfterAdviceExecutor(adviceIndexes)
        );
    }

    /**
     * Creates an around advice executor.
     *
     * @param adviceIndexes
     * @param joinPointType
     * @return the advice executor
     */
    private final AroundAdviceExecutor createAroundAdviceExecutor(
            final AdviceContainer[] adviceIndexes,
            final int joinPointType) {
        return new AroundAdviceExecutor(extractAroundAdvice(adviceIndexes), m_system, joinPointType);
    }

    /**
     * Creates a before advice executor.
     *
     * @param adviceIndexes
     * @return the advice executor
     */
    private final BeforeAdviceExecutor createBeforeAdviceExecutor(final AdviceContainer[] adviceIndexes) {
        return new BeforeAdviceExecutor(extractBeforeAdvice(adviceIndexes), m_system);
    }

    /**
     * Creates an after advice executor.
     *
     * @param adviceIndexes
     * @return the advice executor
     */
    private final AfterAdviceExecutor createAfterAdviceExecutor(final AdviceContainer[] adviceIndexes) {
        return new AfterAdviceExecutor(extractAfterAdvice(adviceIndexes), m_system);
    }

    /**
     * Extracts the around advices.
     *
     * @param adviceIndexes
     * @return
     */
    final static IndexTuple[] extractAroundAdvice(final AdviceContainer[] adviceIndexes) {
        int i, j;
        List aroundAdviceList = new ArrayList();
        for (i = 0; i < adviceIndexes.length; i++) {
            AdviceContainer adviceIndex = adviceIndexes[i];
            IndexTuple[] indexTuples = adviceIndex.getAroundAdvices();
            for (j = 0; j < indexTuples.length; j++) {
                aroundAdviceList.add(indexTuples[j]);
            }
        }
        IndexTuple[] aroundAdvices = new IndexTuple[aroundAdviceList.size()];
        i = 0;
        for (Iterator it = aroundAdviceList.iterator(); it.hasNext(); i++) {
            aroundAdvices[i] = (IndexTuple)it.next();
        }
        return aroundAdvices;
    }

    /**
     * Extracts the before advices.
     *
     * @param adviceIndexes
     * @return
     */
    final static IndexTuple[] extractBeforeAdvice(final AdviceContainer[] adviceIndexes) {
        int i, j;
        List beforeAdviceList = new ArrayList();
        for (i = 0; i < adviceIndexes.length; i++) {
            AdviceContainer adviceIndex = adviceIndexes[i];
            IndexTuple[] indexTuples = adviceIndex.getBeforeAdvices();
            for (j = 0; j < indexTuples.length; j++) {
                beforeAdviceList.add(indexTuples[j]);
            }
        }
        IndexTuple[] beforeAdvices = new IndexTuple[beforeAdviceList.size()];
        i = 0;
        for (Iterator it = beforeAdviceList.iterator(); it.hasNext(); i++) {
            beforeAdvices[i] = (IndexTuple)it.next();
        }
        return beforeAdvices;
    }

    /**
     * Extracts the after advices.
     *
     * @param adviceIndexes
     * @return
     */
    final static IndexTuple[] extractAfterAdvice(final AdviceContainer[] adviceIndexes) {
        int i, j;
        List afterAdviceList = new ArrayList();
        for (i = 0; i < adviceIndexes.length; i++) {
            AdviceContainer adviceIndex = adviceIndexes[i];
            IndexTuple[] indexTuples = adviceIndex.getAfterAdvices();
            for (j = 0; j < indexTuples.length; j++) {
                afterAdviceList.add(indexTuples[j]);
            }
        }
        IndexTuple[] afterAdvices = new IndexTuple[afterAdviceList.size()];
        i = 0;
        for (Iterator it = afterAdviceList.iterator(); it.hasNext(); i++) {
            afterAdvices[i] = (IndexTuple)it.next();
        }
        return afterAdvices;
    }

    /**
     * Creates a new join point manager for a specific class.
     *
     * @param targetClass
     * @param uuid
     */
    private JoinPointManager(final Class targetClass, final String uuid) {
        m_uuid = uuid;
        m_system = SystemLoader.getSystem(m_uuid);
        m_targetClass = targetClass;
        m_classHash = m_targetClass.hashCode();
        m_targetClassMetaData = ReflectionMetaDataMaker.createClassMetaData(m_targetClass);
    }

    /**
     * Contains the JoinPoint instance and some RTTI about the join point.
     */
    static class JoinPointInfo {
        public JoinPoint joinPoint = null;
        public int state = JoinPointState.NOT_ADVISED;
        public long invocations = 0L;
        public boolean isJitCompiled = false;
    }
}


