/*
 * Copyright (C) 2004 TiongHiang Lee
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not,  write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Email: thlee@onemindsoft.org
 */

package org.onemind.jxp;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.onemind.commons.invoke.InvocableFunction;
import org.onemind.commons.java.datastructure.NametableStack;
import org.onemind.commons.java.datastructure.ThreadLocalStack;
import org.onemind.commons.java.lang.reflect.ReflectUtils;
import org.onemind.commons.java.util.*;
import org.onemind.jxp.parser.*;
import org.onemind.jxp.util.StaticImport;
import org.onemind.jxp.util.StaticImportUtils;
/**
 * The jxp processor
 * @author TiongHiang Lee (thlee@onemindsoft.org)
 * 
 */
public class JxpProcessor implements JxpParserVisitor
{

    /** the logger * */
    private static final Logger _logger = Logger.getLogger(JxpProcessor.class.getName());

    /** represent ONE * */
    private static final Integer ONE = new Integer(1);

    /** contains zero values for primitive **/
    private static final Map PRIMITIVE_DEFAULTS = new HashMap();
    static
    {
        PRIMITIVE_DEFAULTS.put(Integer.TYPE, new Integer(0));
        PRIMITIVE_DEFAULTS.put(Long.TYPE, new Long(0));
        PRIMITIVE_DEFAULTS.put(Float.TYPE, new Float(0));
        PRIMITIVE_DEFAULTS.put(Double.TYPE, new Double(0));
        PRIMITIVE_DEFAULTS.put(Character.TYPE, new Character((char) 0));
        PRIMITIVE_DEFAULTS.put(Byte.TYPE, new Byte((byte) 0));
        PRIMITIVE_DEFAULTS.put(Boolean.TYPE, Boolean.FALSE);
    }
    

    /**
     * process a file
     * @param args the arguments
     * @throws Exception if exception
     */
    public static void main(String[] args) throws Exception
    {
        if (args.length != 2)
        {
            System.out.println("Usage: java org.onemind.jxp.JxpProcessor <source dir> <page>");
        } else
        {
            FilePageSource source = new FilePageSource(args[0]);
            JxpContext context = new JxpContext(source);
            JxpProcessor processor = new JxpProcessor(context);
            Writer writer = new OutputStreamWriter(System.out);
            try
            {
                processor.process(args[1], writer, Collections.EMPTY_MAP);
            } catch (Exception e)
            {
                e.printStackTrace();
            }
            writer.flush();
        }
    }

    /**
     * Represents a user defined function
     * @author TiongHiang Lee (thlee@onemindsoft.org)
     * 
     */
    private class JxpUserDefinedFunction
    {

        /** the declarator **/
        private SimpleNode _paramSpec;

        /** the block **/
        private AstBlock _block;

        /**
         * Constructor
         * @param dec the declarator
         * @param block the function block
         */
        public JxpUserDefinedFunction(SimpleNode paramSpec, AstBlock block)
        {
            _paramSpec = paramSpec;
            _block = block;
        }

        /**
         * Get the declarator
         * @return the declarator
         */
        public SimpleNode getParameterSpec()
        {
            return _paramSpec;
        }

        /**
         * Get the block
         * @return the block
         */
        public AstBlock getBlock()
        {
            return _block;
        }
    }

    /** a context local keep tracks of current processing context * */
    private ThreadLocalStack _contextLocal = new ThreadLocalStack();

    /** the jxp page source * */
    private JxpContext _context;

    /** whether do method status **/
    //TODO: add as config options
    private boolean doMethodStats;
    
    /** whether to check function argument type **/
    private boolean _checkFunctionArgumentType = false;

    /** the method statistic counter **/
    private final Counter _methodStats = new Counter();

    /**
     * Constructor
     * @param source the page source
     */
    public JxpProcessor(JxpContext context)
    {
        _context = context;
    }

    /**
     * Get the source
     * @return the source
     */
    public final JxpContext getContext()
    {
        return _context;
    }

    /**
     * Assign the value assignable target node to the value
     * @param node the assignable target
     * @param value the value to assign
     * @return the value assinged
     * @throws ProcessingException if the node is not an assignable
     */
    protected Object assign(SimpleNode node, Object value) throws ProcessingException
    {
        Object target = resolveAssignmentTarget(node, true);
        if (target instanceof Assignable)
        {
            return ((Assignable) target).assign(value);
        } else
        {
            return generateProcessingException(new IllegalArgumentException(node + " is not assignable"), node);
        }
    }

    /**
     * Assign a variable in a current nametable stack to the value
     * @param name the variable name
     * @param value the value
     * @return the value assigned
     */
    protected Object assignVariable(String name, Object value)
    {
        NametableStack ntStack = getCurrentContext().getNametableStack();
        ntStack.assign(name, value);
        return value;
    }

    /**
     * Call the function of the processor. Call predefined print or println if the method name matched. Otherwise, reflection will
     * be used on the processor to invoke method
     * @param methodName the method name
     * @param args the arguments
     * @return the value returned by the method call
     * @throws Exception if there's problem calling method
     */
    protected Object callFunction(String methodName, Object[] args) throws Exception
    {
        //built-in functions
        if (methodName.equals("print"))
        {
            print(args[0]);
            return null;
        } else if (methodName.equals("println"))
        {
            println(args[0]);
            return null;
        } else if (methodName.equals("getJxpEnvironment")){
            return getEnvironment();
        } else
        {
            JxpProcessingContext context = getCurrentContext();
            String functionName = methodName;
            if (context.getUserDefinedFunctions().containsKey(functionName))
            {
                JxpUserDefinedFunction function = (JxpUserDefinedFunction) context.getUserDefinedFunctions().get(functionName);
                NametableStack nt = context.getNametableStack();
                int scope = nt.newLocalScope();
                declareFunctionLocals(functionName, function.getParameterSpec(), args);
                try
                {
                    Object obj = function.getBlock().jjtAccept(this, null);
                    if (obj == Control.EXIT)
                    {
                        return obj;
                    } else if (obj instanceof Control) //return value
                    {
                        return ((Control) obj).getObject();
                    } else
                    {
                        return obj;
                    }
                } finally
                {
                    nt.closeLocalScope(scope);
                }
            } else
            {
                //try static import
                List staticImports = context.getStaticImports();
                Iterator it = staticImports.iterator();
                while (it.hasNext())
                {
                    StaticImport sImport = (StaticImport) it.next();
                    InvocableFunction func = sImport.getFunction(functionName, args);
                    if (func != null)
                    {
                        return func.invoke(null, args);
                    }
                }
                //lastly, throw exception
                throw new NoSuchMethodException("Method " + methodName + " not found for the Processor");
            }
        }
    }

    /**
     * Create the function environment
     * @param params the parameters
     * @param args the arguments
     */
    public void declareFunctionLocals(String functionName, SimpleNode params, Object[] args) throws Exception
    {
        NametableStack ntStack = getCurrentContext().getNametableStack();
        int n = params.jjtGetNumChildren();
        Map defaults = new HashMap();
        for (int i = 0; i < args.length; i++)
        {
            if (args[i] instanceof AstNamedArgument)
            {
                SimpleNode arg = (SimpleNode) args[i];
                Object value = arg.jjtGetChild(0).jjtAccept(this, null);
                defaults.put(arg.getData(), value);
            }
        }
        if (params instanceof AstFormalParameters)
        {
            for (int i = 0; i < n; i++)
            {
                AstFormalParameter param = (AstFormalParameter) params.jjtGetChild(i);
                AstVariableDeclaratorId id = (AstVariableDeclaratorId) param.jjtGetChild(1);
                AstType type = (AstType) param.jjtGetChild(0);
                Class paramType = (Class) type.jjtAccept(this, null);
                
                if (param.jjtGetNumChildren() == 2)
                {//no defaults
                    if (args.length <= i)
                    {
                        throw new IllegalArgumentException("Mismatch number of arguments");
                    } else if (args[i] instanceof AstNamedArgument)
                    {
                        throw new IllegalArgumentException("Expecting required paramater "
                                + id.getData() + " for function " + functionName);
                    } else
                    {
                        if (_checkFunctionArgumentType)
                        {//do type checking                   
                            if (paramType.isPrimitive())
                            {
                                if (args[i] == null)
                                {
                                    throw new IllegalArgumentException("Primitive argument " + id.getData() + " cannot be null");
                                } else if (!ReflectUtils.isPrimitiveInstance(paramType, args[i]))
                                {
                                    throw new IllegalArgumentException("Mismatch argument type for " + id.getData() + ". Expecting "
                                                + paramType.getName());
                                }
                            } else if (args[i] != null)
                            {
                                if (!paramType.isInstance(args[i]))
                                {
                                    throw new IllegalArgumentException("Mismatch argument type for " + id.getData()
                                            + ". Expecting " + paramType.getName());
                                }
                            }
                            
                        }
                        ntStack.declare((String) id.getData(), args[i]);
                    }
                } else
                {//has default                    
                    if (defaults.containsKey(id.getData()))
                    {
                        Object value = defaults.remove(id.getData());
                        ntStack.declare((String) id.getData(), value);
                    } else
                    {
                        if (i >= args.length || args[i] instanceof AstNamedArgument)
                        {
                            Object value = param.jjtGetChild(2).jjtAccept(this, null);
                            ntStack.declare((String) id.getData(), value);
                        } else
                        {
                            ntStack.declare((String) id.getData(), args[i]);
                        }
                    }
                }
            }
            if (defaults.size() > 0)
            {
                throw new IllegalArgumentException("Unknown default " + defaults + " for function " + functionName);
            }
        } else
        {//assume variable parameters (varargs)
            ntStack.declare((String) params.getData(), args);
        }
    }

    /**
     * Throws the exception
     * @param e the throwable
     * @param node the node that the exception current
     * @return will not return since exception will always be thrown
     */
    private ProcessingException generateProcessingException(Throwable e, SimpleNode node)
    {
        ProcessingException ex = new ProcessingException(getCurrentContext().getCurrentPage(), e, node);
        return ex;
    }

    /**
     * Include another page as a part of current page and process it
     * @param id the id
     * @return the object
     * @throws Exception if there's exception
     */
    protected Object includeCall(String id) throws Exception
    {
        return process(id, getCurrentContext());
    }

    /**
     * Instantiate an instance of class type using the arguments
     * @param type the type to instantiate
     * @param args the arguments
     * @return the object instantiated
     * @throws Exception if there's problem in the instantiation
     */
    protected Object instantiate(Class type, Object[] args) throws Exception
    {
        if (_logger.isLoggable(Level.FINEST))
        {
            _logger.finest("Instantiaing " + type + " with " + StringUtils.concat(args, ","));
        }
        return ReflectUtils.newInstance(type, args);
    }

    /**
     * Invoke the method of object obj with given argument
     * @param obj the object
     * @param methodName the method name
     * @param args the arguments
     * @return the value return by the method invoked
     * @throws Exception if there's problem invoking the method
     */
    protected Object invokeMethod(Object obj, String methodName, Object[] args) throws Exception
    {
        if (_logger.isLoggable(Level.FINEST))
        {
            _logger.finest("Invoking " + obj.getClass() + "." + methodName + "(" + StringUtils.concat(args, ",") + ")");
        }
        if (obj == this)
        {
            return callFunction(methodName, args);
        } else
        {
            if (doMethodStats)
            {
                _methodStats.count(methodName);
            }
            if (obj instanceof JxpInvocable)
            {
                JxpInvocable invocable = (JxpInvocable) obj;
                if (invocable.canInvoke(methodName, args))
                {
                    return invocable.invoke(methodName, args);
                }
            } //else fall through
            return ReflectUtils.invoke(obj, methodName, args);
        }
    }

    /**
     * Look up a variable in current nametable stack
     * @param variableName the variable name
     * @return the value of the variable
     * @throws NoSuchFieldException the the variable cannot be found
     */
    protected Object lookupVariable(String variableName) throws NoSuchFieldException
    {
        NametableStack ntStack = getCurrentContext().getNametableStack();
        if (!ntStack.containsName(variableName))
        {
            throw new NoSuchFieldException("Variable/Function " + variableName + " is not declared before");
        }
        Object v = ntStack.access(variableName);
        if (_logger.isLoggable(Level.FINEST))
        {
            _logger.finest("Looking up variable " + variableName + " found " + v);
        }
        return v;
    }

    /**
     * print the object by printing o.toString() using writer of current context. If the object is null, "null" will be printed.
     * @param o the object
     * @throws IOException if there's problem doing the printing
     */
    protected void print(Object o) throws IOException
    {
        if (o == null)
        {
            print("null");
        } else
        {
            print(o.toString());
        }
    }

    /**
     * Print the string using writer of current context
     * @param s the string
     * @throws IOException if there's io problem TODO: make to flag IOException to avoid call writes again
     */
    protected void print(String s) throws IOException
    {
        Writer writer = getCurrentContext().getWriter();
        try
        {
            writer.write(s);
        } catch (Exception e)
        {
            _logger.throwing(getClass().getName(), "print", e);
        }
    }

    /**
     * print the object with the line delimiter "\n"
     * @param o the object
     * @throws IOException if there's problem doing the printing
     */
    protected void println(Object o) throws IOException
    {
        print(o + "\n");
    }

    /**
     * print the object with the line delimiter "\n"
     * @param s the string
     * @throws IOException if there's problem doing the printing
     */
    protected void println(String s) throws IOException
    {
        print(s + "\n");
    }

    /**
     * Process a page with empty environment
     */
    public Object process(String id, Writer writer) throws Exception
    {
        return process(id, writer, Collections.EMPTY_MAP);
    }

    /**
     * process the page identified by id with the context given
     * @param id the id
     * @param env the environment
     * @return value return by this page
     * @throws Exception if there's problem doing the printing
     */
    public Object process(String id, Writer writer, Map env) throws Exception
    {
        JxpProcessingContext procContext = _context.createProcessingContext(env, writer);
        int scope = _contextLocal.pushLocal(procContext);
        try
        {
            return process(id, procContext);
        } finally
        {
            _contextLocal.popLocalUtil(scope);
        }
    }

    /**
     * Processing a page identified by id in current context
     * @param id the id
     * @return the value return by the processing
     * @throws Exception if there's problem doing the printing
     */
    protected Object process(String id, JxpProcessingContext context) throws Exception
    {
        //resolve the actual id
        String scriptId = id;
        JxpPage currentPage = context.getCurrentPage();
        if (currentPage != null && !id.startsWith("/")) //relative path
        {
            String prefix = StringUtils.substringBeforeLast(currentPage.getName(), "/");
            if (prefix != null)
            {
                scriptId = FileUtils.concatFilePath(prefix, id);
            }
        }
        JxpPage page = _context.getPageSource().getJxpPage(scriptId);
        try
        {
            context.pushPage(page);
            return visit(page.getJxpDocument(), null);
        } finally
        {
            context.popPage(page);
        }
    }

    /**
     * Get the current processing context
     * @return the current processing context
     */
    public JxpProcessingContext getCurrentContext()
    {
        return (JxpProcessingContext) _contextLocal.getLocal();
    }

    /**
     * Process a page identified by id in current context
     * @param id the id
     * @return the value return by the page
     * @throws Exception if there's problem doing the printing
     */
    protected Object processCall(String id) throws Exception
    {
        JxpProcessingContext context = getCurrentContext();
        int scope = context.getNametableStack().newScope();
        try
        {
            return process(id, context);
        } finally
        {
            context.getNametableStack().closeScope(scope);
        }
    }

    /**
     * Resolve the arguments for function or method call
     * @param node the node
     * @param context the context
     * @return object array containing the resolved argument values
     * @throws Exception if there's problem
     */
    protected Object[] resolveArguments(AstArguments node, JxpProcessingContext context) throws Exception
    {
        int n = node.jjtGetNumChildren();
        Object[] args = new Object[n];
        for (int i = 0; i < n; i++)
        {
            args[i] = node.jjtGetChild(i).jjtAccept(this, context);
        }
        return args;
    }

    /**
     * Resolve the array dimensions needed
     * @param node the ArrayDims node
     * @param context the processing context
     * @return the int array containing the dimensions info
     * @throws Exception if there's problem resolving the dimension
     */
    protected int[] resolveArrayDims(AstArrayDims node, JxpProcessingContext context) throws Exception
    {
        int[] dims = new int[node.jjtGetNumChildren()];
        for (int i = 0; i < node.jjtGetNumChildren(); i++)
        {
            Object o = node.jjtGetChild(i).jjtAccept(this, context);
            if (o instanceof Number)
            {
                dims[i] = ((Number) o).intValue();
            } else
            {
                throw generateProcessingException(new IllegalArgumentException("Array dimension " + o + " is not a number"), node);
            }
        }
        return dims;
    }

    /**
     * Resolve the assignment target. Exception will be thrown if the node is not assignable and throwExcpetion is true. Otherwise
     * null will be return if the node cannot be resolved to assignment target.
     * @param node the simple node or the target
     * @param throwException whether to throw exception
     * @return the target
     * @throws ProcessingException if the target cannot be resolved
     */
    protected Assignable resolveAssignmentTarget(SimpleNode node, boolean throwException) throws ProcessingException
    {
        if (node instanceof AstName)
        {
            AstName name = (AstName) node;
            List l = (List) name.getData();
            JxpProcessingContext context = getCurrentContext();
            if (l.size() == 1)
            {
                String variableName = (String) l.get(0);
                // first case
                if (context.getNametableStack().containsName(variableName))
                {
                    return new VariableAssignable((String) l.get(0), context);
                } else
                {
                    //second case
                    JxpPage page = context.getCurrentPage();
                    if (page.hasStaticVariable(variableName))
                    {
                        return new StaticVariableAssignable((String) l.get(0), page);
                    }
                    throw generateProcessingException(new IllegalArgumentException("Variable " + variableName
                            + " has not been declared."), node);
                }
            } else
            {
                //TODO: how about assigning to a field? arr
                if (throwException)
                {
                    throw generateProcessingException(new IllegalArgumentException(node + " is not assignable"), node);
                } else
                {
                    return null;
                }
            }
        } else if (node instanceof AstArrayReference)
        {
            try
            {
                Object obj = node.jjtGetChild(0).jjtAccept(this, null);
                if (obj == null)
                {
                    throw generateProcessingException(new NullPointerException(), (SimpleNode) node.jjtGetChild(0));
                }
                if (!obj.getClass().isArray())
                {
                    throw generateProcessingException(new IllegalArgumentException("Argument is not an array"), (SimpleNode) node
                            .jjtGetChild(0));
                }
                Object dimObj = node.jjtGetChild(1).jjtAccept(this, null);
                int dim = 0;
                if (dimObj instanceof Number)
                {
                    dim = ((Number) dimObj).intValue();
                } else
                {
                    throw generateProcessingException(new IllegalArgumentException("Invalid index type " + dimObj.getClass()
                            + " for array"), node);
                }
                return new ArrayAssignable(obj, dim);
            } catch (ProcessingException e)
            {
                throw (ProcessingException) e;
            } catch (Exception e)
            {
                throw generateProcessingException(e, node);
            }
        } else
        { //TODO: other kind of assignable such as array etc
            if (throwException)
            {
                throw generateProcessingException(new IllegalArgumentException(node + " is not assignable"), node);
            } else
            {
                return null;
            }
        }
    }

    /**
     * Resolve the class. Call the current context for resolving since the imports in context specific
     * @param className the class name
     * @return the class
     */
    protected final Class resolveClass(String className)
    {
        return getCurrentContext().resolveClass(className);
    }

    /**
     * Resolve the field on and object. Also resolve the array length field
     * @param obj the object
     * @param fieldName the field name
     * @return the field value
     * @throws Exception if the field cannot be resolved
     */
    protected Object resolveField(Object obj, String fieldName) throws Exception
    {
        if (_logger.isLoggable(Level.FINEST))
        {
            _logger.finest("Resolving field " + fieldName + " of " + obj);
        }
        if (obj == null)
        {
            throw new NoSuchFieldException("Cannot find field " + fieldName + " for null");
        }
        if (fieldName.equals("class"))
        {
            if (obj instanceof Class)
            {
                return (Class) obj;
            } else
            {
                throw new IllegalStateException(".class cannot be apply to " + obj);
            }
        }
        if (fieldName.equals("length"))
        { //check if is array
            if (obj.getClass().isArray())
            {
                return new Integer(Array.getLength(obj));
            } //otherwise just pass through
        }
        if (obj instanceof JxpInvocable)
        {
            JxpInvocable invocable = (JxpInvocable) obj;
            if (invocable.canInvoke(fieldName, null))
            {
                return invocable.invoke(fieldName, null);
            }
        }
        if (obj instanceof Map)
        {
            return ((Map) obj).get(fieldName);
        } //otherwise just pass through to use reflection
        Field f = null;
        if (obj instanceof Class)
        {
            f = ((Class) obj).getField(fieldName);
        } else
        {
            f = obj.getClass().getField(fieldName);
        }
        return f.get(obj);
    }

    /**
     * Resolve a object given the name in current processing context. It cound be variable, or class
     * @param l containing series of identifier
     * @return the object associated with the name
     * @throws Exception if there's problem resolving the name
     */
    protected Object resolveName(List l) throws Exception
    {
        Object current = null;
        JxpProcessingContext context = getCurrentContext();
        NametableStack ntStack = context.getNametableStack();
        for (int i = 0; i < l.size(); i++)
        {
            String s = (String) l.get(i);
            if (i == 0)
            {
                //first case, variable
                if (ntStack.containsName(s))
                {
                    current = ntStack.access(s); //lookup variable
                } else
                {//second case, page static
                    JxpPage page = context.getCurrentPage();
                    if (page.hasStaticVariable(s))
                    {
                        current = page.getStaticVariable(s);
                    } else
                    {
                        //third case, resolve class
                        current = resolveClass(s);
                        if (current == null)
                        {
                            throw new NoSuchFieldException("Variable " + s + " has not been declared");
                        }
                    }
                }
            } else
            {
                current = resolveField(current, s);
            }
        }
        return current;
    }

    /**
     * Concat the ASTName to a dotted name notation
     * @param name the ASTName object
     * @return the dotted name string
     */
    private String toDottedName(AstName name)
    {
        return StringUtils.concat((List) name.getData(), ".");
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstArguments, java.lang.Object)
     */
    public Object visit(AstArguments node, Object data) throws Exception
    {
        throw new IllegalStateException("visit(AstArguments, Object) should not be called");
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstArrayAllocationExpression, java.lang.Object)
     */
    public Object visit(AstArrayAllocationExpression node, Object data) throws Exception
    {
        String className = toDottedName((AstName) node.jjtGetChild(0));
        Class type = resolveClass(className);
        if (type == null)
        {
            throw generateProcessingException(new ClassNotFoundException("No definition found for " + className), node);
        }
        SimpleNode n = (SimpleNode) node.jjtGetChild(1);
        if (n instanceof AstArrayDims)
        {
            int[] dims = resolveArrayDims((AstArrayDims) n, getCurrentContext());
            return Array.newInstance(type, dims);
        } else
        { //must be ArrayInitializer
            return n.jjtAccept(this, type);
        }
    }

    /**
     * (non-Javadoc)
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstArrayDims, java.lang.Object)
     */
    public Object visit(AstArrayDims node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException("AstArrayDims are not supposed to be called"), node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstArrayInitializer, java.lang.Object)
     */
    public Object visit(AstArrayInitializer node, Object data) throws Exception
    {
        Class type = (data instanceof Class) ? (Class) data : Object.class;
        int dim = node.jjtGetNumChildren();
        List l = new ArrayList();
        Class elementType = null;
        for (int i = 0; i < dim; i++)
        {
            Object element = node.jjtGetChild(i).jjtAccept(this, type);
            //make sure the element type is correct
            if (element != null)
            {
                elementType = element.getClass(); //save for latter
                if (!elementType.isArray())
                {//must be primitive - check it
                    if (!type.isAssignableFrom(element.getClass()))
                    {
                        throw generateProcessingException(new IllegalArgumentException("Illegal element " + element + " in "
                                + type.getName() + " array"), (SimpleNode) node.jjtGetChild(i));
                    }
                }
            } else
            {
                if (type.isPrimitive())
                {
                    throw generateProcessingException(new IllegalArgumentException("Illegal element " + element + " in "
                            + type.getName() + " array"), (SimpleNode) node.jjtGetChild(i));
                }
            }
            l.add(element);
        }
        if (elementType != null)
        {
            Object[] array = (Object[]) Array.newInstance(elementType, l.size());
            return l.toArray(array);
        } else
        {
            return l.toArray();
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstArrayReference, java.lang.Object)
     */
    public Object visit(AstArrayReference node, Object data) throws Exception
    {
        Object obj = node.jjtGetChild(0).jjtAccept(this, data);
        if (obj != null && obj.getClass().isArray())
        {
            Object dimObj = node.jjtGetChild(1).jjtAccept(this, data);
            int dim = 0;
            if (dimObj instanceof Number)
            {
                dim = ((Number) dimObj).intValue();
            } else
            {
                throw generateProcessingException(new IllegalArgumentException("Invalid index type " + dimObj.getClass()
                        + " for array"), node);
            }
            if (_logger.isLoggable(Level.FINEST))
            {
                _logger.finest("Resolving array reference of " + obj + "[" + dim + "]");
            }
            return Array.get(obj, dim);
        } else
        {
            throw generateProcessingException(new IllegalArgumentException("Cannot get array referernce of non-array " + obj), node);
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstAssertStatement, java.lang.Object)
     */
    public Object visit(AstAssertStatement node, Object data) throws Exception
    {
        for (int i = 0; i < node.jjtGetNumChildren(); i++)
        {
            Boolean assertion = Evaluator.toBoolean(node.jjtGetChild(i).jjtAccept(this, data));
            if (!assertion.booleanValue())
            {
                throw generateProcessingException(new AssertionError("Assertion error "), (SimpleNode) node.jjtGetChild(i));
            }
        }
        return Boolean.TRUE;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstAssignExpression, java.lang.Object)
     */
    public Object visit(AstAssignExpression node, Object data) throws Exception
    {
        Object value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), value);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstAndAssignExpression, java.lang.Object)
     */
    public Object visit(AstBitwiseAndAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.bitwiseAnd(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstBitAndExpression, java.lang.Object)
     */
    public Object visit(AstBitwiseAndExpression node, Object data) throws Exception
    {
        return Evaluator.bitwiseAnd(node.jjtGetChild(0).jjtAccept(this, data), node.jjtGetChild(1).jjtAccept(this, data));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstTildeUnaryExpression, java.lang.Object)
     */
    public Object visit(AstBitwiseComplementExpression node, Object data) throws Exception
    {
        return Evaluator.bitwiseComplement(node.jjtGetChild(0).jjtAccept(this, data));
    }

    /**
     * (non-Javadoc)
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstBitwiseOrAssignExpression, java.lang.Object)
     */
    public Object visit(AstBitwiseOrAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.bitwiseOr(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstInclusiveOrExpression, java.lang.Object)
     */
    public Object visit(AstBitwiseOrExpression node, Object data) throws Exception
    {
        return Evaluator.bitwiseOr(node.jjtGetChild(0).jjtAccept(this, data), node.jjtGetChild(1).jjtAccept(this, data));
    }

    /**
     * (non-Javadoc)
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstBitwiseXOrAssignExpression, java.lang.Object)
     */
    public Object visit(AstBitwiseXOrAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.bitwiseXOr(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstExclusiveOrExpression, java.lang.Object)
     */
    public Object visit(AstBitwiseXOrExpression node, Object data) throws Exception
    {
        return Evaluator.bitwiseXOr(node.jjtGetChild(0).jjtAccept(this, data), node.jjtGetChild(1).jjtAccept(this, data));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstBlock, java.lang.Object)
     */
    public Object visit(AstBlock node, Object data) throws Exception
    {
        JxpProcessingContext context = getCurrentContext();
        int scope = context.getNametableStack().newScope();
        Object obj = null;
        SimpleNode childNode = null;
        int i = 0;
        try
        {
            for (i = 0; i < node.jjtGetNumChildren(); i++)
            {
                childNode = (SimpleNode) node.jjtGetChild(i);
                obj = childNode.jjtAccept(this, data);
                if (obj instanceof Control)
                {
                    break;
                }
            }
        } catch (ProcessingException pe)
        {
            throw pe;
        } catch (Exception e)
        {
            throw generateProcessingException(e, childNode);
        } finally
        {
            context.getNametableStack().closeScope(scope);
        }
        return obj;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstBreakStatement, java.lang.Object)
     */
    public Object visit(AstBreakStatement node, Object data) throws Exception
    {
        return Control.BREAK;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstCase, java.lang.Object)
     */
    public Object visit(AstCase node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException("Switch case: are not suppossed to be called"), node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstCastExpression, java.lang.Object)
     */
    public Object visit(AstCastExpression node, Object data) throws Exception
    {
        return node.jjtGetChild( 1 ).jjtAccept( this, data );
    }

    /**
     * {@inheritDoc}
     */
    public Object visit(AstCatchBlock node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException("This should not be called"), node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstConditionalAndExpression, java.lang.Object)
     */
    public Object visit(AstConditionalAndExpression node, Object data) throws Exception
    {
        Boolean b1 = Evaluator.toBoolean(node.jjtGetChild(0).jjtAccept(this, data));
        if (!b1.booleanValue())
        {
            return Boolean.FALSE;
        }
        Boolean b2 = Evaluator.toBoolean(node.jjtGetChild(1).jjtAccept(this, data));
        if (!b2.booleanValue())
        {
            return Boolean.FALSE;
        } else
        {
            return Boolean.TRUE;
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstConditionalOrExpression, java.lang.Object)
     */
    public Object visit(AstConditionalOrExpression node, Object data) throws Exception
    {
        Boolean b1 = Evaluator.toBoolean(node.jjtGetChild(0).jjtAccept(this, data));
        if (b1.booleanValue())
        {
            return Boolean.TRUE;
        }
        Boolean b2 = Evaluator.toBoolean(node.jjtGetChild(1).jjtAccept(this, data));
        if (b2.booleanValue())
        {
            return Boolean.TRUE;
        } else
        {
            return Boolean.FALSE;
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstContent, java.lang.Object)
     */
    public Object visit(AstContent node, Object data) throws Exception
    {
        Object content = node.getData();
        print(content);
        return content;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstContinueStatement, java.lang.Object)
     */
    public Object visit(AstContinueStatement node, Object data) throws Exception
    {
        return Control.CONTINUE;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstSlashAssignExpression, java.lang.Object)
     */
    public Object visit(AstDivideAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.divide(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstSlashExpression, java.lang.Object)
     */
    public Object visit(AstDivideExpression node, Object data) throws Exception
    {
        return Evaluator.divide(node.jjtGetChild(0).jjtAccept(this, data), node.jjtGetChild(1).jjtAccept(this, data));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstDoStatement, java.lang.Object)
     */
    public Object visit(AstDoStatement node, Object data) throws Exception
    {
        Node condition = node.jjtGetChild(1);
        Boolean b = null;
        Object returnValue = null;
        do
        {
            returnValue = node.jjtGetChild(0).jjtAccept(this, data);
            if (returnValue instanceof Control)
            {
                if (returnValue == Control.CONTINUE)
                {
                    returnValue = null;
                } else if (returnValue == Control.BREAK)
                {
                    returnValue = null;
                    break;
                } else
                { //must be return w/wo object
                    break;
                }
            }
            b = Evaluator.toBoolean(condition.jjtAccept(this, data));
        } while (b.booleanValue());
        return returnValue;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstEmptyStatement, java.lang.Object)
     */
    public Object visit(AstEmptyStatement node, Object data) throws Exception
    {
        return null;
    }

    /**
     * @see org.onemind.jxp.parser.JxpParserVisitor#visit(jxp.parser.AstEQExpression, java.lang.Object)
     */
    public Object visit(AstEQExpression node, Object data) throws Exception
    {
        Object o1 = node.jjtGetChild(0).jjtAccept(this, data), o2 = node.jjtGetChild(1).jjtAccept(this, data);
        return Evaluator.eq(o1, o2);
    }

    /**
     * (non-Javadoc)
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstField, java.lang.Object)
     */
    public Object visit(AstField node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException("visit(AstField, Object) is not supposed to be called"), node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstFieldDeclaration, java.lang.Object)
     */
    public Object visit(AstFieldDeclaration node, Object data) throws Exception
    {
        Boolean isStatic = (node.getData() == null) ? Boolean.FALSE : Boolean.TRUE;
        Object[] passOn = new Object[]{node.jjtGetChild(0), isStatic};
        //ignore the type               
        for (int i = 1; i < node.jjtGetNumChildren(); i++)
        {
            node.jjtGetChild(i).jjtAccept(this, passOn);
        }
        return null;
    }

    /**
     * (non-Javadoc)
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstFieldReference, java.lang.Object)
     */
    public Object visit(AstFieldReference node, Object data) throws Exception
    {
        Object o1 = node.jjtGetChild(0).jjtAccept(this, data);
        String fieldName = (String) ((SimpleNode) node.jjtGetChild(1)).getData();
        return resolveField(o1, fieldName);
    }

    /**
     * {@inheritDoc}
     */
    public Object visit(AstFinallyBlock node, Object data) throws Exception
    {
        return node.jjtGetChild(0).jjtAccept(this, data);
        //pass on
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstForInit, java.lang.Object)
     */
    public Object visit(AstForInit node, Object data) throws Exception
    {
        return node.childrenAccept(this, data);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstFormalParameter, java.lang.Object)
     */
    public Object visit(AstFormalParameter node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException(
                "visit(AstFormalParameter, Object) are not supposed to be called"), node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstFormalParameters, java.lang.Object)
     */
    public Object visit(AstFormalParameters node, Object data) throws Exception
    {
        throw generateProcessingException(new UnsupportedOperationException("Unsupported Language Feature"), node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstForStatement, java.lang.Object)
     */
    public Object visit(AstForStatement node, Object data) throws Exception
    {
        JxpProcessingContext context = getCurrentContext();
        SimpleNode forInit = null, forCondition = null, forUpdate = null, statements = null;
        for (int i = 0; i < node.jjtGetNumChildren() - 1; i++)
        {
            SimpleNode n = (SimpleNode) node.jjtGetChild(i);
            if (n instanceof AstForInit)
            {
                forInit = n;
            } else if (n instanceof AstForUpdate)
            {
                forUpdate = n;
            } else
            {
                forCondition = n;
            }
        }
        statements = (SimpleNode) node.jjtGetChild(node.jjtGetNumChildren() - 1);
        int newscope = context.getNametableStack().newScope(); //this must be in front of for init
        if (forInit != null)
        {
            forInit.jjtAccept(this, data);
        }
        Boolean condition = Boolean.TRUE;
        if (forCondition != null)
        {
            condition = Evaluator.toBoolean(forCondition.jjtAccept(this, data));
        }
        Object returnValue = null;
        try
        {
            while (condition.booleanValue())
            {
                returnValue = statements.jjtAccept(this, data);
                if (returnValue == Control.CONTINUE)
                {
                    returnValue = null;
                    //let it pass
                } else if (returnValue == Control.BREAK)
                {
                    returnValue = null;
                    break;
                } else if (returnValue instanceof Control)
                { //must be return w/wo object
                    break;
                }
                if (forUpdate != null)
                {
                    forUpdate.jjtAccept(this, data);
                }
                if (forCondition != null)
                {
                    condition = Evaluator.toBoolean(forCondition.jjtAccept(this, data));
                }
            }
        } finally
        {
            context.getNametableStack().closeScope(newscope);
        }
        return returnValue;
    }

    /**
     * (non-Javadoc)
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstForUpdate, java.lang.Object)
     */
    public Object visit(AstForUpdate node, Object data) throws Exception
    {
        return node.childrenAccept(this, data);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstGEExpression, java.lang.Object)
     */
    public Object visit(AstGEExpression node, Object data) throws Exception
    {
        Object o1 = node.jjtGetChild(0).jjtAccept(this, data), o2 = node.jjtGetChild(1).jjtAccept(this, data);
        return Evaluator.ge(o1, o2);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstGTExpression, java.lang.Object)
     */
    public Object visit(AstGTExpression node, Object data) throws Exception
    {
        Object o1 = node.jjtGetChild(0).jjtAccept(this, data), o2 = node.jjtGetChild(1).jjtAccept(this, data);
        return Evaluator.gt(o1, o2);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstHookExpression, java.lang.Object)
     */
    public Object visit(AstHookExpression node, Object data) throws Exception
    {
        Object returnValue = node.jjtGetChild(0).jjtAccept(this, data);
        if (returnValue instanceof Boolean)
        {
            if (Boolean.TRUE.equals(returnValue))
            {
                return node.jjtGetChild(1).jjtAccept(this, data);
            } else
            {
                return node.jjtGetChild(2).jjtAccept(this, data);
            }
        } else
        {
            throw generateProcessingException(new IllegalArgumentException(
                    "condition of hook expression is not a boolean expression"), node);
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstIfStatement, java.lang.Object)
     */
    public Object visit(AstIfStatement node, Object data) throws Exception
    {
        Boolean condition = Evaluator.toBoolean(node.jjtGetChild(0).jjtAccept(this, data));
        if (condition.booleanValue())
        {
            return node.jjtGetChild(1).jjtAccept(this, data);
        } else
        {
            if (node.jjtGetNumChildren() > 2)
            {
                return node.jjtGetChild(2).jjtAccept(this, data);
            } else
            {
                return null;
            }
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstImportDeclaration, java.lang.Object)
     */
    public Object visit(AstImportDeclaration node, Object data) throws Exception
    {
        JxpProcessingContext context = getCurrentContext();
        String packageName = toDottedName((AstName) node.jjtGetChild(0));
        String className = (String) node.getData();
        String importName = packageName + "." + className;
        context.getImports().addImport(importName);
        if (_logger.isLoggable(Level.FINEST))
        {
            _logger.finest("Add import " + importName);
            _logger.finest(context.getImports().getPackages().toString());
        }
        return null;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstInstanceOfExpression, java.lang.Object)
     */
    public Object visit(AstInstanceOfExpression node, Object data) throws Exception
    {
        Object obj = node.jjtGetChild(0).jjtAccept(this, data); //the object
        if (obj == null)
        {
            return Boolean.FALSE; //always false
        } else
        {
            Class c = (Class) node.jjtGetChild(1).jjtAccept(this, data);
            return Boolean.valueOf(c.isInstance(obj));
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstJxpDocument, java.lang.Object)
     */
    public Object visit(AstJxpDocument node, Object data) throws Exception
    {
        int i = 0;
        int n = node.jjtGetNumChildren();
        Object returnValue = null;
        SimpleNode statement = null;
        try
        {
            while (i < n)
            {
                statement = (SimpleNode) node.jjtGetChild(i);
                returnValue = statement.jjtAccept(this, data);
                if (returnValue instanceof Control)
                {
                    return returnValue;
                }
                i++;
            }
        } catch (ProcessingException pe)
        {
            throw pe;
        } catch (Exception e)
        {
            throw generateProcessingException(e, statement);
        }
        return null;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstLabeledStatement, java.lang.Object)
     */
    public Object visit(AstLabeledStatement node, Object data) throws Exception
    {
        throw generateProcessingException(new UnsupportedOperationException("Labeled statement: Unsupported Language Feature"),
                node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstLEExpression, java.lang.Object)
     */
    public Object visit(AstLEExpression node, Object data) throws Exception
    {
        Object o1 = node.jjtGetChild(0).jjtAccept(this, data), o2 = node.jjtGetChild(1).jjtAccept(this, data);
        return Evaluator.le(o1, o2);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstLiteral, java.lang.Object)
     */
    public Object visit(AstLiteral node, Object data) throws Exception
    {
        return node.getData();
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstBangUnaryExpression, java.lang.Object)
     */
    public Object visit(AstLogicalComplementExpression node, Object data) throws Exception
    {
        Boolean b = Evaluator.toBoolean(node.jjtGetChild(0).jjtAccept(this, data));
        return Boolean.valueOf(!b.booleanValue());
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstLShiftAssignExpression, java.lang.Object)
     */
    public Object visit(AstLShiftAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.leftShift(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstLShiftExpression, java.lang.Object)
     */
    public Object visit(AstLShiftExpression node, Object data) throws Exception
    {
        return Evaluator.leftShift(node.jjtGetChild(0).jjtAccept(this, data), node.jjtGetChild(1).jjtAccept(this, data));
    }

    /**
     * (non-Javadoc)
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstLTExpression, java.lang.Object)
     */
    public Object visit(AstLTExpression node, Object data) throws Exception
    {
        Object o1 = node.jjtGetChild(0).jjtAccept(this, data), o2 = node.jjtGetChild(1).jjtAccept(this, data);
        return Evaluator.lt(o1, o2);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstMethodCall, java.lang.Object)
     */
    public Object visit(AstMethodCall node, Object data) throws Exception
    {
        String methodName = (String) node.getData();
        Object[] args = resolveArguments((AstArguments) node.jjtGetChild(0), getCurrentContext());
        if (args.length == 0)
        {
            if (methodName.equals("flush"))
            {
                flush();
                return null;
            }
        } else if (args[0] instanceof String && args.length == 1)
        {
            /*
             if (methodName.equals("process"))
             {
             return processCall((String) args[0]);
             } else
             */
            if (methodName.equals("include"))
            {
                return includeCall((String) args[0]);
            }
        }
        try
        {
            return invokeMethod(this, methodName, args);
        } catch (ProcessingException e)
        {
            throw e;
        } catch (InvocationTargetException e)
        {
            Throwable t = e.getTargetException();
            throw generateProcessingException(t, node);
        } catch (Exception e)
        {
            throw generateProcessingException(e, node);
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstMethodDeclaration, java.lang.Object)
     */
    public Object visit(AstFunctionDeclaration node, Object data) throws Exception
    {
        int i = 0;
        SimpleNode declarator = null;
        do
        {
            declarator = (SimpleNode) node.jjtGetChild(i++);
        } while (!(declarator instanceof AstFunctionDeclarator));
        //child is FunctionDeclarator now
        SimpleNode block = null;
        int n = node.jjtGetNumChildren();
        if (i < n)
        {
            do
            {
                block = (SimpleNode) node.jjtGetChild(i++);
            } while (!(block instanceof AstBlock) && i < n);
        }
        return getCurrentContext().getUserDefinedFunctions().put((String) declarator.getData(),
                new JxpUserDefinedFunction((SimpleNode) declarator.jjtGetChild(0), (AstBlock) block));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstMethodDeclarator, java.lang.Object)
     */
    public Object visit(AstFunctionDeclarator node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException(
                "visit(AstFunctionDeclarator,Object) is not meant to be called."), node);
    }

    /**
     * (non-Javadoc)
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstMethodInvocation, java.lang.Object)
     */
    public Object visit(AstMethodInvocation node, Object data) throws Exception
    {
        //      the prefix node
        Object obj = node.jjtGetChild(0).jjtAccept(this, data);
        SimpleNode methodCall = (SimpleNode) node.jjtGetChild(1);
        String methodName = (String) methodCall.getData();
        if (obj == null)
        {
            throw generateProcessingException(new NullPointerException("Cannot invoke method " + methodName + "(...) of null at "),
                    node);
        }
        Object[] args = resolveArguments((AstArguments) methodCall.jjtGetChild(0), getCurrentContext());
        try
        {
            return invokeMethod(obj, methodName, args);
        } catch (InvocationTargetException e)
        {
            Throwable t = e.getTargetException();
            throw generateProcessingException(t, node);
        } catch (Exception e)
        {
            throw generateProcessingException(e, node);
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstMinusAssignExpression, java.lang.Object)
     */
    public Object visit(AstMinusAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.minus(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstMinusExpression, java.lang.Object)
     */
    public Object visit(AstMinusExpression node, Object data) throws Exception
    {
        return Evaluator.minus(node.jjtGetChild(0).jjtAccept(this, data), node.jjtGetChild(1).jjtAccept(this, data));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstStarAssignExpression, java.lang.Object)
     */
    public Object visit(AstMultiplyAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.multiply(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstMultiplyExpression, java.lang.Object)
     */
    public Object visit(AstMultiplyExpression node, Object data) throws Exception
    {
        return Evaluator.multiply(node.jjtGetChild(0).jjtAccept(this, data), node.jjtGetChild(1).jjtAccept(this, data));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstName, java.lang.Object)
     */
    public Object visit(AstName node, Object data) throws Exception
    {
        List l = (List) node.getData();
        try
        {
            return resolveName(l);
        } catch (Exception e)
        {
            throw generateProcessingException(e, node);
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstNameList, java.lang.Object)
     */
    public Object visit(AstNameList node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException("visit(ASTNameList, Object) not supposed to be called"), node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstNEExpression, java.lang.Object)
     */
    public Object visit(AstNEExpression node, Object data) throws Exception
    {
        Object o1 = node.jjtGetChild(0).jjtAccept(this, data), o2 = node.jjtGetChild(1).jjtAccept(this, data);
        return Evaluator.ne(o1, o2);
    }

    /**
     * (non-Javadoc)
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstObjectAllocationExpression, java.lang.Object)
     */
    public Object visit(AstObjectAllocationExpression node, Object data) throws Exception
    {
        String className = toDottedName((AstName) node.jjtGetChild(0));
        Class type = getCurrentContext().resolveClass(className);
        if (type == null)
        {
            throw generateProcessingException(new ClassNotFoundException("No definition found for " + className), node);
        }
        Object[] args = resolveArguments((AstArguments) node.jjtGetChild(1), getCurrentContext());
        try
        {
            return instantiate(type, args);
        } catch (InvocationTargetException e)
        {
            Throwable t = e.getTargetException();
            throw generateProcessingException(t, node);
        } catch (Exception e)
        {
            throw generateProcessingException(e, node);
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstPlusAssignExpression, java.lang.Object)
     */
    public Object visit(AstPlusAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.plus(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstPlusExpression, java.lang.Object)
     */
    public Object visit(AstPlusExpression node, Object data) throws Exception
    {
        Object o1 = node.jjtGetChild(0).jjtAccept(this, data), o2 = node.jjtGetChild(1).jjtAccept(this, data);
        return Evaluator.plus(o1, o2);        
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstPostfixDecrementExpression, java.lang.Object)
     */
    public Object visit(AstPostDecrementExpression node, Object data) throws Exception
    {
        Object returnValue = node.jjtGetChild(0).jjtAccept(this, data);
        Object newValue = Evaluator.minus(returnValue, ONE);
        if (resolveAssignmentTarget((SimpleNode) node.jjtGetChild(0), false) != null)
        {
            assign((SimpleNode) node.jjtGetChild(0), newValue);
        }
        return returnValue;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstPostfixIncrementExpression, java.lang.Object)
     */
    public Object visit(AstPostIncrementExpression node, Object data) throws Exception
    {
        Object returnValue = node.jjtGetChild(0).jjtAccept(this, data);
        Object newValue = Evaluator.plus(returnValue, ONE);
        if (resolveAssignmentTarget((SimpleNode) node.jjtGetChild(0), false) != null)
        {
            assign((SimpleNode) node.jjtGetChild(0), newValue);
        }
        return returnValue;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstPreDecrementExpression, java.lang.Object)
     */
    public Object visit(AstPreDecrementExpression node, Object data) throws Exception
    {
        Object returnValue = node.jjtGetChild(0).jjtAccept(this, data);
        Object newValue = Evaluator.minus(returnValue, ONE);
        //TODO: improve performance
        if (resolveAssignmentTarget((SimpleNode) node.jjtGetChild(0), false) != null)
        {
            assign((SimpleNode) node.jjtGetChild(0), newValue);
        }
        return newValue;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstPreIncrementExpression, java.lang.Object)
     */
    public Object visit(AstPreIncrementExpression node, Object data) throws Exception
    {
        Object returnValue = node.jjtGetChild(0).jjtAccept(this, data);
        Object newValue = Evaluator.plus(returnValue, ONE);
        //TODO: improve performance
        if (resolveAssignmentTarget((SimpleNode) node.jjtGetChild(0), false) != null)
        {
            assign((SimpleNode) node.jjtGetChild(0), newValue);
        }
        return newValue;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstPrimaryExpression, java.lang.Object)
     */
    public Object visit(AstPrimaryExpression node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException(
                "visit(AstPrimaryExpression, Object) not supposed to be called."), node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstPrimarySuffix, java.lang.Object)
     */
    public Object visit(AstPrimarySuffix node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException("visit(AstPrimarySuffix, Object) not supposed to be called."),
                node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstPrimitiveType, java.lang.Object)
     */
    public Object visit(AstPrimitiveType node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException("visit(AstPrimitiveType, Object) not supposed to be called."),
                node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstPrintStatement, java.lang.Object)
     */
    public Object visit(AstPrintStatement node, Object data) throws Exception
    {
        print(node.jjtGetChild(0).jjtAccept(this, data));
        return null;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstRemainderExpression, java.lang.Object)
     */
    public Object visit(AstRemainderExpression node, Object data) throws Exception
    {
        return Evaluator.remainder(node.jjtGetChild(0).jjtAccept(this, data), node.jjtGetChild(1).jjtAccept(this, data));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstRemAssignExpression, java.lang.Object)
     */
    public Object visit(AstRemAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.remainder(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstReturnStatement, java.lang.Object)
     */
    public Object visit(AstReturnStatement node, Object data) throws Exception
    {
        if (node.jjtGetNumChildren() > 0)
        {
            return new Control(node.jjtGetChild(0).jjtAccept(this, data));
        } else
        {
            return Control.RETURN;
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstRSignedShiftAssignExpression, java.lang.Object)
     */
    public Object visit(AstRSignedShiftAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.rightSignedShift(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstRSignedShiftExpression, java.lang.Object)
     */
    public Object visit(AstRSignedShiftExpression node, Object data) throws Exception
    {
        return Evaluator.rightUnsignedShift(node.jjtGetChild(0).jjtAccept(this, data), node.jjtGetChild(1).jjtAccept(this, data));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstRUnsignedShiftAssignExpression, java.lang.Object)
     */
    public Object visit(AstRUnsignedShiftAssignExpression node, Object data) throws Exception
    {
        Object origValue = node.jjtGetChild(0).jjtAccept(this, data), value = node.jjtGetChild(1).jjtAccept(this, data);
        return assign((SimpleNode) node.jjtGetChild(0), Evaluator.rightUnsignedShift(origValue, value));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstRUnsignedShiftExpression, java.lang.Object)
     */
    public Object visit(AstRUnsignedShiftExpression node, Object data) throws Exception
    {
        return Evaluator.rightUnsignedShift(node.jjtGetChild(0).jjtAccept(this, data), node.jjtGetChild(1).jjtAccept(this, data));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstStatementExpressionList, java.lang.Object)
     */
    public Object visit(AstStatementExpressionList node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException(
                "visit(AstStatementExpressionList, Object) not supposed to be called."), node);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstSwitchStatement, java.lang.Object)
     */
    public Object visit(AstSwitchStatement node, Object data) throws Exception
    {
        Object switchValue = node.jjtGetChild(0).jjtAccept(this, data); //resolve the value first
        if (switchValue == null)
        {
            throw generateProcessingException(new IllegalArgumentException("Switch value cannot be null"), node);
        }
        int n = node.jjtGetNumChildren();
        boolean matchCase = false;
        for (int i = 1; i < n; i++)
        {
            AstCase case_ = (AstCase) node.jjtGetChild(i);
            int caseChildren = case_.jjtGetNumChildren();
            if (!matchCase) //haven't match before
            {//try to match the case
                if (caseChildren == 1)
                {//must be the default: calse
                    matchCase = true;
                } else
                {
                    Object caseValue = case_.jjtGetChild(0).jjtAccept(this, null);
                    if (caseValue == null)
                    {
                        throw generateProcessingException(new IllegalArgumentException("Switch case value cannot be null"), case_);
                    }
                    if (switchValue.equals(caseValue))
                    {
                        matchCase = true;
                    }
                }
            }
            if (matchCase)
            {
                Object obj = case_.jjtGetChild(caseChildren - 1).jjtAccept(this, null);
                if (obj == Control.BREAK)
                {
                    return null;
                } else if (obj instanceof Control)
                {//either return or exit
                    return obj;
                } //else just continue to fall through	            
            }
        }
        return null;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstSynchronizedStatement, java.lang.Object)
     */
    public Object visit(AstSynchronizedStatement node, Object data) throws Exception
    {
        Object obj = node.jjtGetChild(0).jjtAccept(this, data);
        if (obj == null)
        {
            throw generateProcessingException(new IllegalArgumentException("Cannot synchronize on null"), node);
        } else
        {
            synchronized (obj)
            {
                return node.jjtGetChild(1).jjtAccept(this, data);
            }
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstThrowStatement, java.lang.Object)
     */
    public Object visit(AstThrowStatement node, Object data) throws Exception
    {
        Object o = node.jjtGetChild(0).jjtAccept(this, data);
        if (o instanceof Exception)
        {
            throw (Exception) o;
        } else
        {
            throw generateProcessingException(new IllegalArgumentException("Cannot throw non-exception " + o), node);
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstTryStatement, java.lang.Object)
     */
    public Object visit(AstTryStatement node, Object data) throws Exception
    {
        int n = node.jjtGetNumChildren();
        int i = 0;
        try
        {
            SimpleNode block = (SimpleNode) node.jjtGetChild(0);
            return block.jjtAccept(this, data);
        } catch (Throwable e)
        {
            JxpProcessingContext context = getCurrentContext();
            Throwable realCause = e;
            if (realCause instanceof ProcessingException && realCause.getCause() != null)
            {
                realCause = realCause.getCause();
            }
            for (i = 1; i < n; i++)
            {
                SimpleNode handlerBlock = (SimpleNode) node.jjtGetChild(i);
                if (handlerBlock instanceof AstCatchBlock)
                {
                    //catchblock
                    //SimpleNode formalParameter = (SimpleNode) handlerBlock.jjtGetChild(0);
                    AstType type = (AstType) handlerBlock.jjtGetChild(0);
                    Class c = (Class) type.jjtAccept(this, data);
                    if (c.isAssignableFrom(realCause.getClass()))
                    {
                        int scope = context.getNametableStack().newScope();
                        try
                        {
                            AstVariableDeclaratorId id = (AstVariableDeclaratorId) handlerBlock.jjtGetChild(1);
                            context.getNametableStack().declare((String) id.getData(), realCause);
                            return handlerBlock.jjtGetChild(2).jjtAccept(this, data); //execute the catch block
                        } catch (Exception eAgain)
                        {
                            //again
                            throw generateProcessingException(eAgain, (SimpleNode) handlerBlock.jjtGetChild(1));
                        } finally
                        {
                            context.getNametableStack().closeScope(scope);
                        }
                    }
                }
            } //exception has no handler
            if (e instanceof ProcessingException)
            {
                throw (ProcessingException) e; //rethrown - should be because of block
            } else
            {
                throw new IllegalStateException("Should be ProcessingException after try block");
            }
        } finally
        {
            if (i < n)
            { //still something un
                SimpleNode block = (SimpleNode) node.jjtGetChild(n - 1);
                if (block instanceof AstFinallyBlock)
                {
                    block.jjtAccept(this, data);
                }
            }
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstUnaryMinusExpression, java.lang.Object)
     */
    public Object visit(AstUnaryMinusExpression node, Object data) throws Exception
    {
        return Evaluator.negate(node.jjtGetChild(0).jjtAccept(this, data));
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstUnaryPlusExpression, java.lang.Object)
     */
    public Object visit(AstUnaryPlusExpression node, Object data) throws Exception
    {
        return node.jjtGetChild(0).jjtAccept(this, data);
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstVariableDeclarator, java.lang.Object)
     */
    public Object visit(AstVariableDeclarator node, Object data) throws Exception
    {
        Object[] passOn = (Object[]) data;
        Object value = null;
        if (node.jjtGetNumChildren() > 1)
        { //has initializer
            value = node.jjtGetChild(1).jjtAccept(this, null);
        } else
        {
            AstType type = (AstType) passOn[0];
            if (type.jjtGetChild(0) instanceof AstPrimitiveType)
            {
                AstPrimitiveType primitive = (AstPrimitiveType) type.jjtGetChild(0);
                value = PRIMITIVE_DEFAULTS.get(primitive.getData());
            }
        }
        String variableName = (String) ((AstVariableDeclaratorId) node.jjtGetChild(0)).getData();
        if (((Boolean) passOn[1]).booleanValue())
        {//static variable flag, pass from visit(AstFieldDeclaration)
            JxpPage page = getCurrentContext().getCurrentPage();
            if (!page.hasStaticVariable(variableName))
            {
                page.declareStaticVariable(variableName, value);
            }
            return value;
        } else
        {
            getCurrentContext().getNametableStack().declare(variableName, value);
            return value;
        }
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.AstWhileStatement, java.lang.Object)
     */
    public Object visit(AstWhileStatement node, Object data) throws Exception
    {
        Node condition = node.jjtGetChild(0);
        Boolean b = Evaluator.toBoolean(condition.jjtAccept(this, data));
        Object returnValue = null;
        while (b.booleanValue())
        {
            returnValue = node.jjtGetChild(1).jjtAccept(this, data);
            if (returnValue instanceof Control)
            {
                if (returnValue == Control.CONTINUE)
                {
                    returnValue = null; //clear up
                } else if (returnValue == Control.BREAK)
                {
                    returnValue = null; //clear up
                    break;
                } else
                { //must be return w/wo object
                    break;
                }
            }
            b = Evaluator.toBoolean(condition.jjtAccept(this, data));
        }
        return returnValue;
    }

    /**
     * @see jxp.parser.JxpParserVisitor#visit(jxp.parser.SimpleNode, java.lang.Object)
     */
    public Object visit(SimpleNode node, Object data) throws Exception
    {
        throw new IllegalStateException("Illegal state");
    }

    /**
     * Flush the writer
     * @throws IOException if there's io exception
     */
    public void flush() throws IOException
    {
        getCurrentContext().getWriter().flush();
    }

    /** 
     * {@inheritDoc}
     * @todo support array type
     * TODO support array type
     */
    public Object visit(AstType node, Object data) throws Exception
    {
        Node child = node.jjtGetChild(0);
        Class c = null;
        if (child instanceof AstName)
        {
            AstName name = (AstName) child;
            String className = toDottedName(name);
            c = getCurrentContext().resolveClass(className);
            if (c == null)
            {
                throw generateProcessingException(new ClassNotFoundException("Class " + className + " not found"), node);
            }
        } else
        {//primitive type
            c = (Class) ((AstPrimitiveType)child).getData();
        }
        //TODO: work on the dimension
        return c;
    }

    /**
     * Get the method stats
     * @return the method stats
     */
    public Counter getMethodStats()
    {
        return _methodStats;
    }

    /** 
     * {@inheritDoc}
     */
    public Object visit(AstVariableDeclaratorId node, Object data) throws Exception
    {
        // TODO Auto-generated method stub
        throw generateProcessingException(new IllegalStateException(
                "visit(AstVariableDeclaratorId,Object) should not be called directly"), node);
    }

    /** 
     * {@inheritDoc}
     */
    public Object visit(AstExitStatement node, Object data) throws Exception
    {
        return Control.EXIT;
    }

    /** 
     * {@inheritDoc}
     */
    public Object visit(AstStaticImportDeclaration node, Object data) throws Exception
    {
        JxpProcessingContext context = getCurrentContext();
        AstName name = (AstName) node.jjtGetChild(0);
        String className = toDottedName(name);
        Class c = context.resolveClass(className);
        if (c == null)
        {
            throw generateProcessingException(new IllegalArgumentException("Cannot resolve class " + className), node);
        }
        StaticImport si = StaticImportUtils.getStaticImport(c);
        context.addStaticImport(si);
        Iterator it = si.getStaticFields().entrySet().iterator();
        NametableStack ntStack = context.getNametableStack();
        while (it.hasNext())
        {
            Map.Entry entry = (Map.Entry) it.next();
            ntStack.declare((String) entry.getKey(), entry.getValue());
        }
        return null;
    }

    /** 
     * {@inheritDoc}
     */
    public Object visit(AstVariableParameters node, Object data) throws Exception
    {
        throw generateProcessingException(new IllegalStateException("visit(AstVariableParameters) should not be called directly"),
                node);
    }

    /** 
     * {@inheritDoc}
     */
    public Object visit(AstEnhancedForStatement node, Object data) throws Exception
    {
        JxpProcessingContext context = getCurrentContext();
        String variable = (String) node.getData();
        Node statements = node.jjtGetChild(2);
        Object colObj = node.jjtGetChild(1).jjtAccept(this, data);
        if (colObj instanceof Collection)
        {//must be
            Object returnValue = null;
            Iterator it = ((Collection) colObj).iterator();
            int newscope = context.getNametableStack().newScope();
            try
            {
                context.getNametableStack().declare(variable, null);
                while (it.hasNext())
                {
                    assignVariable(variable, it.next());
                    returnValue = statements.jjtAccept(this, data);
                    if (returnValue == Control.CONTINUE)
                    {
                        returnValue = null;
                        //let it pass
                    } else if (returnValue == Control.BREAK)
                    {
                        returnValue = null;
                        break;
                    } else if (returnValue instanceof Control)
                    { //must be return w/wo object
                        break;
                    }
                }
                return returnValue;
            } finally
            {
                context.getNametableStack().closeScope(newscope);
            }
        } else
        {
            throw generateProcessingException(new IllegalArgumentException(
                    "Expression for enhanced for loop must be collection, get " + colObj + " instead"), node);
        }
    }

    public void setDoMethodStats(boolean flag)
    {
        doMethodStats = flag;
    }

    public Object visit(AstNamedArgument node, Object data) throws Exception
    {
        return node; //process it later
    }
    
    /**
     * Get current environment
     * @return the environment
     */
    public Map getEnvironment()
    {
        return getCurrentContext().getNametableStack().asMap();
    }

    
    /**
     * Return the checkFunctionArgumentType
     * @return the checkFunctionArgumentType.
     */
    protected final boolean isCheckFunctionArgumentType()
    {
        return _checkFunctionArgumentType;
    }
    

    
    /**
     * Set the checkFunctionArgumentType
     * @param checkFunctionArgumentType The checkFunctionArgumentType to set.
     */
    protected final void setCheckFunctionArgumentType(boolean checkFunctionArgumentType)
    {
        _checkFunctionArgumentType = checkFunctionArgumentType;
    }

    public Object visit(AstPageDirective node, Object data) throws Exception
    {
        Map m = (Map)node.getData();        
        if (m.containsKey("import")){
            String imports = (String)m.get("import");
            JxpProcessingContext context = getCurrentContext();
            StringTokenizer st = new StringTokenizer(imports, ",");
            while (st.hasMoreTokens()){
                String im = st.nextToken();
                context.getImports().addImport(im);
            }
        }
        return null;
    }
    

    public Object visit(AstIncludeDirective node, Object data) throws Exception
    {
        return null;
    }

    public Object visit(AstIsEmptyExpression node, Object data) throws Exception
    {
        Object obj = node.jjtGetChild(0).jjtAccept(this, data);
        return Boolean.valueOf(StringUtils.isNullOrEmpty(obj));
    }
    
}