/*
* 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);
}
}