/FileExtraction/Java_unseen/dlitz_resin/modules/kernel/src/com/caucho/el/ELParser.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.el;

import com.caucho.util.CharBuffer;
import com.caucho.util.L10N;

import javax.el.ELContext;
import javax.el.FunctionMapper;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.logging.Logger;
import java.util.logging.Level;

/**
 * Parses the expression.
 */
public class ELParser
{
  private static final Logger log = Logger.getLogger(ELParser.class.getName());
  private static final L10N L = new L10N(ELParser.class);

  // The expression string
  private String _string;
  // Current parse index into the string
  private int _index;
  // The peek token
  private int _peek = -1;
  // The current lexeme
  private String _lexeme;
  // Temporary buffer
  private CharBuffer _cb = new CharBuffer();

  protected final ELContext _elContext;
  protected final boolean _isMethodExpr;

  private boolean _checkEscape = true;

  public ELParser(ELContext elContext, String string)
  {
    this(elContext, string, false);
  }

  public ELParser(ELContext elContext, String string, boolean isMethodExpr)
  {
    if (elContext == null)
      throw new NullPointerException();
    
    _elContext = elContext;
    _string = string;
    _isMethodExpr = isMethodExpr;
  }

  protected ELParser create(String string)
  {
    ELParser parser = new ELParser(_elContext, string, _isMethodExpr);

    copyTo(parser);

    return parser;
  }

  /**
   * Copy to the dest parser.
   */
  protected void copyTo(ELParser parser)
  {
  }

  /**
   * Set true if escapes are checked.
   */
  public void setCheckEscape(boolean checkEscape)
  {
    _checkEscape = checkEscape;
  }

  /**
   * Parses the expression string.
   */
  public Expr parse()
    throws ELParseException
  {
    return parseInterpolate();
  }

  /**
   * Parses interpolated code.
   */
  public Expr parseInterpolate()
    throws ELParseException
  {
    CharBuffer text = new CharBuffer();
    CharBuffer exprString = new CharBuffer();
    Expr expr = null;
    int ch;
    int exprToken = -1;

    while ((ch = read()) >= 0) {
      if (_checkEscape && ch == '\\') {
        ch = read();

        if (ch == '$' || ch == '#' || ch == '\\')
          text.append((char) ch);
        else {
          text.append('\\');
          unread();
        }
      }
      else if (ch == '$' || ch == '#') {
        int origChar = ch;

        ch = read();

        if (ch == '{') {
          if (expr != null && _isMethodExpr)
            throw new ELParseException(L.l("Invalid method expression '{0}'",
                                           _string));

          if (exprToken != -1 && exprToken != origChar)
            throw error(L.l("Mixed '#' and '$'. Expected '{0}' at '{1}'",
                            Character.toString((char) exprToken),
                            Character.toString((char) origChar)));

          exprToken = origChar;

          if (text.length() > 0) {
            StringLiteral right = new StringLiteral(text.toString());

            if (expr == null)
              expr = right;
            else
              expr = new InterpolateExpr(expr, right);

            text.clear();
          }

          exprString.clear();

          for (ch = read(); ch > 0 && ch != '}'; ch = read()) {
            exprString.append((char) ch);

            if (ch == '\'' || ch == '"') {
              int end = ch;

              for (ch = read(); ch > 0 && ch != end; ch = read()) {
                exprString.append((char) ch);

                if (ch == '\\') {
                  ch = read();
                  if (ch > 0)
                    exprString.append((char) ch);
                }
              }

              if (ch > 0)
                exprString.append((char) ch);
            }
          }

          if (ch != '}')
            throw error(L.l("expected '}' at end of EL expression",
                            exprString));

          Expr right = create(exprString.toString()).parseExpr();

          if (expr == null)
            expr = right;
          else
            expr = new InterpolateExpr(expr, right);
        }
        else {
          text.append((char) origChar);
          unread();
        }
      }
      else
        text.append((char) ch);
    }

    if (text.length() > 0) {
      StringLiteral right = new StringLiteral(text.toString());

      if (expr == null)
        expr = right;
      else
        expr = new InterpolateExpr(expr, right);
    }

    if (expr == null)
      expr = new StringLiteral("");

    return expr;
  }

  /**
   * expr ::= term
   */
  private Expr parseExpr()
    throws ELParseException
  {
    Expr left = parseTerm();

    while (true) {
      int token = scanToken();

      if (_isMethodExpr) {
        switch (token) {
        case '?':
        case Expr.OR: case Expr.AND:
        case Expr.EQ: case Expr.NE: case Expr.LT:
        case Expr.LE: case Expr.GT: case Expr.GE:
        case Expr.ADD: case Expr.SUB:
        case Expr.MUL: case Expr.DIV: case Expr.MOD:
        case Expr.MATCHES:
          throw new ELParseException(L.l("Invalid method expression `{0}'",
                                         _string));
        }
      }

      switch (token) {
      case '?':
      {
        Expr trueExpr = parseExpr();
        token = scanToken();
        if (token != ':')
          throw error(L.l("Expected ':' at {0}.  Conditional syntax is 'expr ? expr : expr'.", badChar(token)));
        Expr falseExpr = parseExpr();

        left = new ConditionalExpr(left, trueExpr, falseExpr);
      }
      break;

      case Expr.OR:
        left = parseOrExpr(token, left, parseTerm());
        break;

      case Expr.AND:
        left = parseAndExpr(token, left, parseTerm());
        break;

      case Expr.EQ: case Expr.NE: case Expr.LT:
      case Expr.LE: case Expr.GT: case Expr.GE:
      case Expr.MATCHES:
        left = parseCmpExpr(token, left, parseTerm());
        break;

      case Expr.ADD: case Expr.SUB:
        left = parseAddExpr(token, left, parseTerm());
        break;

      case Expr.MUL: case Expr.DIV: case Expr.MOD:
        left = parseMulExpr(token, left, parseTerm());
        break;

      default:
        _peek = token;
        return left;
      }
    }
  }

  /**
   * or-expr ::= or-expr 'or' expr
   *         ::= and-expr
   */
  private Expr parseOrExpr(int code, Expr left, Expr right)
    throws ELParseException
  {
    while (true) {
      int token = scanToken();
      switch (token) {
      case Expr.OR:
        left = new BooleanExpr(code, left, right);
        code = token;
        right = parseTerm();
        break;

      case Expr.AND:
        right = parseAndExpr(token, right, parseTerm());
        break;

      case Expr.EQ: case Expr.NE:
      case Expr.LT: case Expr.GT:
      case Expr.LE: case Expr.GE:
      case Expr.MATCHES:
        right = parseCmpExpr(token, right, parseTerm());
        break;

      case Expr.ADD: case Expr.SUB:
        right = parseAddExpr(token, right, parseTerm());
        break;

      case Expr.MUL: case Expr.DIV: case Expr.MOD:
        right = parseMulExpr(token, right, parseTerm());
        break;

      default:
        _peek = token;
        return new BooleanExpr(code, left, right);
      }
    }
  }

  /**
   * and-expr ::= and-expr 'and' expr
   *          ::= cmp-expr
   */
  private Expr parseAndExpr(int code, Expr left, Expr right)
    throws ELParseException
  {
    while (true) {
      int token = scanToken();
      switch (token) {
      case Expr.AND:
        left = new BooleanExpr(code, left, right);
        code = token;
        right = parseTerm();
        break;

      case Expr.EQ: case Expr.NE:
      case Expr.LT: case Expr.GT:
      case Expr.LE: case Expr.GE:
      case Expr.MATCHES:
        right = parseCmpExpr(token, right, parseTerm());
        break;

      case Expr.ADD: case Expr.SUB:
        right = parseAddExpr(token, right, parseTerm());
        break;

      case Expr.MUL: case Expr.DIV: case Expr.MOD:
        right = parseMulExpr(token, right, parseTerm());
        break;

      default:
        _peek = token;
        return new BooleanExpr(code, left, right);
      }
    }
  }

  /**
   * cmp-expr ::= cmp-expr '=' expr
   *          ::= add-expr
   */
  private Expr parseCmpExpr(int code, Expr left, Expr right)
    throws ELParseException
  {
    while (true) {
      int token = scanToken();
      switch (token) {
      case Expr.EQ: case Expr.NE:
      case Expr.GT: case Expr.LT:
      case Expr.LE: case Expr.GE:
      case Expr.MATCHES:
        left = CmpExpr.create(code, left, right);

        code = token;
        right = parseTerm();
        break;

      case Expr.ADD: case Expr.SUB:
        right = parseAddExpr(token, right, parseTerm());
        break;

      case Expr.MUL: case Expr.DIV: case Expr.MOD:
        right = parseMulExpr(token, right, parseTerm());
        break;

      default:
        _peek = token;
        return CmpExpr.create(code, left, right);
      }
    }
  }

  /**
   * add-expr ::= add-expr '+' expr
   *          ::= mul-expr
   */
  private Expr parseAddExpr(int code, Expr left, Expr right)
    throws ELParseException
  {
    while (true) {
      int token = scanToken();
      switch (token) {
      case Expr.ADD: case Expr.SUB:
        left = BinaryExpr.create(code, left, right);
        code = token;
        right = parseTerm();
        break;

      case Expr.MUL: case Expr.DIV: case Expr.MOD:
        right = parseMulExpr(token, right, parseTerm());
        break;

      default:
        _peek = token;
        return BinaryExpr.create(code, left, right);
      }
    }
  }

  /**
   * mul-expr ::= mul-expr '*' expr
   *          ::= expr
   */
  private Expr parseMulExpr(int code, Expr left, Expr right)
    throws ELParseException
  {
    while (true) {
      int token = scanToken();
      switch (token) {
      case Expr.MUL: case Expr.DIV: case Expr.MOD:
        left = BinaryExpr.create(code, left, right);
        right = parseTerm();
        code = token;
        break;

      default:
        _peek = token;
        return BinaryExpr.create(code, left, right);
      }
    }
  }

  /**
   * term ::= simple-term
   *      ::= term '[' expr ']'
   *      ::= term . identifier
   */
  private Expr parseTerm()
    throws ELParseException
  {
    Expr term = parseSimpleTerm();
    
    while (true) {
      int token = scanToken();

      switch (token) {
      case '[':
      {
        Expr expr = parseExpr();
        token = scanToken();
        if (token != ']')
          throw error(L.l("Expected `]' at {0}.  All open array braces must have matching closing brace.", badChar(token)));

        term = term.createField(expr);
        break;
      }
      
      case '(':
      {
        ArrayList<Expr> argList = new ArrayList<Expr>();

        int ch = skipWhitespace(read());

        while (ch > 0 && ch != ')') {
          unread();
          argList.add(parseExpr());
          
          token = scanToken();
          
          if (token != ',') {
            ch = token;
            break;
          }
          
          ch = skipWhitespace(read());
        }
        
        if (ch != ')')
          throw error(L.l("Expected `)' at {0}.  All functions must have matching closing parenthesis.", badChar(ch)));

        // token = scanToken();

        Expr []args = (Expr []) argList.toArray(new Expr[argList.size()]);

        Expr expr = term.createMethod(args);
        if (expr == null)
          throw error(L.l("Method call not supported in this context `{0}'.",
                          term));
        term = expr;
        break;
      }

      case '.':
      {
        int ch = skipWhitespace(read());
        
        if (! Character.isJavaIdentifierStart((char) ch))
          throw error(L.l("Expected `]' at {0}.  Field references must be identifiers.", badChar(ch)));

        String field = readName(ch);

        term = term.createField(field);
        break;
      }

      case Expr.NOT: {
        if (Expr.NOT == token && term != null && term.isConstant())
          throw new ELParseException(L.l("invalid expression `{0}'", _string));

        _peek = token;
        return term;
      }
        
      default:
        _peek = token;
        return term;
      }
    }
  }

  /**
   * simple-term ::= number
   *             ::= '(' expr ')'
   *             ::= variable
   *             ::= '"' string '"'
   *             ::= true | false | null
   */
  private Expr parseSimpleTerm()
    throws ELParseException
  {
    int ch = read();
    
    ch = skipWhitespace(ch);

    switch (ch) {
    case '.':
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
      {
        if (_isMethodExpr)
          throw new ELParseException(L.l("Invalid method expression `{0}'",
                                         _string));

        long value = 0;
        double exp = 1;
        int digits = 0;

        for (; ch >= '0' && ch <= '9'; ch = read())
          value = 10 * value + ch - '0';

        if (ch != '.' && ch != 'e' && ch != 'E') {
          unread();
          return new LongLiteral(value);
        }
        
        if (ch == '.') {
          for (ch = read(); ch >= '0' && ch <= '9'; ch = read()) {
            value = 10 * value + ch - '0';
            exp *= 10;
            digits--;
          }
        }

        if (ch == 'e' || ch == 'E') {
          int sign = 1;
          int expValue = 0;
          
          ch = read();
          if (ch == '-') {
            sign = -1;
            ch = read();
          }
          else if (ch == '+')
            ch = read();
          
          for (; ch >= '0' && ch <= '9'; ch = read())
            expValue = 10 * expValue + ch - '0';

          exp = Math.pow(10, digits + sign * expValue);

          unread();
          
          return new DoubleLiteral((double) value * (double) exp);
        }
        
        unread();
        return new DoubleLiteral((double) value / (double) exp);
      }

    case '-': {
      if (_isMethodExpr)
        throw new ELParseException(L.l("Invalid method expression `{0}'",
                                       _string));
      
      return new MinusExpr(parseTerm());
    }

    case '!': {
      if (_isMethodExpr)
        throw new ELParseException(L.l("Invalid method expression `{0}'",
                                       _string));

      return UnaryExpr.create(Expr.NOT, parseTerm());
    }

    case '+': {
      if (_isMethodExpr)
        throw new ELParseException(L.l("Invalid method expression `{0}'",
                                       _string));

      return parseTerm();
    }

    case '(':
      {
        Expr expr = parseExpr();
        if ((ch = scanToken()) != ')')
          throw error(L.l("Expected `)' at {0}.  All open parentheses must have matching closing parentheses.", badChar(ch)));
        
        return expr;
      }

    case '\'': case '"':
      {
        int end = ch;
        CharBuffer cb = _cb;
        cb.clear();
        
        for (ch = read(); ch >= 0; ch = read()) {
          if (ch == '\\')
            cb.append((char) read());
          else if (ch != end)
            cb.append((char) ch);
          else if ((ch = read()) == end)
            cb.append((char) ch);
          else {
            unread();
            break;
          }
        }

        return new StringLiteral(cb.toString());
      }

    default:
      if (! Character.isJavaIdentifierStart((char) ch) && ch != ':')
        throw error(L.l("Unexpected character at {0}.", badChar(ch)));
      
      CharBuffer cb = _cb;
      cb.clear();
      
      for (;
           Character.isJavaIdentifierPart((char) ch) || ch == ':';
           ch = read())
        cb.append((char) ch);

      unread();

      if (cb.charAt(cb.length() - 1) == ':') {
        unread();
        
        cb.deleteCharAt(cb.length() - 1);
      }

      String name = cb.toString();

      if (name.equals("null"))
        return new NullLiteral();
      else if (name.equals("true"))
        return new BooleanLiteral(true);
      else if (name.equals("false"))
        return new BooleanLiteral(false);
      else if (name.equals("not"))
        return UnaryExpr.create(Expr.NOT, parseTerm());
      else if (name.equals("empty"))
        return UnaryExpr.create(Expr.EMPTY, parseTerm());
      else {
        VariableMapper varMapper = _elContext.getVariableMapper();

        ValueExpression valueExpr = null;

        if (varMapper != null)
          valueExpr = varMapper.resolveVariable(name);

        if (valueExpr != null)
          return new ValueExpr(name, valueExpr);

        Expr expr = createImplicitObjectExpr(name);

        if (expr != null)
          return expr;

        try {
          Method method = getStaticMethod(name);

          if (method != null)
            return new StaticMethodExpr(method);
          else
            return new IdExpr(name);
        } catch (Exception e) {
          log.log(Level.FINEST, e.toString(), e);

          return new IdExpr(name);
        }
      }
    }
  }

  /**
   * Creates the implicit object for the name.
   */
  protected Expr createImplicitObjectExpr(String name)
  {
    return null;
  }

  /**
   * Creates the implicit object for the name.
   */
  protected Method getStaticMethod(String name)
    throws ELParseException
  {
    Method method = null;

    FunctionMapper funMapper = _elContext.getFunctionMapper();

    if (funMapper != null) {
      String prefix = "";
      String localName = name;

      int p = name.indexOf(':');
      if (p > 0) {
        prefix = name.substring(0, p);
        localName = name.substring(p + 1);
      }
      
      method = funMapper.resolveFunction(prefix, localName);
    }
    
    return method;
  }

  /**
   * Scans the next token.
   *
   * @return token code, expressed as an Expr enumeration.
   */
  private int scanToken()
    throws ELParseException
  {
    if (_peek >= 0) {
      int value = _peek;
      _peek = -1;
      return value;
    }

    int ch = skipWhitespace(read());

    switch (ch) {
    case '+': return Expr.ADD;
    case '-': return Expr.SUB;
    case '*': return Expr.MUL;
    case '/': return Expr.DIV;
    case '%': return Expr.MOD;

    case '!':
      ch = read();
      if (ch == '=')
        return Expr.NE;
      else
        return Expr.NOT;
      
    case '=':
      ch = read();
      if (ch == '=')
        return Expr.EQ;
      else if (ch == '~')
        return Expr.MATCHES;
      else
        throw error(L.l("expected '==' or '=~' at '={0}'", badChar(ch)));
      
    case '&':
      ch = read();
      if (ch == '&')
        return Expr.AND;
      else
        throw error(L.l("expected '&&' at '&{0}'", badChar(ch)));
      
    case '|':
      ch = read();
      if (ch == '|')
        return Expr.OR;
      else
        throw error(L.l("expected '||' at '|{0}'", badChar(ch)));

    case '<':
      ch = read();
      if (ch == '=')
        return Expr.LE;
      else {
        unread();
        return Expr.LT;
      }

    case '>':
      ch = read();
      if (ch == '=')
        return Expr.GE;
      else {
        unread();
        return Expr.GT;
      }

    case '[':
      return '[';
      
    case ']':
      return ']';
      
    case ')':
      return ')';
      
    case '(':
      return '(';
      
    case '.':
      return '.';
      
    case ',':
      return ',';
      
    case '?':
    case ':':
      return ch;

    default:
      if (Character.isJavaIdentifierStart((char) ch)) {
        String name = readName(ch);

        if (name.equals("div"))
          return Expr.DIV;
        else if (name.equals("mod"))
          return Expr.MOD;
        else if (name.equals("eq"))
          return Expr.EQ;
        else if (name.equals("ne"))
          return Expr.NE;
        else if (name.equals("lt"))
          return Expr.LT;
        else if (name.equals("le"))
          return Expr.LE;
        else if (name.equals("gt"))
          return Expr.GT;
        else if (name.equals("ge"))
          return Expr.GE;
        else if (name.equals("and"))
          return Expr.AND;
        else if (name.equals("or"))
          return Expr.OR;
        else if (name.equals("matches"))
          return Expr.MATCHES;
        else
          throw error(L.l("expected binary operation at `{0}'", name));
      }

      unread();
      return -1;
    }
  }

  private String readName(int ch)
  {
    CharBuffer cb = CharBuffer.allocate();

    for (; Character.isJavaIdentifierPart((char) ch); ch = read())
      cb.append((char) ch);

    unread();

    return cb.toString();
  }

  /**
   * Skips whitespace, returning the next meaningful character.
   */
  private int skipWhitespace(int ch)
    throws ELParseException
  {
    for (; ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; ch = read()) {
    }

    return ch;
  }

  /**
   * Reads the next character, returning -1 on end of file.
   */
  private int read()
  {
    if (_index < _string.length())
      return _string.charAt(_index++);
    else {
      _index++;
      return -1;
    }
  }

  /**
   * Unread the last character.
   */
  private void unread()
  {
    _index--;
  }

  /**
   * Returns a readable version of the character.
   */
  private String badChar(int ch)
  {
    if (ch < 0)
      return L.l("end of file");
    else if (ch == '\n')
      return L.l("end of line");
    else
      return "`" + (char) ch + "'";
  }

  /**
   * Returns an new exception.
   */
  private ELParseException error(String message)
  {
    return new ELParseException(message + " in " + _string);
  }
}
