/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * 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 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
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.lib.ejb21.sql;

import java.util.ArrayList;
import java.util.Map;
import java.util.Stack;

import javax.ejb.EJBObject;

import org.ow2.jonas.deployment.ejb.ejbql.ASTArithmeticExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTArithmeticFactor;
import org.ow2.jonas.deployment.ejb.ejbql.ASTArithmeticLiteral;
import org.ow2.jonas.deployment.ejb.ejbql.ASTArithmeticTerm;
import org.ow2.jonas.deployment.ejb.ejbql.ASTBetweenExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTBooleanExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTBooleanLiteral;
import org.ow2.jonas.deployment.ejb.ejbql.ASTCmpPathExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTCollectionMemberExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTCollectionValuedPathExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTComparisonExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTConditionalExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTConditionalFactor;
import org.ow2.jonas.deployment.ejb.ejbql.ASTConditionalTerm;
import org.ow2.jonas.deployment.ejb.ejbql.ASTDatetimeExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTEJBQL;
import org.ow2.jonas.deployment.ejb.ejbql.ASTEmptyCollectionComparisonExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTEntityBeanExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTFloatingPointLiteral;
import org.ow2.jonas.deployment.ejb.ejbql.ASTFunctionsReturningNumerics;
import org.ow2.jonas.deployment.ejb.ejbql.ASTFunctionsReturningStrings;
import org.ow2.jonas.deployment.ejb.ejbql.ASTIdentificationVariable;
import org.ow2.jonas.deployment.ejb.ejbql.ASTInExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTInputParameter;
import org.ow2.jonas.deployment.ejb.ejbql.ASTIntegerLiteral;
import org.ow2.jonas.deployment.ejb.ejbql.ASTLikeExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTLiteral;
import org.ow2.jonas.deployment.ejb.ejbql.ASTNullComparisonExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTPath;
import org.ow2.jonas.deployment.ejb.ejbql.ASTSingleValuedCmrPathExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTSingleValuedPathExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTStringExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTStringLiteral;
import org.ow2.jonas.deployment.ejb.ejbql.ASTWhereClause;
import org.ow2.jonas.deployment.ejb.ejbql.EJBQLConstants;
import org.ow2.jonas.deployment.ejb.ejbql.ParseException;
import org.ow2.jonas.deployment.ejb.ejbql.SimpleNode;
import org.ow2.jonas.lib.ejb21.jorm.JormType;

import org.objectweb.jorm.api.PMapper;
import org.objectweb.jorm.lib.JormPathHelper;
import org.objectweb.jorm.naming.api.PName;
import org.objectweb.jorm.type.api.PType;
import org.objectweb.medor.api.Field;
import org.objectweb.medor.expression.api.MalformedExpressionException;
import org.objectweb.medor.expression.api.Expression;
import org.objectweb.medor.filter.api.FieldOperand;
import org.objectweb.medor.expression.api.Operand;
import org.objectweb.medor.filter.jorm.lib.IsNullPName;
import org.objectweb.medor.expression.lib.Abs;
import org.objectweb.medor.expression.lib.And;
import org.objectweb.medor.filter.lib.BasicFieldOperand;
import org.objectweb.medor.expression.lib.BasicOperand;
import org.objectweb.medor.expression.lib.BasicParameterOperand;
import org.objectweb.medor.filter.lib.CollectionOperand;
import org.objectweb.medor.expression.lib.Concat;
import org.objectweb.medor.expression.lib.DivideBy;
import org.objectweb.medor.expression.lib.Equal;
import org.objectweb.medor.expression.lib.FirstLocate;
import org.objectweb.medor.expression.lib.Greater;
import org.objectweb.medor.expression.lib.GreaterEqual;
import org.objectweb.medor.filter.lib.InCollection;
import org.objectweb.medor.expression.lib.IndexedLocate;
import org.objectweb.medor.filter.lib.IsEmpty;
import org.objectweb.medor.filter.lib.IsNull;
import org.objectweb.medor.expression.lib.Length;
import org.objectweb.medor.expression.lib.Like;
import org.objectweb.medor.expression.lib.Lower;
import org.objectweb.medor.expression.lib.LowerEqual;
import org.objectweb.medor.filter.lib.MemberOf;
import org.objectweb.medor.expression.lib.Minus;
import org.objectweb.medor.expression.lib.Mod;
import org.objectweb.medor.expression.lib.Mult;
import org.objectweb.medor.expression.lib.Not;
import org.objectweb.medor.expression.lib.NotEqual;
import org.objectweb.medor.expression.lib.Or;
import org.objectweb.medor.expression.lib.Plus;
import org.objectweb.medor.expression.lib.Sqrt;
import org.objectweb.medor.expression.lib.Substring;
import org.objectweb.medor.expression.lib.UMinus;
import org.objectweb.medor.query.api.PropagatedField;
import org.objectweb.medor.query.jorm.lib.PNameField;
import org.objectweb.medor.query.jorm.lib.QueryBuilder;
import org.objectweb.medor.type.lib.PTypeSpaceMedor;
import org.objectweb.medor.type.lib.QType;

/**
 * Implementation of a visitor that creates the filter corresponding
 * to the WHERE clause.
 * Created on Sep 6, 2002
 * @author Christophe Ney  [cney@batisseurs.com] : Intial developer
 * @author Helene Joanin: Take into account the following operators
 *                  abs(x), sqrt(x),
 *                  length(str), substring(s, start, length),
 *                  locate(str1, str2), locate(str1, str2, start).
 * @author Helene Joanin: EJBLocalObject as parameter of a EJB-QL query.
 * @author Helene Joanin: Take into account the InExpression (IN operator).
 * @author Helene Joanin: Take into account the IS NULL operator.
 * @author Helene Joanin: Fix a bug in the visitor for ASTIdentificationVariable:
 *                  push a BasicFieldOperand(field) instead of the field itself.
 * @author Helene Joanin: add '.element' to the path for CollectionValuedPathExpression.
 * @author Helene Joanin: Convert IsNull(cmr-field) to Equal(cmr-field, PNameNull).
 * @author Helene Joanin: Take into account the operators: IS EMPTY / IS NOT EMPTY
 * @author Helene Joanin: Take into account the CONCAT operator
 * @author Helene Joanin: Take into account the EJBQL version 2.1 syntax.
 * @author Helene Joanin: Take into account the operator MOD.
 * @author Helene Joanin: Take into account the operator ISNULL with a parameter.
 */
public class EjbqlQueryFilterVisitor extends EjbqlAbstractVisitor {

    Expression qf = null;
    PMapper mapper = null;
    Map fields = null;
    Class[] parameterTypes;
    QueryBuilder qb;

    static final String OP_IGNORE = "ignore";

    /**
     * Constructor
     * @param _mapper the mapper of each fields.
     *        Needed to build the expression for the IsNull with a reference as an operand.
     *        This parameter may be null in case of the GenIC phase
     * @param _fields QueryTreeFields for all defined identifiers and all path expression of the query
     * @param parameterTypes Type of paramaters of the finder/select method
     * @param ejbql root of the lexical tree of the query
     */
    public EjbqlQueryFilterVisitor(PMapper _mapper,
                                   Map _fields,
                                   Class[] parameterTypes,
                                   ASTEJBQL ejbql,
                                   QueryBuilder qb) throws Exception {
        mapper = _mapper;
        fields = _fields;
        this.parameterTypes = parameterTypes;
        this.qb = qb;
        visit(ejbql);
    }

    /**
     * get the query filter that was built from visiting the syntaxic tree
     */
    public Expression getQueryFilter() {
        return qf;
    }

    /**
     * If query contains WHERE clause, visit child nodes<br>
     * where_clause ::= WHERE conditional_expression
     */
    public Object visit(ASTWhereClause node, Object data) {
        visit((SimpleNode) node, data);
        if (OP_IGNORE.equals(((Stack) data).peek())) {
            qf = null;
        } else {
            qf = (Expression) ((Stack) data).pop();
        }
        return null;
    }

    /**
     * Push corresponding MedorField to the stack.<br>
     * single_valued_path_expression ::= path
     * was in initial BNF
     * single_valued_path_expression ::= cmp_path_expression | single_valued_cmr_path_expression
     */
    public Object visit(ASTSingleValuedPathExpression node, Object data) {
        visit((SimpleNode) node, data);
        try {
            String path = (String) ((ASTPath) ((Stack) data).pop()).value;
            Field f = (Field) fields.get(path);
            // FIXME check type for single valued
            // test GenClassRef or ClassRef
            ((Stack) data).push(new BasicFieldOperand(f));
        } catch (Exception e) {
            throw new VisitorException(e);
        }
        return null;
    }

    /**
     * Push corresponding MedorField to the stack.<br>
     * cmp_path_expression ::= path
     * was in initial BNF
     * cmp_path_expression ::= identification_variable. [ single_valued_cmr_field. ] * cmp_field
     */
    public Object visit(ASTCmpPathExpression node, Object data) {
        visit((SimpleNode) node, data);
        try {
            String path = (String) ((ASTPath) ((Stack) data).pop()).value;
            Field f = (Field) fields.get(path);
            // FIXME check type for cmp field
            ((Stack) data).push(new BasicFieldOperand(f));
        } catch (Exception e) {
            throw new VisitorException(e);
        }
        return null;
    }

    /**
     * Push corresponding MedorField to the stack.<br>
     * single_valued_cmr_path_expression ::= path
     * was in initial BNF
     * single_valued_cmr_path_expression ::= identification_variable. [ single_valued_cmr_field. ] * single_valued_cmr_field
     */
    public Object visit(ASTSingleValuedCmrPathExpression node, Object data) {
        visit((SimpleNode) node, data);
        try {
            String path = (String) ((ASTPath) ((Stack) data).pop()).value;
            Field f = (Field) fields.get(path);
            // FIXME check type for single valued cmr
            ((Stack) data).push(new BasicFieldOperand(f));
        } catch (Exception e) {
            throw new VisitorException(e);
        }
        return null;
    }

    /**
     * Push corresponding MedorField to the stack.<br>
     * collection_valued_path_expression ::= path
     * was in initial BNF
     * collection_valued_path_expression ::= identification_variable. [ single_valued_cmr_field. ] *collection_valued_cmr_field
     */
    public Object visit(ASTCollectionValuedPathExpression node, Object data) {
        visit((SimpleNode) node, data);
        try {
            // FIXME check type for collection valued cmr
            String path = (String) ((ASTPath) ((Stack) data).pop()).value;
            String[] parts = splitPath(path);
            String first = parts[0];
            String rest = mergePath(parts, 1, parts.length - 1);
            QueryBuilder subquery = new QueryBuilder(qb);
            subquery.define("", qb.navigate(first));
            Field f = subquery.project(subquery.navigate(rest));
            ((Stack) data).push(new BasicFieldOperand(f));
        } catch (Exception e) {
            throw new VisitorException(e);
        }
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * conditional_expression ::= conditional_term [ OR conditional_term ]*
     */
    public Object visit(ASTConditionalExpression node, Object data) {
        visit((SimpleNode) node, data);
        Object ret = ((Stack) data).pop();
        for (int i = 1; i < node.jjtGetNumChildren(); i++) {
            if (OP_IGNORE.equals(ret)) {
                ret = ((Stack) data).pop();
            } else if (OP_IGNORE.equals(((Stack) data).peek())) {
                ((Stack) data).pop();
            } else {
                ret = new Or((Expression) ((Stack) data).pop(), (Expression) ret);
            }
        }
        // push result on Stack
        ((Stack) data).push(ret);
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * conditional_term ::= conditional_factor [ AND conditional_factor ]*
     */
    public Object visit(ASTConditionalTerm node, Object data) {
        visit((SimpleNode) node, data);
        Object ret = ((Stack) data).pop();
        for (int i = 1; i < node.jjtGetNumChildren(); i++) {
            if (OP_IGNORE.equals(ret)) {
                ret = ((Stack) data).pop();
            } else if (OP_IGNORE.equals(((Stack) data).peek())) {
                ((Stack) data).pop();
            } else {
                ret = new And((Expression) ((Stack) data).pop(), (Expression) ret);
            }
        }
        // push result on Stack
        ((Stack) data).push(ret);
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * conditional_factor ::= [ NOT ] conditional_test
     */
    public Object visit(ASTConditionalFactor node, Object data) {
        visit((SimpleNode) node, data);
        if (node.not) {
            if (!OP_IGNORE.equals(((Stack) data).peek())) {
                Expression ret = (Expression) ((Stack) data).pop();
                // push result on Stack
                ((Stack) data).push(new Not(ret));
            }
        }
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * between_expression ::= arithmetic_expression  [ NOT ] BETWEEN arithmetic_expression AND arithmetic_expression
     */
    public Object visit(ASTBetweenExpression node, Object data) {
        visit((SimpleNode) node, data);
        Expression[] terms = new Expression[3];
        terms[2] = (Expression) ((Stack) data).pop();
        terms[1] = (Expression) ((Stack) data).pop();
        terms[0] = (Expression) ((Stack) data).pop();
        if (node.not) {
            ((Stack) data).push(new Or(new Lower(terms[0], terms[1]), new Greater(terms[0], terms[2])));
        } else {
            ((Stack) data).push(new And(new GreaterEqual(terms[0], terms[1]), new LowerEqual(terms[0], terms[2])));
        }
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * in_expression ::= cmp_path_expression  [ NOT ] IN
     *                       {literal|input_parameter}  [ , {literal|input_parameter} ] * )
     */
    public Object visit(ASTInExpression node, Object data) {
        visit((SimpleNode) node, data);
        Stack s = (Stack) data;
        ArrayList c = new ArrayList();
        for (int i = 1; i <= node.eltnum; i++) {
            c.add(s.pop());
        }
        CollectionOperand co = new CollectionOperand(c);
        FieldOperand f = (FieldOperand) s.pop();
        if (node.not) {
            s.push(new Not(new InCollection(f, co, f.getField().getType())));
        } else {
            s.push(new InCollection(f, co, f.getField().getType()));
        }
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * like_expression ::= cmp_path_expression [ NOT ] LIKE pattern_value
     *                                                        [ ESCAPE escape_character ]
     */
    public Object visit(ASTLikeExpression node, Object data) {
        visit((SimpleNode) node, data);
        Stack s = (Stack) data;
        Expression e1 = (Expression) s.pop();
        Expression e2 = (Expression) s.pop();
        Expression e3 = null;
        if (node.third) {
            //The like operator contains a ESCAPE section.
            e3 = (Expression) s.pop();
        }
        if (e3 == null) {
            s.push(new Like(e2, e1, node.not));
        } else {
            s.push(new Like(e3, e2, e1, node.not));
        }
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * null_comparison_expression ::= {single_valued_path_expression|input_parameter} IS  [ NOT ] NULL
     */
    public Object visit(ASTNullComparisonExpression node, Object data) {
        visit((SimpleNode) node, data);
        Stack s = (Stack) data;
        Expression e1 = (Expression) s.pop();
        if ((e1 instanceof BasicParameterOperand)
            && (e1.getType().getTypeCode() == QType.TYPECODE_PNAME)) {
            // The operand is a bean parameter: use IsNullPName
            if (node.not) {
                s.push(new Not(new IsNullPName(((BasicParameterOperand)e1).getName())));
            } else {
                s.push(new IsNullPName(((BasicParameterOperand)e1).getName()));
            }
        } else if (e1.getType().getTypeCode() == PType.TYPECODE_REFERENCE) {
            // The operand is a cmr field: use the Equal operator with PNameNull as second operand
            // Warning: The PNameNull value may not be build because the mapper may be null
            //          in case of this expression building is done in the GenIC phase
            Field f = ((FieldOperand) e1).getField();
            PNameField pnf = null;
            if (f instanceof PNameField) {
                pnf = (PNameField) f;
            } else if (f instanceof PropagatedField) {
                pnf = (PNameField)((PropagatedField) f).getOriginFields()[0];
            } else {
                // error: unmanaged case
                throw new Error("Operand of IS NULL not valid !?");
            }
            Operand op2;
            if (mapper == null) {
                // dummy operand2 to be able to build a query even in the GenIC phase
                op2 = new BasicOperand("pnamenull");
            } else {
                // real operand2 PNameNull
                PName pnamenull = JormPathHelper.getPNameCoder(pnf.getPNamingContextParameter(),
                                                               mapper).getNull();
                op2 = new BasicOperand(pnamenull, PTypeSpaceMedor.PNAME);
            }
            if (node.not) {
                s.push(new Not(new Equal(e1, op2)));
            } else {
                s.push(new Equal(e1, op2));
            }
        } else {
            // The operand is a cmp field, or a ...: use the IsNull operator
            if (node.not) {
                s.push(new Not(new IsNull(e1)));
            } else {
                s.push(new IsNull(e1));
            }
        }
        return null;

    }

    /**
     * Nothing to do:
     *   Already taken into account in EjbqlVariableVisitor at the variables parsing,
     *   so, just push the OP_IGNORE in the stack
     * empty_collection_comparison_expression ::= collection_valued_path_expression IS [ NOT ] EMPTY
     */
    public Object visit(ASTEmptyCollectionComparisonExpression node, Object data) {
        visit((SimpleNode) node, data);
        Stack s = (Stack) data;
        Expression e = (Expression) s.pop();
        Expression res = new IsEmpty(e);
        if (node.not) {
            res = new Not(res);
        }
        s.push(res);
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * collection_member_expression ::= {single_valued_cmr_path_expression | identification_variable | input_parameter}
     * [ NOT ] MEMBER  [ OF ] collection_valued_path_expression
     */
    public Object visit(ASTCollectionMemberExpression node, Object data) {
        visit((SimpleNode) node, data);
        Stack s = (Stack) data;
        Expression e2 = (Expression) s.pop();
        Expression e1 = (Expression) s.pop();
        ArrayList le2 = new ArrayList(); le2.add(e2);
        ArrayList le1 = new ArrayList(); le1.add(e1);
        Expression res = null;
        try {
            res = new MemberOf(le1, le2);
        } catch (MalformedExpressionException e) {
            throw new Error("Operand expression of MEMBER OF malformed: "
                            + e + e.getNestedException());
        }
        if (node.not) {
            s.push(new Not(res));
        } else {
            s.push(res);
        }
        return null;
    }


    /**
     * Push corresponding Expression to the stack.<br>
     * comparison_expression ::= string_value { = | > | >= | < | <= | <> } string_expression
     *    | boolean_value { = | <>} boolean_expression
     *    | datetime_value { = | > | >= | < | <= | <> } datetime_expression
     *    | entity_bean_value { = | <> } entity_bean_expression
     *    | arithmetic_value { = | > | >= | < | <= | <> } single_value_designator
     */
    public Object visit(ASTComparisonExpression node, Object data) {
        visit((SimpleNode) node, data);
        Expression[] terms = new Expression[2];
        terms[1] = (Expression) ((Stack) data).pop();
        terms[0] = (Expression) ((Stack) data).pop();
        int op = ((Integer) node.ops.get(0)).intValue();
        switch (op) {
        case EJBQLConstants.EQ:
            ((Stack) data).push(new Equal(terms[0], terms[1]));
            break;
        case EJBQLConstants.NE:
            ((Stack) data).push(new NotEqual(terms[0], terms[1]));
            break;
        case EJBQLConstants.GT:
            ((Stack) data).push(new Greater(terms[0], terms[1]));
            break;
        case EJBQLConstants.GE:
            ((Stack) data).push(new GreaterEqual(terms[0], terms[1]));
            break;
        case EJBQLConstants.LT:
            ((Stack) data).push(new Lower(terms[0], terms[1]));
            break;
        case EJBQLConstants.LE:
            ((Stack) data).push(new LowerEqual(terms[0], terms[1]));
            break;
        }
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * arithmetic_expression ::= arithmetic_term [ { + | - } arithmetic_term ] *
     */
    public Object visit(ASTArithmeticExpression node, Object data) {
        visit((SimpleNode) node, data);
        Expression ret = (Expression) ((Stack) data).pop();
        for (int i = 0; i < node.jjtGetNumChildren() - 1; i++) {
            switch (((Integer) node.ops.get(node.jjtGetNumChildren() - 2 - i)).intValue()) {
            case EJBQLConstants.PLUS:
                ret = new Plus((Expression) ((Stack) data).pop(), ret);
                break;
            case EJBQLConstants.MINUS:
                ret = new Minus((Expression) ((Stack) data).pop(), ret);
                break;
            }
        }
        // push result to the Stack
        ((Stack) data).push(ret);
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * arithmetic_term ::= arithmetic_factor [ { * | / } arithmetic_factor ]*
     */
    public Object visit(ASTArithmeticTerm node, Object data) {
        visit((SimpleNode) node, data);
        Expression ret = (Expression) ((Stack) data).pop();
        for (int i = 0; i < node.jjtGetNumChildren() - 1; i++) {
            switch (((Integer) node.ops.get(node.jjtGetNumChildren() - 2 - i)).intValue()) {
            case EJBQLConstants.MULT:
                ret = new Mult((Expression) ((Stack) data).pop(), ret);
                break;
            case EJBQLConstants.DIV:
                ret = new DivideBy((Expression) ((Stack) data).pop(), ret);
                break;
            }
        }
        // push result to the Stack
        ((Stack) data).push(ret);
        return null;
    }

    /**
     * Push corresponding Expression to the stack.<br>
     * arithmetic_factor ::= [ + |- ] arithmetic_primary
     */
    public Object visit(ASTArithmeticFactor node, Object data) {
        visit((SimpleNode) node, data);
        Expression ret = (Expression) ((Stack) data).pop();
        if (node.ops.size() > 0) {
            if (((Integer) node.ops.get(0)).intValue() == EJBQLConstants.MINUS) {
                ret = new UMinus((Expression) ret);
            }
        }
        // push result on Stack
        ((Stack) data).push(ret);
        return null;
    }


    /**
     * Visit child nodes
     * string_expression ::= string_primary | input_parameter
     */
    public Object visit(ASTStringExpression node, Object data) {
        visit((SimpleNode) node, data);
        // FIXME check or convert type?
        return null;
    }

    /**
     * Visit child nodes
     * datetime_expression ::= datetime_value | input_parameter
     */
    public Object visit(ASTDatetimeExpression node, Object data) {
        visit((SimpleNode) node, data);
        // FIXME check or convert type?
        return null;
    }

    /**
     * Visit child nodes
     * boolean_expression ::= cmp_path_expression | boolean_literal | input_parameter
     */
    public Object visit(ASTBooleanExpression node, Object data) {
        visit((SimpleNode) node, data);
        // FIXME check or convert type?
        return null;
    }

    /**
     * Visit child nodes
     * entity_bean_expression ::= entity_bean_value | input_parameter
     */
    public Object visit(ASTEntityBeanExpression node, Object data) {
        visit((SimpleNode) node, data);
        // FIXME check or convert type?
        return null;
    }

    /**
     * visit child nodes
     * Push corresponding Expression to the stack.<br>
     * functions_returning_strings ::= CONCAT (string_expression, string_expression)
     *                               | SUBSTRING (string_expression,arithmetic_expression,arithmetic_expression)
     */
    public Object visit(ASTFunctionsReturningStrings node, Object data) {
        visit((SimpleNode) node, data);
        Stack s = (Stack) data;
        int op = ((Integer) node.ops.get(0)).intValue();
        Expression ret = null;
        Expression e1, e2;
        switch (op) {
        case EJBQLConstants.CONCAT:
            // e1, e2 are in reverse order in the stack
            e2 = (Expression) s.pop();
            e1 = (Expression) s.pop();
            ret = new Concat(e1, e2);
            break;
        case EJBQLConstants.SUBSTRING:
            // e1, e2, e3 are in reverse order in the stack
            Expression e3 = (Expression) s.pop();
            e2 = (Expression) s.pop();
            e1 = (Expression) s.pop();
            ret = new Substring(e1, e2, e3);
            break;
        }
        // push result to the Stack
        s.push(ret);
        return null;
    }


    /**
     * visit child nodes
     * Push corresponding Expression to the stack.<br>
     * functions_returning_numerics ::= LENGTH (string_expression)
     *                                | LOCATE (string_expression, string_expression
     *                                               [ , arithmetic_expression ] )
     *                                | ABS (arithmetic_expression)
     *                                | SQRT (arithmetic_expression)
     *                                | MOD (arithmetic_expression , arithmetic_expression)
     */
    public Object visit(ASTFunctionsReturningNumerics node, Object data) {
        visit((SimpleNode) node, data);
        Stack s = (Stack) data;
        int op = ((Integer) node.ops.get(0)).intValue();
        Expression e1, e2;
        Expression ret = null;
        switch (op) {
        case EJBQLConstants.LENGTH:
            e1 = (Expression) s.pop();
            ret = new Length(e1);
            break;
        case EJBQLConstants.LOCATE:
            // Operands are in the reverse order in the stack
            Expression e3 = null;
            if (node.third) {
                // The locate operator contains a third operand.
                e3 = (Expression) s.pop();
            }
            e2 = (Expression) s.pop();
            e1 = (Expression) s.pop();
            // Warning on the args order for IndexedLocate and FirstLocate.
            if (node.third) {
                ret = new IndexedLocate(e2, e1, e3);
            } else {
                ret = new FirstLocate(e1, e2);
            }
            break;
        case EJBQLConstants.ABS:
            e1 = (Expression) s.pop();
            ret = new Abs(e1);
            break;
        case EJBQLConstants.SQRT:
            e1 = (Expression) s.pop();
            ret = new Sqrt(e1);
            break;
        case EJBQLConstants.MOD:
            e2 = (Expression) s.pop();
            e1 = (Expression) s.pop();
            ret = new Mod(e1, e2);
            break;
        }
        // push result to the Stack
        s.push(ret);
        return null;
    }

    /**
     * Node with value set to identification variable string.
     * Push the Node to the stack
     */
    public Object visit(ASTIdentificationVariable node, Object data) {
    	// Identification variables are case insensitive
    	String vName = ((String) node.value).toLowerCase();
        String fieldname = vName + "." + Field.PNAMENAME;
        Field f = (Field) fields.get(fieldname);
        ((Stack) data).push(new BasicFieldOperand(f));
        return null;
    }

    /**
     * Visit child nodes
     * literal ::= string_literal | arithmetic_literal | boolean_literal
     */
    public Object visit(ASTLiteral node, Object data) {
        visit((SimpleNode) node, data);
        return null;
    }

    /**
     * Node with value set to litteral string.
     * Push the corresponding Operand to the stack
     */
    public Object visit(ASTStringLiteral node, Object data) {
        ((Stack) data).push(new BasicOperand((String) node.value));
        return null;
    }

    /**
     * Visit child nodes
     * arithmetic_literal ::= integer_literal | floatingpoint_literal
     */
    public Object visit(ASTArithmeticLiteral node, Object data) {
        visit((SimpleNode) node, data);
        return null;
    }

    /**
     * Node with value set to integer litteral arithmetic.
     * Push the corresponding Operand to the stack
     */
    public Object visit(ASTIntegerLiteral node, Object data) {
        ((Stack) data).push(new BasicOperand(((Long) node.value).longValue()));
        return null;
    }

    /**
     * Node with value set to integer litteral arithmetic.
     * Push the corresponding Operand to the stack
     */
    public Object visit(ASTFloatingPointLiteral node, Object data) {
        ((Stack) data).push(new BasicOperand(((Double) node.value).doubleValue()));
        return null;
    }

    /**
     * Node with value set to litteral boolean.
     * Push the corresponding Operand to the stack
     */
    public Object visit(ASTBooleanLiteral node, Object data) {
        ((Stack) data).push(new BasicOperand(((Boolean) node.value).booleanValue()));
        return null;
    }

    /**
     * Node with value set to parameter index (1..n) string.
     * Push the corresponding Operand to the stack
     */
    public Object visit(ASTInputParameter node, Object data) {
        try {
            int pIndex = ((Integer) node.value).intValue() - 1;
            if (pIndex >= parameterTypes.length) {
                throw new ParseException("Parameter ?" + (pIndex + 1)
                                         + " is out of range (max = "
                                         + parameterTypes.length + ")");
            }
            if (EJBObject.class.isAssignableFrom(parameterTypes[pIndex])) {
                // TODO: Take into account EJBObject as parameter
                //       (Only EJBLocalObject are already taken into account)
                throw new Error("EJBObject params not implemented");
            }
            Operand op = new BasicParameterOperand(JormType.getPType(parameterTypes[pIndex], false),
                                                   "?" + node.value);
            ((Stack) data).push(op);
            return null;
        } catch (ParseException e) {
            throw new VisitorException(e);
        }
    }

    /**
     * Push the Node to the stack
     */
    public Object visit(ASTPath node, Object data) {
        ((Stack) data).push(node);
        return null;
    }
}
