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

import org.codehaus.aspectwerkz.annotation.AnnotationInfo;
import org.codehaus.aspectwerkz.expression.ast.ASTAnd;
import org.codehaus.aspectwerkz.expression.ast.ASTAttribute;
import org.codehaus.aspectwerkz.expression.ast.ASTCall;
import org.codehaus.aspectwerkz.expression.ast.ASTCflow;
import org.codehaus.aspectwerkz.expression.ast.ASTCflowBelow;
import org.codehaus.aspectwerkz.expression.ast.ASTClassPattern;
import org.codehaus.aspectwerkz.expression.ast.ASTConstructorPattern;
import org.codehaus.aspectwerkz.expression.ast.ASTExecution;
import org.codehaus.aspectwerkz.expression.ast.ASTExpression;
import org.codehaus.aspectwerkz.expression.ast.ASTFieldPattern;
import org.codehaus.aspectwerkz.expression.ast.ASTGet;
import org.codehaus.aspectwerkz.expression.ast.ASTHandler;
import org.codehaus.aspectwerkz.expression.ast.ASTMethodPattern;
import org.codehaus.aspectwerkz.expression.ast.ASTModifier;
import org.codehaus.aspectwerkz.expression.ast.ASTNot;
import org.codehaus.aspectwerkz.expression.ast.ASTOr;
import org.codehaus.aspectwerkz.expression.ast.ASTParameter;
import org.codehaus.aspectwerkz.expression.ast.ASTPointcutReference;
import org.codehaus.aspectwerkz.expression.ast.ASTRoot;
import org.codehaus.aspectwerkz.expression.ast.ASTSet;
import org.codehaus.aspectwerkz.expression.ast.ASTStaticInitialization;
import org.codehaus.aspectwerkz.expression.ast.ASTWithin;
import org.codehaus.aspectwerkz.expression.ast.ASTWithinCode;
import org.codehaus.aspectwerkz.expression.ast.ExpressionParserVisitor;
import org.codehaus.aspectwerkz.expression.ast.Node;
import org.codehaus.aspectwerkz.expression.ast.SimpleNode;
import org.codehaus.aspectwerkz.expression.ast.ASTArgs;
import org.codehaus.aspectwerkz.expression.ast.ASTArgParameter;
import org.codehaus.aspectwerkz.expression.regexp.TypePattern;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.ConstructorInfo;
import org.codehaus.aspectwerkz.reflect.FieldInfo;
import org.codehaus.aspectwerkz.reflect.MemberInfo;
import org.codehaus.aspectwerkz.reflect.MethodInfo;
import org.codehaus.aspectwerkz.reflect.ReflectionInfo;
import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
import org.codehaus.aspectwerkz.reflect.StaticInitializationInfo;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.codehaus.aspectwerkz.expression.ast.ASTHasField;
import org.codehaus.aspectwerkz.expression.ast.ASTHasMethod;
import org.codehaus.aspectwerkz.expression.ast.ASTTarget;
import org.codehaus.aspectwerkz.expression.ast.ASTThis;
import org.codehaus.aspectwerkz.util.Util;

/**
 * The expression visitor.
 * If a runtime residual is required (target => instance of check sometimes), Undeterministic matching is used.
 *
 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
 * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur </a>
 * @author Michael Nascimento
 * @author <a href="mailto:the_mindstorm@evolva.ro">Alex Popescu</a>
 */
public class ExpressionVisitor implements ExpressionParserVisitor {

    protected Node m_root;
    protected String m_expression;
    protected String m_namespace;

    /**
     * The expressionInfo this visitor is built on
     */
    protected ExpressionInfo m_expressionInfo;

    /**
     * Creates a new expression.
     *
     * @param expressionInfo the expressionInfo this visitor is built on for expression with signature
     * @param expression     the expression as a string
     * @param namespace      the namespace
     * @param root           the AST root
     */
    public ExpressionVisitor(final ExpressionInfo expressionInfo,
                             final String expression,
                             final String namespace,
                             final Node root) {
        m_expressionInfo = expressionInfo;
        m_expression = expression;
        m_namespace = namespace;
        m_root = root;
    }

    /**
     * Matches the expression context.
     * If undetermined, assume true.
     * Do not use for poincut reference - see matchUndeterministic
     *
     * @param context
     * @return
     */
    public boolean match(final ExpressionContext context) {
        Boolean match = ((Boolean) visit(m_root, context));
        // undeterministic is assumed to be "true" at this stage
        // since it won't be composed anymore with a NOT (unless
        // thru pointcut reference ie a new visitor)
        return (match != null) ? match.booleanValue() : true;
    }

    protected Boolean matchUndeterministic(final ExpressionContext context) {
        Boolean match = ((Boolean) visit(m_root, context));
        return match;
    }

    // ============ Boot strap =============
    public Object visit(Node node, Object data) {
        return node.jjtGetChild(0).jjtAccept(this, data);
    }

    public Object visit(SimpleNode node, Object data) {
        return node.jjtGetChild(0).jjtAccept(this, data);
    }

    public Object visit(ASTRoot node, Object data) {
        return node.jjtGetChild(0).jjtAccept(this, data);
    }

    public Object visit(ASTExpression node, Object data) {
        return node.jjtGetChild(0).jjtAccept(this, data);
    }

    // ============ Logical operators =============
    public Object visit(ASTOr node, Object data) {
        // the AND and OR can have more than 2 nodes [see jjt grammar]
        Boolean matchL = (Boolean) node.jjtGetChild(0).jjtAccept(this, data);
        Boolean matchR = (Boolean) node.jjtGetChild(1).jjtAccept(this, data);
        Boolean intermediate = Undeterministic.or(matchL, matchR);
        for (int i = 2; i < node.jjtGetNumChildren(); i++) {
            Boolean matchNext = (Boolean) node.jjtGetChild(i).jjtAccept(this, data);
            intermediate = Undeterministic.or(intermediate, matchNext);
        }
        return intermediate;
    }

    public Object visit(ASTAnd node, Object data) {
        // the AND and OR can have more than 2 nodes [see jjt grammar]
        Boolean matchL = (Boolean) node.jjtGetChild(0).jjtAccept(this, data);
        Boolean matchR = (Boolean) node.jjtGetChild(1).jjtAccept(this, data);
        Boolean intermediate = Undeterministic.and(matchL, matchR);
        for (int i = 2; i < node.jjtGetNumChildren(); i++) {
            Boolean matchNext = (Boolean) node.jjtGetChild(i).jjtAccept(this, data);
            intermediate = Undeterministic.and(intermediate, matchNext);
        }
        return intermediate;
    }

    public Object visit(ASTNot node, Object data) {
        Boolean match = (Boolean) node.jjtGetChild(0).jjtAccept(this, data);
        return Undeterministic.not(match);
    }

    // ============ Pointcut types =============
    public Object visit(ASTPointcutReference node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        ExpressionNamespace namespace = ExpressionNamespace.getNamespace(m_namespace);
        ExpressionVisitor expression = namespace.getExpression(node.getName());
        return expression.matchUndeterministic(context);
    }

    public Object visit(ASTExecution node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        if (context.hasExecutionPointcut() && (context.hasMethodInfo() || context.hasConstructorInfo())) {
            return visitAnnotatedNode(node, context.getReflectionInfo());
        } else {
            return Boolean.FALSE;
        }
    }

    public Object visit(ASTCall node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        if (context.hasCallPointcut() && (context.hasMethodInfo() || context.hasConstructorInfo())) {
            return visitAnnotatedNode(node, context.getReflectionInfo());
        } else {
            return Boolean.FALSE;
        }
    }

    public Object visit(ASTSet node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        if (context.hasSetPointcut() && context.hasFieldInfo()) {
            return visitAnnotatedNode(node, context.getReflectionInfo());
        } else {
            return Boolean.FALSE;
        }
    }

    public Object visit(ASTGet node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        if (context.hasGetPointcut() && context.hasFieldInfo()) {
            return visitAnnotatedNode(node, context.getReflectionInfo());
        } else {
            return Boolean.FALSE;
        }
    }

    public Object visit(ASTHandler node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        if (context.hasHandlerPointcut() && context.hasClassInfo()) {
            return node.jjtGetChild(0).jjtAccept(this, context.getReflectionInfo());
        } else {
            return Boolean.FALSE;
        }
    }

    public Object visit(ASTStaticInitialization node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        
        if (context.hasStaticInitializationPointcut() && context.hasReflectionInfo()) {
        	ReflectionInfo reflectInfo = context.getReflectionInfo();
        	
        	if(reflectInfo instanceof StaticInitializationInfo) {
        		ClassInfo declaringClassInfo = ((StaticInitializationInfo) reflectInfo).getDeclaringType();

                // In an annotated subtree, only the last child node may represent the pattern
                Node patternNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
                if (!(patternNode instanceof ASTAttribute)) {
                    Boolean matchPattern = (Boolean) patternNode.jjtAccept(this, reflectInfo);
                    if (Boolean.FALSE.equals(matchPattern)) {
                        return Boolean.FALSE;
                    }
                }

                // match on annotation if no pattern node or matched already
            	boolean matchedAnnotations = visitAttributes(node, declaringClassInfo);
            	if (!matchedAnnotations) {
            		return Boolean.FALSE;
            	} else {
                    return Boolean.TRUE;
                }
        	} else {
        		return Boolean.FALSE;
        	}
        } else {
            return Boolean.FALSE;
        }
    }

    public Object visit(ASTWithin node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        if (context.hasWithinReflectionInfo()) {
        	ReflectionInfo reflectInfo = context.getWithinReflectionInfo();
        	ReflectionInfo withinInfo  = null;
        	
        	if(reflectInfo instanceof MemberInfo) {
        		withinInfo = ((MemberInfo) reflectInfo).getDeclaringType();
        	} else if(reflectInfo instanceof ClassInfo) {
        		withinInfo = reflectInfo;
        	} else {
        		return Boolean.FALSE;
            }
	        return visitAnnotatedNode(
	        		node,
					withinInfo);
        } else {
            return null;
        }
    }

    public Object visit(ASTWithinCode node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        
        if(!context.hasWithinReflectionInfo()) {
        	return null;
        }
        
        ReflectionInfo reflectInfo = context.getWithinReflectionInfo();
        
        if(node.isStaticInitializer()) {
    		if(reflectInfo instanceof StaticInitializationInfo) {
    			// Ignore the ASTStaticInitialization node in this context
    			SimpleNode staticClinitNode = (SimpleNode) node.jjtGetChild(0);
           		ClassInfo declaringClassInfo = ((StaticInitializationInfo) reflectInfo).getDeclaringType();
        		
            	boolean matchedAnnotations = visitAttributes(staticClinitNode, declaringClassInfo);
            	if(!matchedAnnotations) {
            		return Boolean.FALSE;
            	}
        		
            	// In an annotated subtree, the last child node represents the pattern
            	Node lastNode = staticClinitNode.jjtGetChild(staticClinitNode.jjtGetNumChildren() - 1);
            	if(lastNode instanceof ASTAttribute) {
            		return Boolean.TRUE;
            	} else {
                    return lastNode.jjtAccept(this, reflectInfo);
                }
    		} else {
    			return Boolean.FALSE;
    		}
        } else {
	        return visitAnnotatedNode(
	        		node,
					reflectInfo
					);
        } 
    }


    public Object visit(ASTHasMethod node, Object data) {
        ExpressionContext context = (ExpressionContext) data;

        // we are matching on the CALLER info
        // for execution() pointcut, this is equals to CALLEE info
        ReflectionInfo info = context.getWithinReflectionInfo();
        ClassInfo classInfo = info instanceof MemberInfo
				? ((MemberInfo) info).getDeclaringType()
				: (ClassInfo) info;
        
        Node patternNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
        boolean hasPatternNode = !(patternNode instanceof ASTAttribute);

        MethodInfo[] methodInfos = classInfo.getMethods();
        for (int i = 0; i < methodInfos.length; i++) {
            if (hasPatternNode) {
                if(Boolean.FALSE.equals(patternNode.jjtAccept(this, methodInfos[i]))) {
                    continue;
                }
            }

            boolean matchAnnotations = visitAttributes(node, methodInfos[i]);
            if (matchAnnotations) {
                return Boolean.TRUE;
            }
		}

        ConstructorInfo[] constructorInfos = classInfo.getConstructors();
        for (int i = 0; i < constructorInfos.length; i++) {
            if (hasPatternNode) {
                if(Boolean.FALSE.equals(patternNode.jjtAccept(this, constructorInfos[i]))) {
                    continue;
                }
            }

            boolean matchAnnotations = visitAttributes(node, constructorInfos[i]);
            if (matchAnnotations) {
                return Boolean.TRUE;
            }
        }

        return Boolean.FALSE;
    }

    public Object visit(ASTHasField node, Object data) {
        ExpressionContext context = (ExpressionContext) data;

        // we are matching on the CALLER info
        // for execution() pointcut, this is equals to CALLEE info
        ReflectionInfo info = context.getWithinReflectionInfo();
        ClassInfo classInfo = (info instanceof MemberInfo) ?
                              ((MemberInfo) info).getDeclaringType() : (ClassInfo) info;

        Node patternNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
        boolean hasPatternNode = !(patternNode instanceof ASTAttribute);

        FieldInfo[] fieldInfos = classInfo.getFields();
        for (int i = 0; i < fieldInfos.length; i++) {
            if (hasPatternNode) {
                if (Boolean.FALSE.equals(patternNode.jjtAccept(this, fieldInfos[i]))) {
                    continue;
                }
            }

        	boolean matchAnnotations = visitAttributes(node, fieldInfos[i]);
        	if (matchAnnotations) {
        		return Boolean.TRUE;
        	}
        }

        return Boolean.FALSE;
    }

    public Object visit(ASTTarget node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        ReflectionInfo info = context.getReflectionInfo();

//        //TODO - seems to be the case for AJ - not intuitive
//        if (info instanceof ConstructorInfo) {
//            // target(..) does not match for constructors
//            return Boolean.FALSE;
//        }
        ClassInfo declaringType = null;
        if (info instanceof MemberInfo) {
            // if method/field is static, target(..) is evaluated to false
            if (Modifier.isStatic(((MemberInfo) info).getModifiers())) {
                return Boolean.FALSE;
            }

            declaringType = ((MemberInfo) info).getDeclaringType();
        } else if (info instanceof ClassInfo) {
            declaringType = (ClassInfo) info;
        } else {
            return Boolean.FALSE;
        }

        String boundedTypeName = node.getBoundedType(m_expressionInfo);
        // check if the context we match is an interface call, while the bounded type of target(..) is not an
        // interface. In such a case we will need a runtime check
        if (declaringType.isInterface()) {
            // if we are a instanceof (subinterface) of the bounded type, then we don't need a runtime check
            if (ClassInfoHelper.instanceOf(declaringType, boundedTypeName)) {
                return Boolean.TRUE;
            } else {
                //System.out.println("*** RT check for "  + boundedTypeName + " when I am " + declaringType.getName());
                // a runtime check with instance of will be required
                return null;
            }
        } else {
            return Util.booleanValueOf(ClassInfoHelper.instanceOf(declaringType, boundedTypeName));
        }
    }

    public Object visit(ASTThis node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        // for execution pointcut, this(..) is used to match the callee info
        // and we are assuming here that withinInfo is properly set to reflectionInfo
        if (context.hasWithinReflectionInfo()) {
            ReflectionInfo withinInfo = context.getWithinReflectionInfo();
            if (withinInfo instanceof MemberInfo) {
                // if method is static (callee for execution or caller for call/get/set), this(..) is evaluated to false
                if (Modifier.isStatic(((MemberInfo) withinInfo).getModifiers())) {
                    return Boolean.FALSE;
                }
                return Util.booleanValueOf(
                        ClassInfoHelper.instanceOf(
                                ((MemberInfo) withinInfo).getDeclaringType(),
                                node.getBoundedType(m_expressionInfo)
                        )
                );
            } else if (withinInfo instanceof ClassInfo) {
                return Util.booleanValueOf(
                        ClassInfoHelper.instanceOf((ClassInfo) withinInfo, node.getBoundedType(m_expressionInfo))
                );
            }
        }
        return Boolean.FALSE;
    }

    public Object visit(ASTCflow node, Object data) {
        return null;
    }

    public Object visit(ASTCflowBelow node, Object data) {
        return null;
    }

    // ============ Patterns =============
    public Object visit(ASTClassPattern node, Object data) {
        if (data instanceof ClassInfo) {
	        ClassInfo classInfo = (ClassInfo) data;
	        TypePattern typePattern = node.getTypePattern();

	        if (typePattern.matchType(classInfo)
	        	&& visitModifiers(node, classInfo)) {
	            return Boolean.TRUE;
	        } else {
	            return Boolean.FALSE;
	        }
        } else if (data instanceof StaticInitializationInfo) {
        	ClassInfo classInfo = ((StaticInitializationInfo) data).getDeclaringType();
        	
        	if(node.getTypePattern().matchType(classInfo)) {
        		return Boolean.TRUE;
        	} else {
        		return Boolean.FALSE;
        	}
        	
//        	return new Boolean(node.getTypePattern().matchType(classInfo));
        }
        
        return Boolean.FALSE;
    }

    public Object visit(ASTMethodPattern node, Object data) {
        if (data instanceof MethodInfo) {
            MethodInfo methodInfo = (MethodInfo) data;
            if (node.getMethodNamePattern().matches(methodInfo.getName())
                && node.getDeclaringTypePattern().matchType(methodInfo.getDeclaringType())
                && node.getReturnTypePattern().matchType(methodInfo.getReturnType())
                && visitModifiers(node, methodInfo)
                && visitParameters(node, methodInfo.getParameterTypes())) {
                return Boolean.TRUE;
            }
        }

        return Boolean.FALSE;
    }

    public Object visit(ASTConstructorPattern node, Object data) {
        if (data instanceof ConstructorInfo) {
            ConstructorInfo constructorMetaData = (ConstructorInfo) data;
            if (node.getDeclaringTypePattern().matchType(constructorMetaData.getDeclaringType())
                && visitModifiers(node, constructorMetaData)
                && visitParameters(node, constructorMetaData.getParameterTypes())) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }

    public Object visit(ASTFieldPattern node, Object data) {
        if (data instanceof FieldInfo) {
            FieldInfo fieldInfo = (FieldInfo) data;
            if (node.getFieldNamePattern().matches(fieldInfo.getName())
                && node.getDeclaringTypePattern().matchType(fieldInfo.getDeclaringType())
                && node.getFieldTypePattern().matchType(fieldInfo.getType())
                && visitModifiers(node, fieldInfo)) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }

    public Object visit(ASTParameter node, Object data) {
        ClassInfo parameterType = (ClassInfo) data;
        if (node.getDeclaringClassPattern().matchType(parameterType)) {
            return Boolean.TRUE;
        } else {
            return Boolean.FALSE;
        }
    }

    public Object visit(ASTArgs node, Object data) {
        ExpressionContext ctx = (ExpressionContext) data;
        if (node.jjtGetNumChildren() <= 0) {
            // args(EMPTY)
            return (getParametersCount(ctx) == 0) ? Boolean.TRUE : Boolean.FALSE;
        } else {
            // check for ".." as first node
            int expressionParameterCount = node.jjtGetNumChildren();// the number of node minus eager one.
            boolean isFirstArgEager = ((ASTArgParameter) node.jjtGetChild(0)).getTypePattern().isEagerWildCard();
            boolean isLastArgEager = ((ASTArgParameter) node.jjtGetChild(node.jjtGetNumChildren() - 1))
                    .getTypePattern().isEagerWildCard();
            // args(..)
            if (isFirstArgEager && expressionParameterCount == 1) {
                return Boolean.TRUE;
            }
            int contextParametersCount = getParametersCount(ctx);
            if (isFirstArgEager && isLastArgEager) {
                expressionParameterCount -= 2;
                if (expressionParameterCount == 0) {
                    // expression is "args(.., ..)"
                    return Boolean.TRUE;
                }
                // we need to find a starting position - args(..,int, bar, ..)
                // foo(int) //int is ok
                // foo(bar,int,bar) //int is ok
                // foo(bar,int,foo,int,bar) // int is ok, but then we fail, so move on to next..
                int matchCount = 0;
                int ictx = 0;
                for (int iexp = 0; iexp < expressionParameterCount; iexp++) {
                    if (ictx >= contextParametersCount) {
                        // too many args in args()
                        matchCount = -1;
                        break;
                    }
                    ctx.setCurrentTargetArgsIndex(ictx);
                    // do we have an eager wildcard in the middle ?
                    boolean isEager = ((ASTArgParameter) node.jjtGetChild(iexp + 1)).getTypePattern().isEagerWildCard();
                    if (isEager) {
                        // TODO - ignore for now, but not really supported - eager in the middle will match one
                    }
                    if (Boolean.TRUE.equals((Boolean) node.jjtGetChild(iexp + 1).jjtAccept(this, ctx))) {
                        matchCount += 1;
                        ictx++;
                    } else {
                        // assume matched by starting ".." and rewind expression index
                        matchCount = 0;
                        ictx++;
                        iexp = -1;
                    }
                }
                if (matchCount == expressionParameterCount) {
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else if (isFirstArgEager) {
                expressionParameterCount--;
                if (contextParametersCount >= expressionParameterCount) {
                    // do a match from last to first, break when args() nodes are exhausted
                    for (int i = 0; (i < contextParametersCount) && (expressionParameterCount - i >= 0); i++) {
                        ctx.setCurrentTargetArgsIndex(contextParametersCount - 1 - i);
                        if (Boolean.TRUE.equals(
                                (Boolean) node.jjtGetChild(expressionParameterCount - i).jjtAccept(
                                        this,
                                        ctx
                                )
                        )) {
                            ;//go on with "next" arg
                        } else {
                            return Boolean.FALSE;
                        }
                    }
                    return Boolean.TRUE;
                } else {
                    //args() as more args than context we try to match
                    return Boolean.FALSE;
                }
            } else if (isLastArgEager) {
                expressionParameterCount--;
                if (contextParametersCount >= expressionParameterCount) {
                    // do a match from first to last, break when args() nodes are exhausted
                    for (int i = 0; (i < contextParametersCount) && (i < expressionParameterCount); i++) {
                        ctx.setCurrentTargetArgsIndex(i);
                        if (Boolean.TRUE.equals((Boolean) node.jjtGetChild(i).jjtAccept(this, ctx))) {
                            ;//go on with next arg
                        } else {
                            return Boolean.FALSE;
                        }
                    }
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else {
                // no eager wildcard in args()
                // check that args length are equals
                if (expressionParameterCount == contextParametersCount) {
                    for (int i = 0; i < node.jjtGetNumChildren(); i++) {
                        ctx.setCurrentTargetArgsIndex(i);
                        if (Boolean.TRUE.equals((Boolean) node.jjtGetChild(i).jjtAccept(this, ctx))) {
                            ;//go on with next arg
                        } else {
                            return Boolean.FALSE;
                        }
                    }
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            }
        }
    }

    public Object visit(ASTArgParameter node, Object data) {
        //TODO we are not doing any hierarchical test when the arg is bound
        // => args(e) and before(Exception e) will not mathch on catch(SubException e) ..
        // is that required ? how AJ syntax behaves ?

        TypePattern typePattern = node.getTypePattern();
        TypePattern realPattern = typePattern;

        // check if the arg is in the pointcut signature. In such a case, use the declared type
        //TODO can we improve that with a lazy attach of the realTypePattern to the node
        // and a method that always return the real pattern
        // It must be lazy since args are not added at info ctor time [can be refactored..]
        // do some filtering first to avoid unnecessary map lookup
        
        int pointcutArgIndex = -1;
        if (typePattern.getPattern().indexOf(".") < 0) {
            String boundedType = m_expressionInfo.getArgumentType(typePattern.getPattern());
            if (boundedType != null) {
                pointcutArgIndex = m_expressionInfo.getArgumentIndex(typePattern.getPattern());
                realPattern = TypePattern.compileTypePattern(boundedType, SubtypePatternType.NOT_HIERARCHICAL);
            }
        }
        // grab parameter from context
        ExpressionContext ctx = (ExpressionContext) data;
        ClassInfo argInfo = null;
        try {
            if (ctx.getReflectionInfo() instanceof MethodInfo) {
                argInfo = ((MethodInfo) ctx.getReflectionInfo()).getParameterTypes()[ctx.getCurrentTargetArgsIndex()];
            } else if (ctx.getReflectionInfo() instanceof ConstructorInfo) {
                argInfo = ((ConstructorInfo) ctx.getReflectionInfo()).getParameterTypes()[ctx
                        .getCurrentTargetArgsIndex()];
            } else if (ctx.getReflectionInfo() instanceof FieldInfo) {
                argInfo = ((FieldInfo) ctx.getReflectionInfo()).getType();
            } else if (ctx.getPointcutType().equals(PointcutType.HANDLER) && ctx.getReflectionInfo() instanceof ClassInfo) {
                argInfo = (ClassInfo) ctx.getReflectionInfo();
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            // ExpressionContext args are exhausted
            return Boolean.FALSE;
        }
        if (realPattern.matchType(argInfo)) {
            return Boolean.TRUE;
        } else {
            return Boolean.FALSE;
        }
    }

    public Object visit(ASTAttribute node, Object data) {
        boolean matchAnnotation = false;
        List annotations = (List) data;
        for (Iterator it = annotations.iterator(); it.hasNext();) {
            AnnotationInfo annotation = (AnnotationInfo) it.next();
            if (annotation.getName().equals(node.getName())) {
                matchAnnotation = true;
            }
        }
        if (node.isNot()) {
            return Util.booleanValueOf(!matchAnnotation);
        } else {
            return Util.booleanValueOf(matchAnnotation);
        }
    }

    public Object visit(ASTModifier node, Object data) {
        ReflectionInfo refInfo = (ReflectionInfo) data;
        int modifiersToMatch = refInfo.getModifiers();
        int modifierPattern = node.getModifier();
        if (node.isNot()) {
            if ((modifierPattern & Modifier.PUBLIC) != 0) {
                if (((modifiersToMatch & Modifier.PUBLIC) == 0)) {
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else if ((modifierPattern & Modifier.PROTECTED) != 0) {
                if ((modifiersToMatch & Modifier.PROTECTED) == 0) {
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else if ((modifierPattern & Modifier.PRIVATE) != 0) {
                if ((modifiersToMatch & Modifier.PRIVATE) == 0) {
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else if ((modifierPattern & Modifier.STATIC) != 0) {
                if ((modifiersToMatch & Modifier.STATIC) == 0) {
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else if ((modifierPattern & Modifier.SYNCHRONIZED) != 0) {
                if ((modifiersToMatch & Modifier.SYNCHRONIZED) == 0) {
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else if ((modifierPattern & Modifier.FINAL) != 0) {
                if ((modifiersToMatch & Modifier.FINAL) == 0) {
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else if ((modifierPattern & Modifier.TRANSIENT) != 0) {
                if ((modifiersToMatch & Modifier.TRANSIENT) == 0) {
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else if ((modifierPattern & Modifier.VOLATILE) != 0) {
                if ((modifiersToMatch & Modifier.VOLATILE) == 0) {
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else if ((modifierPattern & Modifier.STRICT) != 0) {
                if ((modifiersToMatch & Modifier.STRICT) == 0) {
                    return Boolean.TRUE;
                } else {
                    return Boolean.FALSE;
                }
            } else {
                return Boolean.FALSE;
            }
        } else {
            if ((modifierPattern & Modifier.PUBLIC) != 0) {
                if (((modifiersToMatch & Modifier.PUBLIC) == 0)) {
                    return Boolean.FALSE;
                } else {
                    return Boolean.TRUE;
                }
            } else if ((modifierPattern & Modifier.PROTECTED) != 0) {
                if ((modifiersToMatch & Modifier.PROTECTED) == 0) {
                    return Boolean.FALSE;
                } else {
                    return Boolean.TRUE;
                }
            } else if ((modifierPattern & Modifier.PRIVATE) != 0) {
                if ((modifiersToMatch & Modifier.PRIVATE) == 0) {
                    return Boolean.FALSE;
                } else {
                    return Boolean.TRUE;
                }
            } else if ((modifierPattern & Modifier.STATIC) != 0) {
                if ((modifiersToMatch & Modifier.STATIC) == 0) {
                    return Boolean.FALSE;
                } else {
                    return Boolean.TRUE;
                }
            } else if ((modifierPattern & Modifier.SYNCHRONIZED) != 0) {
                if ((modifiersToMatch & Modifier.SYNCHRONIZED) == 0) {
                    return Boolean.FALSE;
                } else {
                    return Boolean.TRUE;
                }
            } else if ((modifierPattern & Modifier.FINAL) != 0) {
                if ((modifiersToMatch & Modifier.FINAL) == 0) {
                    return Boolean.FALSE;
                } else {
                    return Boolean.TRUE;
                }
            } else if ((modifierPattern & Modifier.TRANSIENT) != 0) {
                if ((modifiersToMatch & Modifier.TRANSIENT) == 0) {
                    return Boolean.FALSE;
                } else {
                    return Boolean.TRUE;
                }
            } else if ((modifierPattern & Modifier.VOLATILE) != 0) {
                if ((modifiersToMatch & Modifier.VOLATILE) == 0) {
                    return Boolean.FALSE;
                } else {
                    return Boolean.TRUE;
                }
            } else if ((modifierPattern & Modifier.STRICT) != 0) {
                if ((modifiersToMatch & Modifier.STRICT) == 0) {
                    return Boolean.FALSE;
                } else {
                    return Boolean.TRUE;
                }
            } else {
                return Boolean.TRUE;
            }
        }
    }

    protected boolean visitAttributes(SimpleNode node, ReflectionInfo refInfo) {
        int nrChildren = node.jjtGetNumChildren();
        if (nrChildren != 0) {
            for (int i = 0; i < nrChildren; i++) {
                Node child = node.jjtGetChild(i);
                if (child instanceof ASTAttribute) {
                    List annotations = refInfo.getAnnotations();
                    if (Boolean.TRUE.equals(child.jjtAccept(this, annotations))) {
                        continue;
                    } else {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    protected boolean visitModifiers(SimpleNode node, ReflectionInfo refInfo) {
        int nrChildren = node.jjtGetNumChildren();
        if (nrChildren != 0) {
            for (int i = 0; i < nrChildren; i++) {
                Node child = node.jjtGetChild(i);
                if (child instanceof ASTModifier) {
                    if (Boolean.TRUE.equals(child.jjtAccept(this, refInfo))) {
                        continue;
                    } else {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    protected boolean visitParameters(SimpleNode node, ClassInfo[] parameterTypes) {
        int nrChildren = node.jjtGetNumChildren();
        if (nrChildren <= 0) {
            return (parameterTypes.length == 0);
        }

        // collect the parameter nodes
        List parameterNodes = new ArrayList();
        for (int i = 0; i < nrChildren; i++) {
            Node child = node.jjtGetChild(i);
            if (child instanceof ASTParameter) {
                parameterNodes.add(child);
            }
        }

        if (parameterNodes.size() <= 0) {
            return (parameterTypes.length == 0);
        }

        //TODO duplicate code with args() match
        //TODO refactor parameterNodes in an array for faster match

        // look for eager pattern at the beginning and end
        int expressionParameterCount = parameterNodes.size();
        boolean isFirstArgEager = ((ASTParameter) parameterNodes.get(0)).getDeclaringClassPattern().isEagerWildCard();
        boolean isLastArgEager = ((ASTParameter) parameterNodes.get(expressionParameterCount - 1)).getDeclaringClassPattern()
                .isEagerWildCard();
        // foo(..)
        if (isFirstArgEager && expressionParameterCount == 1) {
            return true;
        }
        int contextParametersCount = parameterTypes.length;
        if (isFirstArgEager && isLastArgEager) {
            expressionParameterCount -= 2;
            if (expressionParameterCount == 0) {
                // foo(.., ..)
                return true;
            }
            // we need to find a starting position - foo(..,int, bar, ..)
            // foo(int) //int is ok
            // foo(bar,int,bar) //int is ok
            // foo(bar,int,foo,int,bar) // int is ok, but then we fail, so move on to next..
            int matchCount = 0;
            int ictx = 0;
            for (int iexp = 0; iexp < expressionParameterCount; iexp++) {
                if (ictx >= contextParametersCount) {
                    // too many args in foo()
                    matchCount = -1;
                    break;
                }
                // do we have an eager wildcard in the middle ?
                ASTParameter parameterNode = (ASTParameter) parameterNodes.get(iexp + 1);
                boolean isEager = parameterNode.getDeclaringClassPattern().isEagerWildCard();
                if (isEager) {
                    // TODO - ignore for now, but not really supported - eager in the middle will match one
                }
                if (Boolean.TRUE.equals((Boolean) parameterNode.jjtAccept(this, parameterTypes[ictx]))) {
                    matchCount += 1;
                    ictx++;
                } else {
                    // assume matched by starting ".." and rewind expression index
                    matchCount = 0;
                    ictx++;
                    iexp = -1;
                }
            }
            if (matchCount == expressionParameterCount) {
                return true;
            } else {
                return false;
            }
        } else if (isFirstArgEager) {
            expressionParameterCount--;
            if (contextParametersCount >= expressionParameterCount) {
                // do a match from last to first, break when foo() nodes are exhausted
                for (int i = 0; (i < contextParametersCount) && (expressionParameterCount - i >= 0); i++) {
                    ASTParameter parameterNode = (ASTParameter) parameterNodes.get(expressionParameterCount - i);
                    if (Boolean.TRUE.equals(
                            (Boolean) parameterNode.jjtAccept(
                                    this,
                                    parameterTypes[contextParametersCount - 1 - i]
                            )
                    )) {
                        ;//go on with "next" param
                    } else {
                        return false;
                    }
                }
                return true;
            } else {
                //foo() as more param than context we try to match
                return false;
            }
        } else if (isLastArgEager) {
            expressionParameterCount--;
            if (contextParametersCount >= expressionParameterCount) {
                // do a match from first to last, break when foo() nodes are exhausted
                for (int i = 0; (i < contextParametersCount) && (i < expressionParameterCount); i++) {
                    ASTParameter parameterNode = (ASTParameter) parameterNodes.get(i);
                    if (Boolean.TRUE.equals((Boolean) parameterNode.jjtAccept(this, parameterTypes[i]))) {
                        ;//go on with next param
                    } else {
                        return false;
                    }
                }
                return true;
            } else {
                return false;
            }
        } else {
            // no eager wildcard in foo()
            // check that param length are equals
            if (expressionParameterCount == contextParametersCount) {
                for (int i = 0; i < parameterNodes.size(); i++) {
                    ASTParameter parameterNode = (ASTParameter) parameterNodes.get(i);
                    if (Boolean.TRUE.equals((Boolean) parameterNode.jjtAccept(this, parameterTypes[i]))) {
                        ;//go on with next param
                    } else {
                        return false;
                    }
                }
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * Returns the string representation of the expression.
     *
     * @return
     */
    public String toString() {
        return m_expression;
    }

    /**
     * Returns the number of parameters to the target method/constructor else -1.
     *
     * @param ctx
     * @return
     */
    private int getParametersCount(final ExpressionContext ctx) {
        ReflectionInfo reflectionInfo = ctx.getReflectionInfo();
        if (reflectionInfo instanceof MethodInfo) {
            return ((MethodInfo) reflectionInfo).getParameterTypes().length;
        } else if (reflectionInfo instanceof ConstructorInfo) {
            return ((ConstructorInfo) reflectionInfo).getParameterTypes().length;
        } else if (reflectionInfo instanceof FieldInfo) {
            return 1;//field set support for args()
        } else if (ctx.getPointcutType().equals(PointcutType.HANDLER) && reflectionInfo instanceof ClassInfo) {
            // handler args(e) binding
            return 1;
        } else {
            return -1;
        }
    }

    /**
     * Test the context upon the expression tree, under a node that can
     * contain annotations.
     * 
     * @param node	root node of the annotation expression
     * @param reflectInfo context reflection info
     * 
     * @return <CODE>Boolean.TRUE</CODE> in case the <tt>reflectInfo</tt> match
     * 		   the expression subtree, <CODE>Boolean.FALSE</CODE> otherwise.
     */
    protected Object visitAnnotatedNode(SimpleNode node,
									    ReflectionInfo reflectInfo) {
        // In an annotated subtree, only the last child node may represent the pattern
        Node patternNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
        if (!(patternNode instanceof ASTAttribute)) {
            if (Boolean.FALSE.equals((Boolean)patternNode.jjtAccept(this, reflectInfo))) {
                return Boolean.FALSE;
            }
        }

        boolean matchedAnnotations = visitAttributes(node, reflectInfo);
    	if (!matchedAnnotations) {
    		return Boolean.FALSE;
    	} else {
            return Boolean.TRUE;
        }
    }

    /**
     * Access the ASTRoot we visit
     *
     * @return
     */
    public Node getASTRoot() {
        return m_root;
    }

    /**
     * Access the ExpressionInfo we are build on
     *
     * @return
     */
    public ExpressionInfo getExpressionInfo() {
        return m_expressionInfo;
    }
}
