com.caucho.el.ELParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of resin Show documentation
Show all versions of resin Show documentation
Resin Java Application Server
/*
* Copyright (c) 1998-2018 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;
protected boolean _isParsingArgs;
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 && ! _isParsingArgs) {
switch (token) {
case '?':
case Expr.COND_BINARY:
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.COND_BINARY:
{
Expr defaultExpr = parseExpr();
left = new ConditionalNullExpr(left, defaultExpr);
}
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 argList = new ArrayList();
int ch = skipWhitespace(read());
while (ch > 0 && ch != ')') {
unread();
_isParsingArgs = true;
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)));
_isParsingArgs = false;
// 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 && ! _isParsingArgs)
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 && ! _isParsingArgs)
throw new ELParseException(L.l("Invalid method expression `{0}'",
_string));
return new MinusExpr(parseTerm());
}
case '!': {
if (_isMethodExpr && ! _isParsingArgs)
throw new ELParseException(L.l("Invalid method expression `{0}'",
_string));
return UnaryExpr.create(Expr.NOT, parseTerm());
}
case '+': {
if (_isMethodExpr && ! _isParsingArgs)
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 '?':
ch = read();
if (ch == ':')
return Expr.COND_BINARY;
else {
unread();
return '?';
}
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);
}
}