/**************************************************************************************
 * 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 org.codehaus.aspectwerkz.AspectSystem;
import org.codehaus.aspectwerkz.ConstructorTuple;
import org.codehaus.aspectwerkz.AdviceInfo;
import org.codehaus.aspectwerkz.MethodTuple;
import org.codehaus.aspectwerkz.SystemLoader;
import org.codehaus.aspectwerkz.aspect.management.AspectRegistry;
import org.codehaus.aspectwerkz.aspect.management.Pointcut;
import org.codehaus.aspectwerkz.expression.PointcutType;
import org.codehaus.aspectwerkz.joinpoint.CatchClauseRtti;
import org.codehaus.aspectwerkz.joinpoint.CodeRtti;
import org.codehaus.aspectwerkz.joinpoint.FieldRtti;
import org.codehaus.aspectwerkz.joinpoint.JoinPoint;
import org.codehaus.aspectwerkz.joinpoint.Rtti;
import org.codehaus.aspectwerkz.joinpoint.impl.CatchClauseRttiImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.CatchClauseSignatureImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.ConstructorRttiImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.ConstructorSignatureImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.FieldRttiImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.FieldSignatureImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.MethodRttiImpl;
import org.codehaus.aspectwerkz.joinpoint.impl.MethodSignatureImpl;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
import org.codehaus.aspectwerkz.reflect.MemberInfo;
import org.codehaus.aspectwerkz.reflect.impl.java.JavaClassInfo;
import org.codehaus.aspectwerkz.reflect.impl.java.JavaClassInfoRepository;
import org.codehaus.aspectwerkz.transform.TransformationUtil;

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 EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
//import EDU.oswego.cs.dl.util.concurrent.ReaderPreferenceReadWriteLock;

/**
 * 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>
 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </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;

    /**
     * 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 Map s_managers = new HashMap();

    private static final JoinPointRegistry s_registry = new JoinPointRegistry();

    private final JavaClassInfoRepository m_classInfoRepository;

    private final AspectSystem m_system;

    private final Class m_targetClass;

    private final int m_classHash;

    private int m_hotswapCount = 0;

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

    //    private final ReadWriteLock m_readWriteLock = new ReaderPreferenceReadWriteLock();

    /**
     * Creates a new join point manager for a specific class.
     * 
     * @param targetClass
     */
    private JoinPointManager(final Class targetClass) {
        m_system = SystemLoader.getSystem(targetClass.getClassLoader());
        m_targetClass = targetClass;
        m_classHash = m_targetClass.hashCode();
        m_classInfoRepository = JavaClassInfoRepository.getRepository(targetClass.getClassLoader());
        m_hotswapCount = 0;
    }

    /**
     * Creates a new join point manager for a specific class.
     * 
     * @param targetClass
     * @param hotswapCount
     */
    private JoinPointManager(final Class targetClass, int hotswapCount) {
        m_system = SystemLoader.getSystem(targetClass.getClassLoader());
        m_targetClass = targetClass;
        m_classHash = m_targetClass.hashCode();
        m_classInfoRepository = JavaClassInfoRepository.getRepository(targetClass.getClassLoader());
        m_hotswapCount = hotswapCount;
    }

    /**
     * Returns the join point manager for a specific class.
     * 
     * @param targetClass
     * @param uuid
     * @return the join point manager instance for this class
     * @TODO: UUID is not use or needed anymore, remove it from the TFs
     */
    public final static JoinPointManager getJoinPointManager(final Class targetClass, final String uuid) {
        if (s_managers.containsKey(targetClass)) { //TODO AVAOPC should be Weak ?
            return (JoinPointManager) s_managers.get(targetClass);
        } else {
            JoinPointManager manager = new JoinPointManager(targetClass);
            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;
        if (joinPointIndex < 0) {
            throw new RuntimeException();
        }

        //        m_readWriteLock.writeLock().acquire();
        synchronized (m_joinPoints) {
            //        try {
            if ((joinPointIndex >= m_joinPoints.length) || (m_joinPoints[joinPointIndex] == null)) {
                s_registry.registerJoinPoint(
                    joinPointType,
                    methodHash,
                    null,
                    m_classHash,
                    m_targetClass,
                    null,
                    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];
            }
        }

        //        } finally {
        //            m_readWriteLock.writeLock().release();
        //        }
        //
        JoinPointInfo joinPointInfo = (JoinPointInfo) threadLocal.get();
        if (joinPointInfo == null) {
            joinPointInfo = new JoinPointInfo();
            threadLocal.set(joinPointInfo);
        }
        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 joinPointMetaDataMap = s_registry.getJoinPointMetaData(m_classHash, methodHash);
            JoinPointMetaData joinPointMetaData = (JoinPointMetaData) joinPointMetaDataMap.get(PointcutType.EXECUTION);
            AdviceIndexInfo[] adviceIndexes = joinPointMetaData.adviceIndexes;
            List cflowExpressions = joinPointMetaData.cflowExpressions;
            Pointcut cflowPointcut = joinPointMetaData.cflowPointcut;
            initCflowManagement(cflowPointcut, joinPointInfo);
            switch (joinPointType) {
                case JoinPointType.METHOD_EXECUTION:
                    joinPoint = createMethodJoinPoint(
                        methodHash,
                        joinPointType,
                        m_targetClass,
                        adviceIndexes,
                        joinPointMetaData,
                        targetInstance,
                        targetInstance);
                    break;
                case JoinPointType.CONSTRUCTOR_EXECUTION:
                    joinPoint = createConstructorJoinPoint(
                        methodHash,
                        joinPointType,
                        m_targetClass,
                        adviceIndexes,
                        joinPointMetaData,
                        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
        joinPoint.setTarget(targetInstance);
        if (parameters != null) {
            ((CodeRtti) joinPoint.getRtti()).setParameterValues(parameters);
        }
        enterCflow(joinPointInfo);
        try {
            return joinPoint.proceed();
        } finally {
            exitCflow(joinPointInfo);
        }
    }

    /**
     * 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 targetClass
     * @param targetInstance
     * @param thisClass
     * @param thisInstance
     * @param withinMethodName
     * @param withinMethodSignature
     * @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 Class targetClass,
        final Object targetInstance,
        final Class thisClass,
        final Object thisInstance,
        final String withinMethodName,
        final String withinMethodSignature,
        final int joinPointType) throws Throwable {
        ThreadLocal threadLocal;
        synchronized (m_joinPoints) {
            if ((joinPointIndex >= m_joinPoints.length) || (m_joinPoints[joinPointIndex] == null)) {
                MemberInfo withinMemberInfo = ClassInfoHelper.createMemberInfo(
                    targetClass,
                    withinMethodName,
                    withinMethodSignature);
                s_registry.registerJoinPoint(
                    joinPointType,
                    methodHash,
                    null,
                    m_classHash,
                    thisClass,
                    withinMemberInfo,
                    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,
                thisClass,
                m_targetClass,
                thisInstance,
                thisInstance);
        }
        JoinPoint joinPoint = joinPointInfo.joinPoint;

        // if null or redefined -> create a new join point and cache it
        if (joinPoint == null) {
            Map joinPointMetaDataMap = s_registry.getJoinPointMetaData(m_classHash, methodHash);
            JoinPointMetaData joinPointMetaData = (JoinPointMetaData) joinPointMetaDataMap.get(PointcutType.CALL);
            AdviceIndexInfo[] adviceIndexes = joinPointMetaData.adviceIndexes;
            List cflowExpressions = joinPointMetaData.cflowExpressions;
            Pointcut cflowPointcut = joinPointMetaData.cflowPointcut;
            initCflowManagement(cflowPointcut, joinPointInfo);
            switch (joinPointType) {
                case JoinPointType.METHOD_CALL:

                    // TODO: make diff between target and this instances
                    joinPoint = createMethodJoinPoint(
                        methodHash,
                        joinPointType,
                        thisClass,
                        adviceIndexes,
                        joinPointMetaData,
                        thisInstance,
                        thisInstance);
                    break;
                case JoinPointType.CONSTRUCTOR_CALL:

                    // TODO: make diff between target and this instances
                    joinPoint = createConstructorJoinPoint(
                        methodHash,
                        joinPointType,
                        thisClass,
                        adviceIndexes,
                        joinPointMetaData,
                        thisInstance,
                        thisInstance);
                    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;
            }
        }
        joinPoint.setTarget(thisInstance);
        if (parameters != null) {
            ((CodeRtti) joinPoint.getRtti()).setParameterValues(parameters);
        }
        enterCflow(joinPointInfo);
        try {
            return joinPoint.proceed();
        } finally {
            exitCflow(joinPointInfo);
        }
    }

    /**
     * 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;
        synchronized (m_joinPoints) {
            if ((joinPointIndex >= m_joinPoints.length) || (m_joinPoints[joinPointIndex] == null)) {
                s_registry.registerJoinPoint(
                    JoinPointType.FIELD_SET,
                    fieldHash,
                    fieldSignature,
                    m_classHash,
                    declaringClass,
                    null,
                    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 joinPointMetaDataMap = s_registry.getJoinPointMetaData(m_classHash, fieldHash);
            JoinPointMetaData joinPointMetaData = (JoinPointMetaData) joinPointMetaDataMap.get(PointcutType.SET);
            AdviceIndexInfo[] adviceIndexes = joinPointMetaData.adviceIndexes;
            List cflowExpressions = joinPointMetaData.cflowExpressions;
            Pointcut cflowPointcut = joinPointMetaData.cflowPointcut;
            initCflowManagement(cflowPointcut, joinPointInfo);
            joinPoint = createFieldJoinPoint(
                fieldHash,
                JoinPointType.FIELD_SET,
                m_targetClass,
                adviceIndexes,
                joinPointMetaData,
                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
        joinPoint.setTarget(targetInstance);
        if (fieldValue[0] != null) {
            ((FieldRtti) joinPoint.getRtti()).setFieldValue(fieldValue[0]); // array due to sucky
            // javassist field
            // handling
        }
        enterCflow(joinPointInfo);
        try {
            joinPoint.proceed();
        } finally {
            exitCflow(joinPointInfo);
        }
    }

    /**
     * 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;
        synchronized (m_joinPoints) {
            if ((joinPointIndex >= m_joinPoints.length) || (m_joinPoints[joinPointIndex] == null)) {
                s_registry.registerJoinPoint(
                    JoinPointType.FIELD_GET,
                    fieldHash,
                    fieldSignature,
                    m_classHash,
                    declaringClass,
                    null,
                    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 joinPointMetaDataMap = s_registry.getJoinPointMetaData(m_classHash, fieldHash);
            JoinPointMetaData joinPointMetaData = (JoinPointMetaData) joinPointMetaDataMap.get(PointcutType.GET);
            AdviceIndexInfo[] adviceIndexes = joinPointMetaData.adviceIndexes;
            List cflowExpressions = joinPointMetaData.cflowExpressions;
            Pointcut cflowPointcut = joinPointMetaData.cflowPointcut;
            initCflowManagement(cflowPointcut, joinPointInfo);
            joinPoint = createFieldJoinPoint(
                fieldHash,
                JoinPointType.FIELD_GET,
                m_targetClass,
                adviceIndexes,
                joinPointMetaData,
                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
        joinPoint.setTarget(targetInstance);
        enterCflow(joinPointInfo);
        try {
            return joinPoint.proceed();
        } finally {
            exitCflow(joinPointInfo);
        }
    }

    /**
     * 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;
        synchronized (m_joinPoints) {
            if ((joinPointIndex >= m_joinPoints.length) || (m_joinPoints[joinPointIndex] == null)) {
                ClassInfo withinClassInfo = createClassInfo(m_targetClass);
                s_registry.registerJoinPoint(
                    JoinPointType.HANDLER,
                    handlerHash,
                    handlerSignature,
                    m_classHash,
                    exceptionInstance.getClass(),
                    withinClassInfo,
                    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 joinPointMetaDataMap = s_registry.getJoinPointMetaData(m_classHash, handlerHash);
            JoinPointMetaData joinPointMetaData = (JoinPointMetaData) joinPointMetaDataMap.get(PointcutType.HANDLER);
            AdviceIndexInfo[] adviceIndexes = joinPointMetaData.adviceIndexes;
            List cflowExpressions = joinPointMetaData.cflowExpressions;
            Pointcut cflowPointcut = joinPointMetaData.cflowPointcut;
            initCflowManagement(cflowPointcut, joinPointInfo);
            joinPoint = createCatchClauseJoinPoint(
                exceptionInstance.getClass(),
                m_targetClass,
                handlerSignature,
                adviceIndexes,
                joinPointMetaData,
                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
        joinPoint.setTarget(targetInstance);
        ((CatchClauseRtti) joinPoint.getRtti()).setParameterValue(exceptionInstance);
        enterCflow(joinPointInfo);
        try {
            joinPoint.proceed();
        } finally {
            exitCflow(joinPointInfo);
        }
    }

    /**
     * Creates a class info instance out of a class instance.
     * 
     * @param klass
     * @return class info
     */
    private ClassInfo createClassInfo(final Class klass) {
        ClassInfo classInfo = m_classInfoRepository.getClassInfo(klass.getName());
        if (classInfo == null) {
            classInfo = JavaClassInfo.getClassInfo(klass);
        }
        return classInfo;
    }

    /**
     * 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) {
        synchronized (joinPointInfo) {
            joinPointInfo.invocations++;
            if (joinPointInfo.state == JoinPointState.REDEFINED) {
                joinPointInfo.invocations = 0L;
            } else if (joinPointInfo.invocations == JIT_COMPILATION_BOUNDRY) {
                Map joinPointMetaDataMap = s_registry.getJoinPointMetaData(m_classHash, joinPointHash);
                if (joinPointMetaDataMap.containsKey(pointcutType)) {
                    JoinPointMetaData joinPointMetaData = (JoinPointMetaData) joinPointMetaDataMap.get(pointcutType);
                    AdviceIndexInfo[] adviceIndexes = joinPointMetaData.adviceIndexes;
                    joinPointInfo.joinPoint = JitCompiler.compileJoinPoint(
                        joinPointHash,
                        joinPointType,
                        pointcutType,
                        adviceIndexes,
                        declaringClass,
                        targetClass,
                        m_system,
                        thisInstance,
                        targetInstance,
                        m_hotswapCount);
                    joinPointInfo.isJitCompiled = true;
                }
            }
        }
    }

    /**
     * Create a method join point.
     * 
     * @param methodHash
     * @param joinPointType
     * @param declaringClass
     * @param adviceIndexes
     * @param joinPointMetaData
     * @param thisInstance
     * @param targetInstance
     * @return
     */
    private final MethodJoinPoint createMethodJoinPoint(
        final int methodHash,
        final int joinPointType,
        final Class declaringClass,
        final AdviceIndexInfo[] adviceIndexes,
        final JoinPointMetaData joinPointMetaData,
        final Object thisInstance,
        final Object targetInstance) {
        MethodTuple methodTuple = AspectRegistry.getMethodTuple(declaringClass, methodHash);
        Class declaringType = methodTuple.getDeclaringClass();
        MethodSignatureImpl signature = new MethodSignatureImpl(declaringType, methodTuple);
        Rtti rtti = new MethodRttiImpl(signature, thisInstance, targetInstance);
        return new MethodJoinPoint(
            joinPointType,
            m_targetClass,
            signature,
            rtti,
            joinPointMetaData,
            createAroundAdviceExecutor(adviceIndexes, joinPointType),
            createBeforeAdviceExecutor(adviceIndexes),
            createAfterAdviceExecutor(adviceIndexes));
    }

    /**
     * Create a constructor join point.
     * 
     * @param constructorHash
     * @param joinPointType
     * @param declaringClass
     * @param adviceIndexes
     * @param joinPointMetaData
     * @param thisInstance
     * @param targetInstance
     * @return
     */
    private final JoinPoint createConstructorJoinPoint(
        final int constructorHash,
        final int joinPointType,
        final Class declaringClass,
        final AdviceIndexInfo[] adviceIndexes,
        final JoinPointMetaData joinPointMetaData,
        final Object thisInstance,
        final Object targetInstance) {
        ConstructorTuple constructorTuple = AspectRegistry.getConstructorTuple(declaringClass, constructorHash);
        Class declaringType = constructorTuple.getDeclaringClass();
        ConstructorSignatureImpl signature = new ConstructorSignatureImpl(declaringType, constructorTuple);
        Rtti rtti = new ConstructorRttiImpl(signature, thisInstance, targetInstance);
        return new ConstructorJoinPoint(
            joinPointType,
            m_targetClass,
            signature,
            rtti,
            joinPointMetaData,
            createAroundAdviceExecutor(adviceIndexes, joinPointType),
            createBeforeAdviceExecutor(adviceIndexes),
            createAfterAdviceExecutor(adviceIndexes));
    }

    /**
     * Create a field join point.
     * 
     * @param fieldHash
     * @param joinPointType
     * @param declaringClass
     * @param adviceIndexes
     * @param joinPointMetaData
     * @param thisInstance
     * @param targetInstance
     * @return
     */
    private final JoinPoint createFieldJoinPoint(
        final int fieldHash,
        final int joinPointType,
        final Class declaringClass,
        final AdviceIndexInfo[] adviceIndexes,
        final JoinPointMetaData joinPointMetaData,
        final Object thisInstance,
        final Object targetInstance) {
        Field field = AspectRegistry.getField(declaringClass, fieldHash);
        FieldSignatureImpl signature = new FieldSignatureImpl(declaringClass, field);
        Rtti rtti = new FieldRttiImpl(signature, thisInstance, targetInstance);
        return new FieldJoinPoint(
            joinPointType,
            m_targetClass,
            signature,
            rtti,
            joinPointMetaData,
            createAroundAdviceExecutor(adviceIndexes, joinPointType),
            createBeforeAdviceExecutor(adviceIndexes),
            createAfterAdviceExecutor(adviceIndexes));
    }

    /**
     * Create a catch clause join point.
     * 
     * @param exceptionClass
     * @param declaringClass
     * @param catchClauseSignature
     * @param adviceIndexes
     * @param joinPointMetaData
     * @param thisInstance
     * @param targetInstance
     * @return
     */
    private final JoinPoint createCatchClauseJoinPoint(
        final Class exceptionClass,
        final Class declaringClass,
        final String catchClauseSignature,
        final AdviceIndexInfo[] adviceIndexes,
        final JoinPointMetaData joinPointMetaData,
        final Object thisInstance,
        final Object targetInstance) {
        CatchClauseSignatureImpl signature = new CatchClauseSignatureImpl(
            exceptionClass,
            declaringClass,
            catchClauseSignature);
        Rtti rtti = new CatchClauseRttiImpl(signature, thisInstance, targetInstance);
        return new CatchClauseJoinPoint(m_targetClass, signature, rtti, joinPointMetaData, 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 AdviceIndexInfo[] adviceIndexes,
        final int joinPointType) {
        return new AroundAdviceExecutor(extractAroundAdvice(adviceIndexes), joinPointType);
    }

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

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

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

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

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

    /**
     * Reset the join point manager.
     * 
     * @param klass
     */
    public static synchronized void reset(Class klass) {
        JoinPointManager oldJoinPointManager = getJoinPointManager(klass, "N/A/runtime");

        // flush JP Registry
        s_registry.reset(klass.hashCode());
        JoinPointManager joinPointManager = new JoinPointManager(klass, oldJoinPointManager.m_hotswapCount + 1);
        s_managers.put(klass, joinPointManager);
        try {
            Field jpMan = klass.getDeclaredField(TransformationUtil.JOIN_POINT_MANAGER_FIELD);
            jpMan.setAccessible(true);
            jpMan.set(null, joinPointManager);
        } catch (Exception e) {
            System.err.println("ERROR: Unable to propagate JoinPointManager");
            e.printStackTrace();
        }
        joinPointManager.m_joinPoints = new ThreadLocal[0];
    }

    /**
     * Records entering into cflow.
     * 
     * @param joinPointInfo
     */
    private void enterCflow(final JoinPointInfo joinPointInfo) throws Throwable {
        AdviceInfo enter = joinPointInfo.enterCflow;
        if (enter != null) {
            enter.getAspectManager().getAspectContainer(enter.getAspectIndex()).invokeAdvice(
                enter.getMethodIndex(),
                joinPointInfo.joinPoint);
        }
    }

    /**
     * Records exiting from cflow.
     * 
     * @param joinPointInfo
     */
    private void exitCflow(final JoinPointInfo joinPointInfo) throws Throwable {
        AdviceInfo exit = joinPointInfo.exitCflow;
        if (exit != null) {
            exit.getAspectManager().getAspectContainer(exit.getAspectIndex()).invokeAdvice(
                exit.getMethodIndex(),
                joinPointInfo.joinPoint);
        }
    }

    /**
     * Initializes the cflow management.
     * 
     * @param cflowPointcut
     * @param joinPointInfo
     */
    private void initCflowManagement(final Pointcut cflowPointcut, final JoinPointInfo joinPointInfo) {
        if (cflowPointcut != null) {
            AdviceInfo[] beforeAdviceIndexes = cflowPointcut.getBeforeAdviceIndexes();
            AdviceInfo[] afterAdviceIndexes = cflowPointcut.getAfterAdviceIndexes();
            if ((beforeAdviceIndexes.length != 0) && (afterAdviceIndexes.length != 0)) {
                joinPointInfo.enterCflow = beforeAdviceIndexes[0];
                joinPointInfo.exitCflow = afterAdviceIndexes[0];
            }
        }
    }

    /**
     * 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;

        public AdviceInfo enterCflow;

        public AdviceInfo exitCflow;
    }
}