/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// ----------------------------------------------------------------------------
// OPTIONS
// ----------------------------------------------------------------------------
options {
  STATIC = false;
  UNICODE_INPUT = true;

  // some performance optimizations
  ERROR_REPORTING = false;
}

// ----------------------------------------------------------------------------
// PARSER
// ----------------------------------------------------------------------------

PARSER_BEGIN(SelectorParser)
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.activemq.selector;

import java.io.*;
import java.util.*;

import javax.jms.InvalidSelectorException;

import org.apache.activemq.filter.*;
import org.apache.activemq.filter.FunctionCallExpression.invalidFunctionExpressionException;
import org.apache.activemq.util.LRUCache;

/**
 * JMS Selector Parser generated by JavaCC
 *
 * Do not edit this .java file directly - it is autogenerated from SelectorParser.jj
 */
public class SelectorParser {

    private static final Map cache = Collections.synchronizedMap(new LRUCache(100));
    private static final String CONVERT_STRING_EXPRESSIONS_PREFIX = "convert_string_expressions:";

    public static BooleanExpression parse(String sql) throws InvalidSelectorException {
        Object result = cache.get(sql);
        if (result instanceof InvalidSelectorException) {
            throw (InvalidSelectorException) result;
        } else if (result instanceof BooleanExpression) {
            return (BooleanExpression) result;
        } else {

            boolean convertStringExpressions = false;
            if( sql.startsWith(CONVERT_STRING_EXPRESSIONS_PREFIX)) {
                convertStringExpressions = true;
                sql = sql.substring(CONVERT_STRING_EXPRESSIONS_PREFIX.length());
            }

            if( convertStringExpressions ) {
                ComparisonExpression.CONVERT_STRING_EXPRESSIONS.set(true);
            }
            try {
                BooleanExpression e = new SelectorParser(sql).parse();
                cache.put(sql, e);
                return e;
            } catch (InvalidSelectorException t) {
                cache.put(sql, t);
                throw t;
            } finally {
                if( convertStringExpressions ) {
                    ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove();
                }
            }
        }
    }

    public static void clearCache() {
        cache.clear();
    }

    private String sql;

    protected SelectorParser(String sql) {
        this(new StringReader(sql));
        this.sql = sql;
    }

    protected BooleanExpression parse() throws InvalidSelectorException {
        try {
            return this.JmsSelector();
        }
        catch (Throwable e) {
            throw (InvalidSelectorException) new InvalidSelectorException(sql).initCause(e);
        }
    }

    private BooleanExpression asBooleanExpression(Expression value) throws ParseException  {
        if (value instanceof BooleanExpression) {
            return (BooleanExpression) value;
        }
        if (value instanceof PropertyExpression) {
            return UnaryExpression.createBooleanCast( value );
        }
        throw new ParseException("Expression will not result in a boolean value: " + value);
    }

}

PARSER_END(SelectorParser)

// ----------------------------------------------------------------------------
// Tokens
// ----------------------------------------------------------------------------

/* White Space */
SPECIAL_TOKEN :
{
  " " | "\t" | "\n" | "\r" | "\f"
}

/* Comments */
SKIP:
{
  <LINE_COMMENT: "--" (~["\n","\r"])* ("\n"|"\r"|"\r\n") >
}

SKIP:
{
  <BLOCK_COMMENT: "/*" (~["*"])* "*" ("*" | (~["*","/"] (~["*"])* "*"))* "/">
}

/* Reserved Words */
TOKEN [IGNORE_CASE] :
{
    <  NOT     : "NOT">
  | <  AND     : "AND">
  | <  OR      : "OR">
  | <  BETWEEN : "BETWEEN">
  | <  LIKE    : "LIKE">
  | <  ESCAPE  : "ESCAPE">
  | <  IN      : "IN">
  | <  IS      : "IS">
  | <  TRUE    : "TRUE" >
  | <  FALSE   : "FALSE" >
  | <  NULL    : "NULL" >
  | <  XPATH   : "XPATH" >
  | <  XQUERY  : "XQUERY" >
}

/* Literals */
TOKEN [IGNORE_CASE] :
{

    < DECIMAL_LITERAL: ["1"-"9"] (["0"-"9"])* (["l","L"])? >
  | < HEX_LITERAL: "0" ["x","X"] (["0"-"9","a"-"f","A"-"F"])+ >
  | < OCTAL_LITERAL: "0" (["0"-"7"])* >
  | < FLOATING_POINT_LITERAL:
          (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)? // matches: 5.5 or 5. or 5.5E10 or 5.E10
        | "." (["0"-"9"])+ (<EXPONENT>)?              // matches: .5 or .5E10
        | (["0"-"9"])+ <EXPONENT>                     // matches: 5E10
    >
  | < #EXPONENT: "E" (["+","-"])? (["0"-"9"])+ >
  | < STRING_LITERAL: "'" ( ("''") | ~["'"] )*  "'" >
}

TOKEN [IGNORE_CASE] :
{
    < ID : ["a"-"z", "_", "$"] (["a"-"z","0"-"9","_", "$"])* >
}

// ----------------------------------------------------------------------------
// Grammer
// ----------------------------------------------------------------------------
BooleanExpression JmsSelector() :
{
    Expression left=null;
}
{
    (
        left = orExpression() <EOF>
    )
    {
        return asBooleanExpression(left);
    }

}

Expression orExpression() :
{
    Expression left;
    Expression right;
}
{
    (
        left = andExpression()
        (
            <OR> right = andExpression()
            {
                left = LogicExpression.createOR(asBooleanExpression(left), asBooleanExpression(right));
            }
        )*
    )
    {
        return left;
    }

}


Expression andExpression() :
{
    Expression left;
    Expression right;
}
{
    (
        left = equalityExpression()
        (
            <AND> right = equalityExpression()
            {
                left = LogicExpression.createAND(asBooleanExpression(left), asBooleanExpression(right));
            }
        )*
    )
    {
        return left;
    }
}

Expression equalityExpression() :
{
    Expression left;
    Expression right;
}
{
    (
        left = comparisonExpression()
        (

            "=" right = comparisonExpression()
            {
                left = ComparisonExpression.createEqual(left, right);
            }
            |
            "<>" right = comparisonExpression()
            {
                left = ComparisonExpression.createNotEqual(left, right);
            }
            |
            LOOKAHEAD(2)
            <IS> <NULL>
            {
                left = ComparisonExpression.createIsNull(left);
            }
            |
            <IS> <NOT> <NULL>
            {
                left = ComparisonExpression.createIsNotNull(left);
            }
        )*
    )
    {
        return left;
    }
}

Expression comparisonExpression() :
{
    Expression left;
    Expression right;
    Expression low;
    Expression high;
    String t, u;
    boolean not;
    ArrayList list;
}
{
    (
        left = addExpression()
        (

                ">" right = addExpression()
                {
                    left = ComparisonExpression.createGreaterThan(left, right);
                }
            |
                ">=" right = addExpression()
                {
                    left = ComparisonExpression.createGreaterThanEqual(left, right);
                }
            |
                "<" right = addExpression()
                {
                    left = ComparisonExpression.createLessThan(left, right);
                }
            |
                "<=" right = addExpression()
                {
                    left = ComparisonExpression.createLessThanEqual(left, right);
                }
           |
                {
                    u=null;
                }
                <LIKE> t = stringLitteral()
                    [ <ESCAPE> u = stringLitteral() ]
                {
                    left = ComparisonExpression.createLike(left, t, u);
                }
           |
                LOOKAHEAD(2)
                {
                    u=null;
                }
                <NOT> <LIKE> t = stringLitteral() [ <ESCAPE> u = stringLitteral() ]
                {
                    left = ComparisonExpression.createNotLike(left, t, u);
                }
            |
                <BETWEEN> low = addExpression() <AND> high = addExpression()
                {
                    left = ComparisonExpression.createBetween(left, low, high);
                }
            |
                LOOKAHEAD(2)
                <NOT> <BETWEEN> low = addExpression() <AND> high = addExpression()
                {
                    left = ComparisonExpression.createNotBetween(left, low, high);
                }
            |
                <IN>
                "("
                    t = stringLitteral()
                    {
                        list = new ArrayList();
                        list.add( t );
                    }
                    (
                        ","
                        t = stringLitteral()
                        {
                            list.add( t );
                        }

                    )*
                ")"
                {
                   left = ComparisonExpression.createInFilter(left, list);
                }
            |
                LOOKAHEAD(2)
                <NOT> <IN>
                "("
                    t = stringLitteral()
                    {
                        list = new ArrayList();
                        list.add( t );
                    }
                    (
                        ","
                        t = stringLitteral()
                        {
                            list.add( t );
                        }

                    )*
                ")"
                {
                   left = ComparisonExpression.createNotInFilter(left, list);
                }

        )*
    )
    {
        return left;
    }
}

Expression addExpression() :
{
    Expression left;
    Expression right;
}
{
    left = multExpr()
    (
        LOOKAHEAD( ("+"|"-") multExpr())
        (
            "+" right = multExpr()
            {
                left = ArithmeticExpression.createPlus(left, right);
            }
            |
            "-" right = multExpr()
            {
                left = ArithmeticExpression.createMinus(left, right);
            }
        )

    )*
    {
        return left;
    }
}

Expression multExpr() :
{
    Expression left;
    Expression right;
}
{
    left = unaryExpr()
    (
        "*" right = unaryExpr()
        {
            left = ArithmeticExpression.createMultiply(left, right);
        }
        |
        "/" right = unaryExpr()
        {
            left = ArithmeticExpression.createDivide(left, right);
        }
        |
        "%" right = unaryExpr()
        {
            left = ArithmeticExpression.createMod(left, right);
        }

    )*
    {
        return left;
    }
}


Expression unaryExpr() :
{
    String s=null;
    Expression left=null;
}
{
    (
        LOOKAHEAD( "+" unaryExpr() )
        "+" left=unaryExpr()
        |
        "-" left=unaryExpr()
        {
            left = UnaryExpression.createNegate(left);
        }
        |
        <NOT> left=unaryExpr()
        {
            left = UnaryExpression.createNOT( asBooleanExpression(left) );
        }
        |
        <XPATH> s=stringLitteral()
        {
            left = UnaryExpression.createXPath( s );
        }
        |
        <XQUERY> s=stringLitteral()
        {
            left = UnaryExpression.createXQuery( s );
        }
        |
            LOOKAHEAD( <ID> "(" )
            left = functionCallExpr()
        |
        left = primaryExpr()
    )
    {
        return left;
    }

}

Expression functionCallExpr () :
{
    Token      func_name;
    FunctionCallExpression func_call = null;
    Expression arg = null;
    ArrayList arg_list = new ArrayList();
}
{
    func_name = <ID> "("
    (
        arg = unaryExpr()
        {
            arg_list.add(arg);
        }
        (
            ","
            arg = unaryExpr()
            {
                arg_list.add(arg);
            }
        ) *
    )
    ")"
    {
        try
        {
            return FunctionCallExpression.createFunctionCall(func_name.image, arg_list);
        }
        catch ( invalidFunctionExpressionException inv_exc )
        {
            // Re-throw as an error to avoid the need to propogate the throws declaration.
            throw new Error("invalid function call expression", inv_exc);
        }
    }
}

Expression primaryExpr() :
{
    Expression left=null;
}
{
    (
        left = literal()
        |
        left = variable()
        |
        "(" left = orExpression() ")"
    )
    {
        return left;
    }
}



ConstantExpression literal() :
{
    Token t;
    String s;
    ConstantExpression left=null;
}
{
    (
        (
            s = stringLitteral()
            {
                left = new ConstantExpression(s);
            }
        )
        |
        (
            t = <DECIMAL_LITERAL>
            {
                left = ConstantExpression.createFromDecimal(t.image);
            }
        )
        |
        (
            t = <HEX_LITERAL>
            {
                left = ConstantExpression.createFromHex(t.image);
            }
        )
        |
        (
            t = <OCTAL_LITERAL>
            {
                left = ConstantExpression.createFromOctal(t.image);
            }
        )
        |
        (
            t = <FLOATING_POINT_LITERAL>
            {
                left = ConstantExpression.createFloat(t.image);
            }
        )
        |
        (
            <TRUE>
            {
                left = ConstantExpression.TRUE;
            }
        )
        |
        (
            <FALSE>
            {
                left = ConstantExpression.FALSE;
            }
        )
        |
        (
            <NULL>
            {
                left = ConstantExpression.NULL;
            }
        )
    )
    {
        return left;
    }
}

String stringLitteral() :
{
    Token t;
    StringBuffer rc = new StringBuffer();
    boolean first=true;
}
{
    t = <STRING_LITERAL>
    {
        // Decode the sting value.
        String image = t.image;
        for( int i=1; i < image.length()-1; i++ ) {
            char c = image.charAt(i);
            if( c == '\'' )
                i++;
               rc.append(c);
        }
        return rc.toString();
    }
}

PropertyExpression variable() :
{
    Token t;
    PropertyExpression left=null;
}
{
    (
        t = <ID>
        {
            left = new PropertyExpression(t.image);
        }
    )
    {
        return left;
    }
}
