options {
  LOOKAHEAD = 1;
  CHOICE_AMBIGUITY_CHECK = 5;
  OTHER_AMBIGUITY_CHECK = 2;
  STATIC = false;
  //DEBUG_PARSER = true;
  //DEBUG_LOOKAHEAD = false;
  //DEBUG_TOKEN_MANAGER = true;
  ERROR_REPORTING = true;
  JAVA_UNICODE_ESCAPE = false;
  UNICODE_INPUT = true;
  IGNORE_CASE = false;
  USER_TOKEN_MANAGER = false;
  USER_CHAR_STREAM = false;
  BUILD_PARSER = true;
  BUILD_TOKEN_MANAGER = true;
  SANITY_CHECK = true;
  FORCE_LA_CHECK = true;

  MULTI = true;
  NODE_PACKAGE = "org.exoplatform.services.jcr.impl.core.query.sql";
  VISITOR = true;
}

PARSER_BEGIN(JCRSQLParser)
/*
 * 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.exoplatform.services.jcr.impl.core.query.sql;

import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.LocationFactory;
import org.exoplatform.services.jcr.datamodel.InternalQName;
//import org.apache.jackrabbit.name.UnknownPrefixException;
import org.exoplatform.services.jcr.impl.core.query.QueryConstants;
import javax.jcr.RepositoryException;

public class JCRSQLParser {

  private String statement;

  //private NamespaceAccessor resolver;

  private LocationFactory locationFactory;

  public static void main(String args[]) throws ParseException {
  	JCRSQLParser parser = new JCRSQLParser(System.in);
  	parser.Query().dump("");
  }

  public static ASTQuery parse(String statement, LocationFactory locationFactory) throws ParseException {
  	java.io.StringReader sReader = new java.io.StringReader(statement);
  	JCRSQLParser parser = new JCRSQLParser(sReader);
  	parser.setLocationfactory(locationFactory);
  	return parser.Query();
  }

  void setLocationfactory(LocationFactory locationFactory) {
        this.locationFactory = locationFactory;
  }
//  void setNamespaceResolver(NamespaceResolver resolver) {
//        this.resolver = resolver;
//  } 

  void handleTokenMgrError(TokenMgrError e) throws ParseException{
    if (e.errorCode == e.LEXICAL_ERROR)
    {
      StringBuffer sb = new StringBuffer();
      try
      {
        char c;
        while ((c = token_source.input_stream.readChar()) != ' ')
        {
          sb.append(c);
        }
      }
      catch (java.io.IOException ioe)
      {
        // probably EOF occurs, but any way, do not report this exceptions 
      }

      throw new ParseException("Unknown Query Construction : \"" + sb.toString() + "\". " + e.getMessage());
    }
    else
    {
      throw e;
    }
  }
}

PARSER_END(JCRSQLParser)

SKIP :
{
  " "
| "\r"
| "\n"
| "\t"
| < COMMENT: <COMMENT_INTRO> (<COMMENT_CHAR>)* "\n" >
| < #COMMENT_CHAR: <NON_QUOTE_CHAR> | <QUOTE> >
| < #NON_QUOTE_CHAR: ~["'"] > // syntax rules
| < #COMMENT_INTRO: <MINUS_SIGN> <MINUS_SIGN> (<MINUS_SIGN>)* >
}

// KEY WORDS
TOKEN [ IGNORE_CASE ] :
{
  < BY: "BY" >
| < IN: "IN" >
| < OR: "OR" >
| < IS: "IS" >
| < AND: "AND" >
| < ASC: "ASC" >
| < NOT: "NOT" >
| < DESC: "DESC" >
| < LIKE: "LIKE" >
| < NULL: "NULL" >
| < FROM: "FROM" >
| < LOWER: "LOWER" >
| < ORDER: "ORDER" >
| < UPPER: "UPPER" >
| < WHERE: "WHERE" >
| < ESCAPE: "ESCAPE" >
| < SELECT: "SELECT" >
| < BETWEEN: "BETWEEN" >
| < EXCERPT: "EXCERPT" >
| < SIMILAR: "SIMILAR" >
| < CONTAINS: "CONTAINS" >
| < SPELLCHECK: "SPELLCHECK" >
}



TOKEN:
{
  < #SIMPLE_LATIN_LETTER: <SIMPLE_LATIN_UPPER_CASE_LETTER> | <SIMPLE_LATIN_LOWER_CASE_LETTER> >
| < #SIMPLE_LATIN_UPPER_CASE_LETTER: (["A"-"Z"]) >
| < #SIMPLE_LATIN_LOWER_CASE_LETTER: (["a"-"z"]) >

| < #LETTER: <BASE_CHAR> | <IDEOGRAPHIC> >

| < #BASE_CHAR:
          ["\u0041"-"\u005A"] | ["\u0061"-"\u007A"] | ["\u00C0"-"\u00D6"] | ["\u00D8"-"\u00F6"]
        | ["\u00F8"-"\u00FF"] | ["\u0100"-"\u0131"] | ["\u0134"-"\u013E"] | ["\u0141"-"\u0148"]
        | ["\u014A"-"\u017E"] | ["\u0180"-"\u01C3"] | ["\u01CD"-"\u01F0"] | ["\u01F4"-"\u01F5"]
        | ["\u01FA"-"\u0217"] | ["\u0250"-"\u02A8"] | ["\u02BB"-"\u02C1"] | "\u0386" | ["\u0388"-"\u038A"]
        | "\u038C" | ["\u038E"-"\u03A1"] | ["\u03A3"-"\u03CE"] | ["\u03D0"-"\u03D6"] | "\u03DA"
        | "\u03DC" | "\u03DE" | "\u03E0" | ["\u03E2"-"\u03F3"] | ["\u0401"-"\u040C"] | ["\u040E"-"\u044F"]
        | ["\u0451"-"\u045C"] | ["\u045E"-"\u0481"] | ["\u0490"-"\u04C4"] | ["\u04C7"-"\u04C8"]
        | ["\u04CB"-"\u04CC"] | ["\u04D0"-"\u04EB"] | ["\u04EE"-"\u04F5"] | ["\u04F8"-"\u04F9"]
        | ["\u0531"-"\u0556"] | "\u0559" | ["\u0561"-"\u0586"] | ["\u05D0"-"\u05EA"] | ["\u05F0"-"\u05F2"]
        | ["\u0621"-"\u063A"] | ["\u0641"-"\u064A"] | ["\u0671"-"\u06B7"] | ["\u06BA"-"\u06BE"]
        | ["\u06C0"-"\u06CE"] | ["\u06D0"-"\u06D3"] | "\u06D5" | ["\u06E5"-"\u06E6"] | ["\u0905"-"\u0939"]
        | "\u093D" | ["\u0958"-"\u0961"] | ["\u0985"-"\u098C"] | ["\u098F"-"\u0990"] | ["\u0993"-"\u09A8"]
        | ["\u09AA"-"\u09B0"] | "\u09B2" | ["\u09B6"-"\u09B9"] | ["\u09DC"-"\u09DD"] | ["\u09DF"-"\u09E1"]
        | ["\u09F0"-"\u09F1"] | ["\u0A05"-"\u0A0A"] | ["\u0A0F"-"\u0A10"] | ["\u0A13"-"\u0A28"]
        | ["\u0A2A"-"\u0A30"] | ["\u0A32"-"\u0A33"] | ["\u0A35"-"\u0A36"] | ["\u0A38"-"\u0A39"]
        | ["\u0A59"-"\u0A5C"] | "\u0A5E" | ["\u0A72"-"\u0A74"] | ["\u0A85"-"\u0A8B"] | "\u0A8D"
        | ["\u0A8F"-"\u0A91"] | ["\u0A93"-"\u0AA8"] | ["\u0AAA"-"\u0AB0"] | ["\u0AB2"-"\u0AB3"]
        | ["\u0AB5"-"\u0AB9"] | "\u0ABD" | "\u0AE0" | ["\u0B05"-"\u0B0C"] | ["\u0B0F"-"\u0B10"]
        | ["\u0B13"-"\u0B28"] | ["\u0B2A"-"\u0B30"] | ["\u0B32"-"\u0B33"] | ["\u0B36"-"\u0B39"]
        | "\u0B3D" | ["\u0B5C"-"\u0B5D"] | ["\u0B5F"-"\u0B61"] | ["\u0B85"-"\u0B8A"]
        | ["\u0B8E"-"\u0B90"] | ["\u0B92"-"\u0B95"] | ["\u0B99"-"\u0B9A"] | "\u0B9C" | ["\u0B9E"-"\u0B9F"]
        | ["\u0BA3"-"\u0BA4"] | ["\u0BA8"-"\u0BAA"] | ["\u0BAE"-"\u0BB5"] | ["\u0BB7"-"\u0BB9"]
        | ["\u0C05"-"\u0C0C"] | ["\u0C0E"-"\u0C10"] | ["\u0C12"-"\u0C28"] | ["\u0C2A"-"\u0C33"]
        | ["\u0C35"-"\u0C39"] | ["\u0C60"-"\u0C61"] | ["\u0C85"-"\u0C8C"] | ["\u0C8E"-"\u0C90"]
        | ["\u0C92"-"\u0CA8"] | ["\u0CAA"-"\u0CB3"] | ["\u0CB5"-"\u0CB9"] | "\u0CDE" | ["\u0CE0"-"\u0CE1"]
        | ["\u0D05"-"\u0D0C"] | ["\u0D0E"-"\u0D10"] | ["\u0D12"-"\u0D28"] | ["\u0D2A"-"\u0D39"]
        | ["\u0D60"-"\u0D61"] | ["\u0E01"-"\u0E2E"] | "\u0E30" | ["\u0E32"-"\u0E33"] | ["\u0E40"-"\u0E45"]
        | ["\u0E81"-"\u0E82"] | "\u0E84" | ["\u0E87"-"\u0E88"] | "\u0E8A" | "\u0E8D" | ["\u0E94"-"\u0E97"]
        | ["\u0E99"-"\u0E9F"] | ["\u0EA1"-"\u0EA3"] | "\u0EA5" | "\u0EA7" | ["\u0EAA"-"\u0EAB"]
        | ["\u0EAD"-"\u0EAE"] | "\u0EB0" | ["\u0EB2"-"\u0EB3"] | "\u0EBD" | ["\u0EC0"-"\u0EC4"]
        | ["\u0F40"-"\u0F47"] | ["\u0F49"-"\u0F69"] | ["\u10A0"-"\u10C5"] | ["\u10D0"-"\u10F6"] | "\u1100"
        | ["\u1102"-"\u1103"] | ["\u1105"-"\u1107"] | "\u1109" | ["\u110B"-"\u110C"] | ["\u110E"-"\u1112"]
        | "\u113C" | "\u113E" | "\u1140" | "\u114C" | "\u114E" | "\u1150" | ["\u1154"-"\u1155"] | "\u1159"
        | ["\u115F"-"\u1161"] | "\u1163" | "\u1165" | "\u1167" | "\u1169" | ["\u116D"-"\u116E"]
        | ["\u1172"-"\u1173"] | "\u1175" | "\u119E" | "\u11A8" | "\u11AB" | ["\u11AE"-"\u11AF"]
        | ["\u11B7"-"\u11B8"] | "\u11BA" | ["\u11BC"-"\u11C2"] | "\u11EB" | "\u11F0" | "\u11F9"
        | ["\u1E00"-"\u1E9B"] | ["\u1EA0"-"\u1EF9"] | ["\u1F00"-"\u1F15"] | ["\u1F18"-"\u1F1D"]
        | ["\u1F20"-"\u1F45"] | ["\u1F48"-"\u1F4D"] | ["\u1F50"-"\u1F57"] | "\u1F59" | "\u1F5B" | "\u1F5D"
        | ["\u1F5F"-"\u1F7D"] | ["\u1F80"-"\u1FB4"] | ["\u1FB6"-"\u1FBC"] | "\u1FBE" | ["\u1FC2"-"\u1FC4"]
        | ["\u1FC6"-"\u1FCC"] | ["\u1FD0"-"\u1FD3"] | ["\u1FD6"-"\u1FDB"] | ["\u1FE0"-"\u1FEC"]
        | ["\u1FF2"-"\u1FF4"] | ["\u1FF6"-"\u1FFC"] | "\u2126" | ["\u212A"-"\u212B"] | "\u212E"
        | ["\u2180"-"\u2182"] | ["\u3041"-"\u3094"] | ["\u30A1"-"\u30FA"] | ["\u3105"-"\u312C"]
        | ["\uAC00"-"\uD7A3"] >
| < #IDEOGRAPHIC :
          ["\u4E00"-"\u9FA5"] | "\u3007" | ["\u3021"-"\u3029"] >

| < #DIGIT: (["0"-"9"]) >


| < DOUBLE_QUOTE: "\"" >
| < PERCENT: "%" >
| < AMPERSAND: "&" >
| < QUOTE: "'" >
| < LEFT_PAREN: "(" >
| < RIGHT_PAREN: ")" >
| < ASTERISK: "*" >
| < PLUS_SIGN: "+" >
| < COMMA: "," >
| < MINUS_SIGN: "-" >
| < PERIOD: "." >
| < SOLIDUS: "/" >
| < COLON: ":" >
| < SEMICOLON: ";" >
| < LT_OPERATOR: "<" >
| < EQ_OPERATOR: "=" >
| < GT_OPERATOR: ">" >
| < QUESTION_MARK: "?" >
| < UNDERSCORE: "_" >
| < VERTICAL_BAR: "|" >

| < LEFT_BRACKET: "[" >  // also special char?
| < RIGHT_BRACKET: "]" > // also special char?


| < REGULAR_IDENTIFIER: <IDENTIFIER_BODY> >
| < #IDENTIFIER_BODY: <IDENTIFIER_START> (<UNDERSCORE> | <COLON> | <IDENTIFIER_PART>)* >
| < #IDENTIFIER_START: <LETTER> > // syntax rules 5.2
| < #IDENTIFIER_PART: <IDENTIFIER_START> | <DIGIT> | <MINUS_SIGN> >

| < DELIMITED_IDENTIFIER: <DOUBLE_QUOTE> <DELIMITED_IDENTIFIER_BODY> <DOUBLE_QUOTE> >
| < #DELIMITED_IDENTIFIER_BODY: (<DELIMITED_IDENTIFIER_PART>)+ >
| < #DELIMITED_IDENTIFIER_PART: <NON_DOUBLE_QUOTE_CHAR> | <DOUBLE_QUOTE_SYMBOL> >
| < #NON_DOUBLE_QUOTE_CHAR: ~["\""] > // syntax rules 5.2
| < #DOUBLE_QUOTE_SYMBOL: <DOUBLE_QUOTE><DOUBLE_QUOTE> >

| < NE_OPERATOR: "<>" >
| < GE_OPERATOR: ">=" >
| < LE_OPERATOR: "<=" >
| < CONCAT_OPERATOR: "||" >
| < DOUBLE_PERIOD: ".." >

/*
| < COMMENT: <COMMENT_INTRO> (<COMMENT_CHAR>)* "\n" >
| < #COMMENT_CHAR: <NON_QUOTE_CHAR> | <QUOTE> >
| < #NON_QUOTE_CHAR: ~["'"] > // syntax rules
| < #COMMENT_INTRO: <MINUS_SIGN> <MINUS_SIGN> (<MINUS_SIGN>)* >
*/

/* Literals */

| < EXACT_NUMERIC_LITERAL: (<SIGN>)? (<UNSIGNED_INTEGER> (<PERIOD> (<UNSIGNED_INTEGER>)?)?) | (<PERIOD> <UNSIGNED_INTEGER>) >
| < #SIGN: <PLUS_SIGN> | <MINUS_SIGN> >
| < APPROXIMATE_NUMERIC_LITERAL: <MANTISSA> ("E" | "e") <EXPONENT> >
| < #MANTISSA: <EXACT_NUMERIC_LITERAL> >
| < #EXPONENT: <SIGNED_INTEGER> >
| < #SIGNED_INTEGER: (<SIGN>)? <UNSIGNED_INTEGER> >
| < #UNSIGNED_INTEGER: (<DIGIT>)+ >
| < DATETIME_LITERAL: <DATE_LITERAL> | /* <TIME_LITERAL> | */ <TIMESTAMP_LITERAL> >
| < DATE_LITERAL: "DATE" (" ")+ <DATE_STRING> >
//| < TIME_LITERAL: "TIME" (" ")+ <TIME_STRING> >  // not supported in jcr
| < TIMESTAMP_LITERAL: "TIMESTAMP" (" ")+ <TIMESTAMP_STRING> >
| < #DATE_STRING: <QUOTE> <DATE_VALUE> <QUOTE> >
// | < #TIME_STRING: <QUOTE> <TIME_VALUE> (<TIME_ZONE_INTERVAL>)? <QUOTE> > // not supported in jcr
| < #TIMESTAMP_STRING: <QUOTE> <DATE_VALUE> (" "|"T") <TIME_VALUE> (<TIME_ZONE_INTERVAL>)? <QUOTE> >
| < #TIME_ZONE_INTERVAL: "Z" | (<SIGN> <HOURS_VALUE> <COLON> <MINUTES_VALUE>) >
| < #DATE_VALUE: <YEARS_VALUE> <MINUS_SIGN> <MONTHS_VALUE> <MINUS_SIGN> <DAYS_VALUE> >
| < #TIME_VALUE: <HOURS_VALUE> <COLON> <MINUTES_VALUE> <COLON> <SECONDS_VALUE> >
| < #YEARS_VALUE: <DATETIME_VALUE> >
| < #MONTHS_VALUE: <DATETIME_VALUE> >
| < #DAYS_VALUE: <DATETIME_VALUE> >
| < #HOURS_VALUE: <DATETIME_VALUE> >
| < #MINUTES_VALUE: <DATETIME_VALUE> >
| < #SECONDS_VALUE: <SECONDS_INTEGER_VALUE> (<PERIOD> (<SECONDS_FRACTION>)?)? >
| < #SECONDS_INTEGER_VALUE: <UNSIGNED_INTEGER> >
| < #SECONDS_FRACTION: <UNSIGNED_INTEGER> >
| < #DATETIME_VALUE: <UNSIGNED_INTEGER> >

| < CHAR_STRING_LITERAL: <QUOTE> (<CHAR_REPRESENTATION>)* <QUOTE> >
| < #CHAR_REPRESENTATION: <NON_QUOTE_CHAR> | (<QUOTE_SYMBOL>) >
| < #QUOTE_SYMBOL: <QUOTE><QUOTE> >
}




// QUERY EXPRESSIONS, 7

ASTQuery Query() :
{}
{
  try{

  <SELECT>
  SelectList()
  TableExpression()
  [ OrderByClause() ]
  <EOF>
  
  }catch(TokenMgrError e){
    handleTokenMgrError(e);
  }
  
  { return jjtThis; }
}

void SelectList() :
{}
{
    (<ASTERISK>)
  | ( SelectItem() ( <COMMA> SelectItem() )* )
}

void SelectItem() #void :
{}
{
  (ExcerptFunction() | Identifier() (<PERIOD> Identifier() { Node n = jjtree.popNode(); jjtree.popNode(); jjtree.pushNode(n); } )?)
}

void TableExpression() #void :
{}
{
  FromClause()
  [ WhereClause() ]
}

void FromClause() :
{}
{
  <FROM> Identifier() (<COMMA> Identifier())*
}

void WhereClause() :
{}
{
  <WHERE> SearchCondition()
}

// PREDICATES 8.1
void Predicate() :
{
  int operationType;
  InternalQName identifier;
  String value;
  String escapeString;
}
{
  (
    (
      (
        (
          identifier = Identifier() { jjtThis.setIdentifier(identifier); } ( <PERIOD> identifier = Identifier() { Node n = jjtree.popNode(); jjtree.popNode(); jjtree.pushNode(n); jjtThis.setIdentifier(identifier); } )?
        )
      |
        (
          identifier = PropertyFunction() { jjtThis.setIdentifier(identifier); }
        )
      )
      (
        (
          operationType = ComparisonOperation() { jjtThis.setOperationType(operationType); }
          (Literal() | (identifier = Identifier() { jjtThis.setIdentifier(identifier); } ( <PERIOD> identifier = Identifier() { Node n = jjtree.popNode(); jjtree.popNode(); jjtree.pushNode(n); jjtThis.setIdentifier(identifier); } )? ) )
        )
      |
        ((<NOT> { jjtThis.setNegate(true); })?
          (
            (<BETWEEN> { jjtThis.setOperationType(QueryConstants.OPERATION_BETWEEN); } Literal() <AND> Literal())
          |
            (
              <LIKE> { jjtThis.setOperationType(QueryConstants.OPERATION_LIKE); } value = CharStringLiteral()
              {
                ASTLiteral s = new ASTLiteral(JJTLITERAL);
                s.setType(QueryConstants.TYPE_STRING);
                s.setValue(value);
                jjtree.pushNode(s);
              }
              (<ESCAPE> escapeString = CharStringLiteral() { jjtThis.setEscapeString(escapeString); })?
            )
          )
        )
      |
        (<IS> (<NOT> { jjtThis.setNegate(true); })? <NULL>
          {
            jjtThis.setOperationType(jjtThis.isNegate() ? QueryConstants.OPERATION_NOT_NULL : QueryConstants.OPERATION_NULL);
          }
        )
      )
    )
  |
    (
      Literal() (<NOT> { jjtThis.setNegate(true); })? <IN>
      (
        (
            identifier = Identifier()
          | identifier = PropertyFunction()
        )
        {
          jjtThis.setIdentifier(identifier);
          jjtThis.setOperationType(jjtThis.isNegate() ? QueryConstants.OPERATION_NE_GENERAL : QueryConstants.OPERATION_EQ_GENERAL);
        }
      )
    )
  |
    (
      <SIMILAR> "(" { jjtThis.setOperationType(QueryConstants.OPERATION_SIMILAR); }
        ("." | identifier = Identifier() { jjtThis.setIdentifier(identifier); })
        ","
        value = CharStringLiteral()
          {
            ASTLiteral s = new ASTLiteral(JJTLITERAL);
            s.setType(QueryConstants.TYPE_STRING);
            s.setValue(value);
            jjtree.pushNode(s);
          } ")"
    )
  |
    (
      <SPELLCHECK> "(" { jjtThis.setOperationType(QueryConstants.OPERATION_SPELLCHECK); }
        value = CharStringLiteral()
          {
            ASTLiteral stmt = new ASTLiteral(JJTLITERAL);
            stmt.setType(QueryConstants.TYPE_STRING);
            stmt.setValue(value);
            jjtree.pushNode(stmt);
          } ")"
    )
  )
}

InternalQName PropertyFunction() #void :
{
  InternalQName identifier;
}
{
  (
      identifier = LowerFunction()
    | identifier = UpperFunction()
  )
  {
    return identifier;
  }
}

InternalQName LowerFunction() :
{
  InternalQName identifier;
}
{
  (
    <LOWER> "(" identifier = Identifier() ")"
  )
  {
    return identifier;
  }
}

InternalQName UpperFunction() :
{
  InternalQName identifier;
}
{
  (
    <UPPER> "(" identifier = Identifier() ")"
  )
  {
    return identifier;
  }
}

int ComparisonOperation() #void :
{
  int operationType;
}
{
  (
      <EQ_OPERATOR> { operationType = QueryConstants.OPERATION_EQ_GENERAL; }
    | <NE_OPERATOR> { operationType = QueryConstants.OPERATION_NE_GENERAL; }
    | <LT_OPERATOR> { operationType = QueryConstants.OPERATION_LT_GENERAL; }
    | <GT_OPERATOR> { operationType = QueryConstants.OPERATION_GT_GENERAL; }
    | <LE_OPERATOR> { operationType = QueryConstants.OPERATION_LE_GENERAL; }
    | <GE_OPERATOR> { operationType = QueryConstants.OPERATION_GE_GENERAL; }
  )
  {
    return operationType;
  }
}


// SEARCH CONDITION 8.12
void SearchCondition() #void :
{}
{
  OrExpression()
}

void OrExpression() #void :
{}
{
  (
    AndExpression() (<OR> AndExpression())*
  ) #OrExpression(>1)
}

void AndExpression() #void :
{}
{
  (
    UnaryExpression() (<AND> UnaryExpression())*
  ) #AndExpression(>1)
}

void UnaryExpression() #void :
{}
{
  (
    <NOT> UnaryExpression()
  ) #NotExpression()
|
  (
    PrimaryExpression()
  )
}

void PrimaryExpression() #void :
{}
{
  (
    Predicate()
  )
|
  (
    BracketExpression()
  )
|
  (
    ContainsExpression()
  )
}

void BracketExpression() :
{}
{
  <LEFT_PAREN> SearchCondition() <RIGHT_PAREN>
}

void ContainsExpression() :
{
  Token t = null;
  InternalQName name = null;
}
{
  <CONTAINS>
  <LEFT_PAREN>
  ( <ASTERISK> | <PERIOD> | (name = Identifier() { jjtThis.setPropertyName(name); }) ) ","
  t = <CHAR_STRING_LITERAL> { jjtThis.setQuery(t.image.substring(1, t.image.length() - 1).replaceAll("''", "'")); }
  <RIGHT_PAREN>
}

void Literal() :
{
  Token t = null;
  String value;
}
{
  (
    t = <APPROXIMATE_NUMERIC_LITERAL> { jjtThis.setType(QueryConstants.TYPE_DOUBLE); jjtThis.setValue(t.image); }
  |
    // can contain a dot -> use double
    t = <EXACT_NUMERIC_LITERAL>
    {
      if (t.image.indexOf('.') > -1) {
        jjtThis.setType(QueryConstants.TYPE_DOUBLE);
      } else {
        jjtThis.setType(QueryConstants.TYPE_LONG);
      }
      jjtThis.setValue(t.image);
    }
  |
    value = CharStringLiteral() { jjtThis.setType(QueryConstants.TYPE_STRING); jjtThis.setValue(value); }
  |
    t = <DATETIME_LITERAL>
    {
      if (t.image.startsWith("TIMESTAMP")) {
        jjtThis.setValue(t.image.substring(t.image.indexOf('\'') + 1, t.image.length() - 1));
        if (jjtThis.getValue().indexOf(" ") == 10) {
          // replace SQL 92 timesamp string with ISO8601
          StringBuffer tmp = new StringBuffer();
          tmp.append(jjtThis.getValue().substring(0, 10));
          tmp.append("T").append(jjtThis.getValue().substring(11));
          jjtThis.setValue(tmp.toString());
        }
        jjtThis.setType(QueryConstants.TYPE_TIMESTAMP);
      /*
      } else if (t.image.startsWith("TIME")) {
        jjtThis.setValue(t.image.substring(t.image.indexOf('\'') + 1, t.image.length() - 1));
        jjtThis.setType(QueryConstants.TYPE_TIME);
      */
      } else {
        jjtThis.setValue(t.image.substring(t.image.indexOf('\'') + 1, t.image.length() - 1));
        jjtThis.setType(QueryConstants.TYPE_DATE);
      }
    }
  )
}

String CharStringLiteral() #void :
{
  Token t;
  String value = "";
}
{
  (
    t = <CHAR_STRING_LITERAL> { value += t.image.substring(1, t.image.length() - 1); } ( t = <CHAR_STRING_LITERAL> { value += t.image.substring(1, t.image.length() - 1); } )*
  )
  {
    // strip any quote escapes
    return value.replaceAll("''", "'");
  }
}

InternalQName Identifier() :
{
  Token t = null;
  InternalQName name = null;
  boolean pseudoProperty = false;
}
{
  (
    t = <REGULAR_IDENTIFIER> ( <LEFT_PAREN> <RIGHT_PAREN> { pseudoProperty = true; } ) ?
      {
        try {
          String jcrName = t.image;
          if (pseudoProperty && !jcrName.equals("jcr:score")) {
            jcrName += "()";
          }
          InternalQName internalQName = locationFactory.parseJCRName(jcrName).getInternalName();
          jjtThis.setName(internalQName);
        } catch (RepositoryException e) {
          throw new ParseException(e.getMessage());
        } 
      }
  |
    t = <DELIMITED_IDENTIFIER>
      {
        try {
          InternalQName internalQName = locationFactory.parseJCRName(t.image.substring(1, t.image.length()-1).replaceAll("\"\"", "\"")).getInternalName();
          jjtThis.setName(internalQName);
        } catch (RepositoryException e) {
          throw new ParseException(e.getMessage());
        } 
      }
  |
    (
      (
        // or any keyword
          t = <BY>
        | t = <IN>
        | t = <OR>
        | t = <IS>
        | t = <AND>
        /*| <NOT>*/
        | t = <LIKE>
        | t = <NULL>
        | t = <FROM>
        | t = <ORDER>
        | t = <WHERE>
        | t = <SELECT>
        | t = <BETWEEN>
      )
      {
        try {
		  InternalQName internalQName = locationFactory.parseJCRName(t.image).getInternalName();
          jjtThis.setName(internalQName);
        } catch (RepositoryException e) {
          throw new ParseException(e.getMessage());
        } 
      }
    )
  )
  {
    return jjtThis.getName();
  }
}

InternalQName ExcerptFunction() :
{}
{
  <EXCERPT> "(" <PERIOD> ")"
  {
    return new InternalQName(Constants.NS_EXO_URI, "excerpt(.)");
  }
}

void OrderByClause() :
{}
{
  <ORDER> <BY> OrderSpec() (<COMMA> OrderSpec())*
}

void OrderSpec() :
{}
{
  Identifier() (AscendingOrderSpec() | DescendingOrderSpec())?
}

void AscendingOrderSpec() :
{}
{
  <ASC>
}

void DescendingOrderSpec() :
{}
{
  <DESC>
}
