SpelExpressionParser made threadsafe
This commit is contained in:
parent
cd20be7a4f
commit
855eac549e
|
|
@ -28,4 +28,12 @@ public class SpelExpressionParserFactory {
|
||||||
return new SpelExpressionParser();
|
return new SpelExpressionParser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param configuration configuration bit flags @see SpelExpressionParserConfiguration
|
||||||
|
* @return an expression parser instance configured appropriately
|
||||||
|
*/
|
||||||
|
public static ExpressionParser getParser(int configuration) {
|
||||||
|
return new SpelExpressionParser(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,750 +15,46 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.expression.spel.standard;
|
package org.springframework.expression.spel.standard;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import org.springframework.expression.Expression;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
import org.springframework.expression.ParseException;
|
import org.springframework.expression.ParseException;
|
||||||
import org.springframework.expression.ParserContext;
|
import org.springframework.expression.ParserContext;
|
||||||
import org.springframework.expression.common.TemplateAwareExpressionParser;
|
import org.springframework.expression.common.TemplateAwareExpressionParser;
|
||||||
import org.springframework.expression.spel.SpelExpression;
|
import org.springframework.expression.spel.SpelExpression;
|
||||||
import org.springframework.expression.spel.SpelMessage;
|
import org.springframework.expression.spel.standard.internal.InternalSpelExpressionParser;
|
||||||
import org.springframework.expression.spel.SpelParseException;
|
|
||||||
import org.springframework.expression.spel.ast.Assign;
|
|
||||||
import org.springframework.expression.spel.ast.BooleanLiteral;
|
|
||||||
import org.springframework.expression.spel.ast.CompoundExpression;
|
|
||||||
import org.springframework.expression.spel.ast.ConstructorReference;
|
|
||||||
import org.springframework.expression.spel.ast.Elvis;
|
|
||||||
import org.springframework.expression.spel.ast.FunctionReference;
|
|
||||||
import org.springframework.expression.spel.ast.Identifier;
|
|
||||||
import org.springframework.expression.spel.ast.Indexer;
|
|
||||||
import org.springframework.expression.spel.ast.Literal;
|
|
||||||
import org.springframework.expression.spel.ast.MethodReference;
|
|
||||||
import org.springframework.expression.spel.ast.NullLiteral;
|
|
||||||
import org.springframework.expression.spel.ast.OpAnd;
|
|
||||||
import org.springframework.expression.spel.ast.OpDivide;
|
|
||||||
import org.springframework.expression.spel.ast.OpEQ;
|
|
||||||
import org.springframework.expression.spel.ast.OpGE;
|
|
||||||
import org.springframework.expression.spel.ast.OpGT;
|
|
||||||
import org.springframework.expression.spel.ast.OpLE;
|
|
||||||
import org.springframework.expression.spel.ast.OpLT;
|
|
||||||
import org.springframework.expression.spel.ast.OpMinus;
|
|
||||||
import org.springframework.expression.spel.ast.OpModulus;
|
|
||||||
import org.springframework.expression.spel.ast.OpMultiply;
|
|
||||||
import org.springframework.expression.spel.ast.OpNE;
|
|
||||||
import org.springframework.expression.spel.ast.OpOr;
|
|
||||||
import org.springframework.expression.spel.ast.OpPlus;
|
|
||||||
import org.springframework.expression.spel.ast.OperatorInstanceof;
|
|
||||||
import org.springframework.expression.spel.ast.OperatorMatches;
|
|
||||||
import org.springframework.expression.spel.ast.OperatorNot;
|
|
||||||
import org.springframework.expression.spel.ast.OperatorPower;
|
|
||||||
import org.springframework.expression.spel.ast.Projection;
|
|
||||||
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
|
|
||||||
import org.springframework.expression.spel.ast.QualifiedIdentifier;
|
|
||||||
import org.springframework.expression.spel.ast.Selection;
|
|
||||||
import org.springframework.expression.spel.ast.SpelNodeImpl;
|
|
||||||
import org.springframework.expression.spel.ast.StringLiteral;
|
|
||||||
import org.springframework.expression.spel.ast.Ternary;
|
|
||||||
import org.springframework.expression.spel.ast.TypeReference;
|
|
||||||
import org.springframework.expression.spel.ast.VariableReference;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hand written SpEL parser. Instances are reusable.
|
* SpEL parser. Instances are reusable and thread safe.
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public class SpelExpressionParser extends TemplateAwareExpressionParser {
|
public class SpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
|
|
||||||
// The expression being parsed
|
|
||||||
private String expressionString;
|
|
||||||
|
|
||||||
// The token stream constructed from that expression string
|
|
||||||
private List<Token> tokenStream;
|
|
||||||
|
|
||||||
// length of a populated token stream
|
|
||||||
private int tokenStreamLength;
|
|
||||||
|
|
||||||
// Current location in the token stream when processing tokens
|
|
||||||
private int tokenStreamPointer;
|
|
||||||
|
|
||||||
// For rules that build nodes, they are stacked here for return
|
|
||||||
private Stack<SpelNodeImpl> constructedNodes = new Stack<SpelNodeImpl>();
|
|
||||||
|
|
||||||
private int configuration;
|
private int configuration;
|
||||||
|
|
||||||
public SpelExpressionParser() {
|
|
||||||
this(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a parser with some configured behaviour. Supported configuration
|
* Create a parser with some configured behaviour. Supported configuration
|
||||||
* bit flags can be seen in @see {@link SpelExpressionParserConfiguration}
|
* bit flags can be seen in {@link SpelExpressionParserConfiguration}
|
||||||
* @param configuration bitflags for configuration options
|
* @param configuration bitflags for configuration options
|
||||||
*/
|
*/
|
||||||
public SpelExpressionParser(int configuration) {
|
public SpelExpressionParser(int configuration) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a parser with default behaviour.
|
||||||
|
*/
|
||||||
|
public SpelExpressionParser() {
|
||||||
|
this(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Expression doParseExpression(String expressionString, ParserContext context) throws ParseException {
|
||||||
|
return new InternalSpelExpressionParser(configuration).doParseExpression(expressionString, context);
|
||||||
|
}
|
||||||
|
|
||||||
public SpelExpression parse(String expressionString) throws ParseException {
|
public SpelExpression parse(String expressionString) throws ParseException {
|
||||||
return doParseExpression(expressionString, null);
|
return new InternalSpelExpressionParser(configuration).parse(expressionString);
|
||||||
}
|
|
||||||
|
|
||||||
protected SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException {
|
|
||||||
try {
|
|
||||||
this.expressionString = expressionString;
|
|
||||||
Tokenizer tokenizer = new Tokenizer(expressionString);
|
|
||||||
tokenizer.process();
|
|
||||||
tokenStream = tokenizer.getTokens();
|
|
||||||
tokenStreamLength = tokenStream.size();
|
|
||||||
tokenStreamPointer = 0;
|
|
||||||
constructedNodes.clear();
|
|
||||||
SpelNodeImpl ast = eatExpression();
|
|
||||||
if (moreTokens()) {
|
|
||||||
throw new SpelParseException(peekToken().startpos,SpelMessage.MORE_INPUT,toString(nextToken()));
|
|
||||||
}
|
|
||||||
assert constructedNodes.isEmpty();
|
|
||||||
return new SpelExpression(expressionString, ast, configuration);
|
|
||||||
} catch (InternalParseException ipe) {
|
|
||||||
throw ipe.getCause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expression
|
|
||||||
// : logicalOrExpression
|
|
||||||
// ( (ASSIGN^ logicalOrExpression)
|
|
||||||
// | (DEFAULT^ logicalOrExpression)
|
|
||||||
// | (QMARK^ expression COLON! expression)
|
|
||||||
// | (ELVIS^ expression))?;
|
|
||||||
private SpelNodeImpl eatExpression() {
|
|
||||||
SpelNodeImpl expr = eatLogicalOrExpression();
|
|
||||||
if (moreTokens()) {
|
|
||||||
Token t = peekToken();
|
|
||||||
if (t.kind==TokenKind.ASSIGN) { // a=b
|
|
||||||
nextToken();
|
|
||||||
SpelNodeImpl assignedValue = eatLogicalOrExpression();
|
|
||||||
return new Assign(toPos(t),expr,assignedValue);
|
|
||||||
} else if (t.kind==TokenKind.ELVIS) { // a?:b (a if it isn't null, otherwise b)
|
|
||||||
nextToken(); // elvis has left the building
|
|
||||||
SpelNodeImpl valueIfNull = eatExpression();
|
|
||||||
return new Elvis(toPos(t),expr,valueIfNull);
|
|
||||||
} else if (t.kind==TokenKind.QMARK) { // a?b:c
|
|
||||||
nextToken();
|
|
||||||
SpelNodeImpl ifTrueExprValue = eatExpression();
|
|
||||||
eatToken(TokenKind.COLON);
|
|
||||||
SpelNodeImpl ifFalseExprValue = eatExpression();
|
|
||||||
return new Ternary(toPos(t),expr,ifTrueExprValue,ifFalseExprValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
//logicalOrExpression : logicalAndExpression (OR^ logicalAndExpression)*;
|
|
||||||
private SpelNodeImpl eatLogicalOrExpression() {
|
|
||||||
SpelNodeImpl expr = eatLogicalAndExpression();
|
|
||||||
while (peekIdentifierToken("or")) {
|
|
||||||
Token t = nextToken(); //consume OR
|
|
||||||
SpelNodeImpl rhExpr = eatLogicalAndExpression();
|
|
||||||
checkRightOperand(t,rhExpr);
|
|
||||||
expr = new OpOr(toPos(t),expr,rhExpr);
|
|
||||||
}
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// logicalAndExpression : relationalExpression (AND^ relationalExpression)*;
|
|
||||||
private SpelNodeImpl eatLogicalAndExpression() {
|
|
||||||
SpelNodeImpl expr = eatRelationalExpression();
|
|
||||||
while (peekIdentifierToken("and")) {
|
|
||||||
Token t = nextToken();// consume 'AND'
|
|
||||||
SpelNodeImpl rhExpr = eatRelationalExpression();
|
|
||||||
checkRightOperand(t,rhExpr);
|
|
||||||
expr = new OpAnd(toPos(t),expr,rhExpr);
|
|
||||||
}
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// relationalExpression : sumExpression (relationalOperator^ sumExpression)?;
|
|
||||||
private SpelNodeImpl eatRelationalExpression() {
|
|
||||||
SpelNodeImpl expr = eatSumExpression();
|
|
||||||
Token relationalOperatorToken = maybeEatRelationalOperator();
|
|
||||||
if (relationalOperatorToken!=null) {
|
|
||||||
Token t = nextToken(); //consume relational operator token
|
|
||||||
SpelNodeImpl rhExpr = eatSumExpression();
|
|
||||||
checkRightOperand(t,rhExpr);
|
|
||||||
TokenKind tk = relationalOperatorToken.kind;
|
|
||||||
if (relationalOperatorToken.isNumericRelationalOperator()) {
|
|
||||||
int pos = toPos(t);
|
|
||||||
if (tk==TokenKind.GT) {
|
|
||||||
return new OpGT(pos,expr,rhExpr);
|
|
||||||
} else if (tk==TokenKind.LT) {
|
|
||||||
return new OpLT(pos,expr,rhExpr);
|
|
||||||
} else if (tk==TokenKind.LE) {
|
|
||||||
return new OpLE(pos,expr,rhExpr);
|
|
||||||
} else if (tk==TokenKind.GE) {
|
|
||||||
return new OpGE(pos,expr,rhExpr);
|
|
||||||
} else if (tk==TokenKind.EQ) {
|
|
||||||
return new OpEQ(pos,expr,rhExpr);
|
|
||||||
} else {
|
|
||||||
assert tk==TokenKind.NE;
|
|
||||||
return new OpNE(pos,expr,rhExpr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tk==TokenKind.INSTANCEOF) {
|
|
||||||
return new OperatorInstanceof(toPos(t),expr,rhExpr);
|
|
||||||
} else if (tk==TokenKind.MATCHES) {
|
|
||||||
return new OperatorMatches(toPos(t),expr,rhExpr);
|
|
||||||
} else {
|
|
||||||
assert tk==TokenKind.BETWEEN;
|
|
||||||
return new org.springframework.expression.spel.ast.OperatorBetween(toPos(t),expr,rhExpr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
//sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*;
|
|
||||||
private SpelNodeImpl eatSumExpression() {
|
|
||||||
SpelNodeImpl expr = eatProductExpression();
|
|
||||||
while (peekToken(TokenKind.PLUS,TokenKind.MINUS)) {
|
|
||||||
Token t = nextToken();//consume PLUS or MINUS
|
|
||||||
SpelNodeImpl rhExpr = eatProductExpression();
|
|
||||||
checkRightOperand(t,rhExpr);
|
|
||||||
if (t.kind==TokenKind.PLUS) {
|
|
||||||
expr = new OpPlus(toPos(t),expr,rhExpr);
|
|
||||||
} else {
|
|
||||||
assert t.kind==TokenKind.MINUS;
|
|
||||||
expr = new OpMinus(toPos(t),expr,rhExpr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
|
|
||||||
private SpelNodeImpl eatProductExpression() {
|
|
||||||
SpelNodeImpl expr = eatPowerExpression();
|
|
||||||
while (peekToken(TokenKind.STAR,TokenKind.DIV,TokenKind.MOD)) {
|
|
||||||
Token t = nextToken(); // consume STAR/DIV/MOD
|
|
||||||
SpelNodeImpl rhExpr = eatPowerExpression();
|
|
||||||
checkRightOperand(t,rhExpr);
|
|
||||||
if (t.kind==TokenKind.STAR) {
|
|
||||||
expr = new OpMultiply(toPos(t),expr,rhExpr);
|
|
||||||
} else if (t.kind==TokenKind.DIV) {
|
|
||||||
expr = new OpDivide(toPos(t),expr,rhExpr);
|
|
||||||
} else {
|
|
||||||
assert t.kind==TokenKind.MOD;
|
|
||||||
expr = new OpModulus(toPos(t),expr,rhExpr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// powerExpr : unaryExpression (POWER^ unaryExpression)? ;
|
|
||||||
private SpelNodeImpl eatPowerExpression() {
|
|
||||||
SpelNodeImpl expr = eatUnaryExpression();
|
|
||||||
if (peekToken(TokenKind.POWER)) {
|
|
||||||
Token t = nextToken();//consume POWER
|
|
||||||
SpelNodeImpl rhExpr = eatUnaryExpression();
|
|
||||||
checkRightOperand(t,rhExpr);
|
|
||||||
return new OperatorPower(toPos(t),expr, rhExpr);
|
|
||||||
}
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// unaryExpression: (PLUS^ | MINUS^ | BANG^) unaryExpression | primaryExpression ;
|
|
||||||
private SpelNodeImpl eatUnaryExpression() {
|
|
||||||
if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.NOT)) {
|
|
||||||
Token t = nextToken();
|
|
||||||
SpelNodeImpl expr = eatUnaryExpression();
|
|
||||||
if (t.kind==TokenKind.NOT) {
|
|
||||||
return new OperatorNot(toPos(t),expr);
|
|
||||||
} else if (t.kind==TokenKind.PLUS) {
|
|
||||||
return new OpPlus(toPos(t),expr);
|
|
||||||
} else {
|
|
||||||
assert t.kind==TokenKind.MINUS;
|
|
||||||
return new OpMinus(toPos(t),expr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return eatPrimaryExpression();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// primaryExpression : startNode (node)? -> ^(EXPRESSION startNode (node)?);
|
|
||||||
private SpelNodeImpl eatPrimaryExpression() {
|
|
||||||
List<SpelNodeImpl> nodes = new ArrayList<SpelNodeImpl>();
|
|
||||||
SpelNodeImpl start = eatStartNode(); // always a start node
|
|
||||||
nodes.add(start);
|
|
||||||
while (maybeEatNode()) {
|
|
||||||
nodes.add(pop());
|
|
||||||
}
|
|
||||||
if (nodes.size()==1) {
|
|
||||||
return nodes.get(0);
|
|
||||||
} else {
|
|
||||||
return new CompoundExpression(toPos(start.getStartPosition(),nodes.get(nodes.size()-1).getEndPosition()),nodes.toArray(new SpelNodeImpl[nodes.size()]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// node : ((DOT dottedNode) | (SAFE_NAVI dottedNode) | nonDottedNode)+;
|
|
||||||
private boolean maybeEatNode() {
|
|
||||||
SpelNodeImpl expr = null;
|
|
||||||
if (peekToken(TokenKind.DOT,TokenKind.SAFE_NAVI)) {
|
|
||||||
expr = eatDottedNode();
|
|
||||||
} else {
|
|
||||||
expr = maybeEatNonDottedNode();
|
|
||||||
}
|
|
||||||
if (expr==null) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
push(expr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nonDottedNode: indexer;
|
|
||||||
private SpelNodeImpl maybeEatNonDottedNode() {
|
|
||||||
if (peekToken(TokenKind.LSQUARE)) {
|
|
||||||
if (maybeEatIndexer()) {
|
|
||||||
return pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//dottedNode
|
|
||||||
// : ((methodOrProperty
|
|
||||||
// | functionOrVar
|
|
||||||
// | projection
|
|
||||||
// | selection
|
|
||||||
// | firstSelection
|
|
||||||
// | lastSelection
|
|
||||||
// ))
|
|
||||||
// ;
|
|
||||||
private SpelNodeImpl eatDottedNode() {
|
|
||||||
Token t = nextToken();// it was a '.' or a '?.'
|
|
||||||
boolean nullSafeNavigation = t.kind==TokenKind.SAFE_NAVI;
|
|
||||||
if (maybeEatMethodOrProperty(nullSafeNavigation) || maybeEatFunctionOrVar() || maybeEatProjection(nullSafeNavigation) || maybeEatSelection(nullSafeNavigation)) {
|
|
||||||
return pop();
|
|
||||||
}
|
|
||||||
raiseInternalException(t.startpos,SpelMessage.UNEXPECTED_DATA_AFTER_DOT,toString(peekToken()));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// functionOrVar
|
|
||||||
// : (POUND ID LPAREN) => function
|
|
||||||
// | var
|
|
||||||
//
|
|
||||||
// function : POUND id=ID methodArgs -> ^(FUNCTIONREF[$id] methodArgs);
|
|
||||||
// var : POUND id=ID -> ^(VARIABLEREF[$id]);
|
|
||||||
private boolean maybeEatFunctionOrVar() {
|
|
||||||
if (!peekToken(TokenKind.HASH)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Token t = nextToken();
|
|
||||||
Token functionOrVariableName = eatToken(TokenKind.IDENTIFIER);
|
|
||||||
SpelNodeImpl[] args = maybeEatMethodArgs();
|
|
||||||
if (args==null) {
|
|
||||||
push(new VariableReference(functionOrVariableName.data,toPos(t.startpos,functionOrVariableName.endpos)));
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
push(new FunctionReference(functionOrVariableName.data,toPos(t.startpos,functionOrVariableName.endpos),args));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!;
|
|
||||||
private SpelNodeImpl[] maybeEatMethodArgs() {
|
|
||||||
if (!peekToken(TokenKind.LPAREN)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
List<SpelNodeImpl> args = new ArrayList<SpelNodeImpl>();
|
|
||||||
consumeArguments(args);
|
|
||||||
eatToken(TokenKind.RPAREN);
|
|
||||||
return args.toArray(new SpelNodeImpl[args.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void eatConstructorArgs(List<SpelNodeImpl> accumulatedArguments) {
|
|
||||||
if (!peekToken(TokenKind.LPAREN)) {
|
|
||||||
throw new InternalParseException(new SpelParseException(expressionString,positionOf(peekToken()),SpelMessage.MISSING_CONSTRUCTOR_ARGS));
|
|
||||||
}
|
|
||||||
consumeArguments(accumulatedArguments);
|
|
||||||
eatToken(TokenKind.RPAREN);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for consuming arguments for either a method or a constructor call
|
|
||||||
*/
|
|
||||||
private void consumeArguments(List<SpelNodeImpl> accumulatedArguments) {
|
|
||||||
int pos = peekToken().startpos;
|
|
||||||
Token next = null;
|
|
||||||
do {
|
|
||||||
nextToken();// consume ( (first time through) or comma (subsequent times)
|
|
||||||
Token t = peekToken();
|
|
||||||
if (t==null) {
|
|
||||||
raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS);
|
|
||||||
}
|
|
||||||
if (t.kind!=TokenKind.RPAREN) {
|
|
||||||
accumulatedArguments.add(eatExpression());
|
|
||||||
}
|
|
||||||
next = peekToken();
|
|
||||||
} while (next!=null && next.kind==TokenKind.COMMA);
|
|
||||||
if (next==null) {
|
|
||||||
raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int positionOf(Token t) {
|
|
||||||
if (t==null) {
|
|
||||||
// if null assume the problem is because the right token was
|
|
||||||
// not found at the end of the expression
|
|
||||||
return expressionString.length();
|
|
||||||
} else {
|
|
||||||
return t.startpos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//startNode
|
|
||||||
// : parenExpr | literal
|
|
||||||
// | type
|
|
||||||
// | methodOrProperty
|
|
||||||
// | functionOrVar
|
|
||||||
// | projection
|
|
||||||
// | selection
|
|
||||||
// | firstSelection
|
|
||||||
// | lastSelection
|
|
||||||
// | indexer
|
|
||||||
// | constructor
|
|
||||||
private SpelNodeImpl eatStartNode() {
|
|
||||||
if (maybeEatLiteral()) {
|
|
||||||
return pop();
|
|
||||||
} else if (maybeEatParenExpression()) {
|
|
||||||
return pop();
|
|
||||||
} else if (maybeEatTypeReference() || maybeEatNullReference() || maybeEatConstructorReference() || maybeEatMethodOrProperty(false) || maybeEatFunctionOrVar()) {
|
|
||||||
return pop();
|
|
||||||
} else if (maybeEatProjection(false) || maybeEatSelection(false) || maybeEatIndexer()) {
|
|
||||||
return pop();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private boolean maybeEatTypeReference() {
|
|
||||||
if (peekToken(TokenKind.IDENTIFIER)) {
|
|
||||||
Token typeName = peekToken();
|
|
||||||
if (!typeName.stringValue().equals("T")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
nextToken();
|
|
||||||
eatToken(TokenKind.LPAREN);
|
|
||||||
SpelNodeImpl node = eatPossiblyQualifiedId(true);
|
|
||||||
// dotted qualified id
|
|
||||||
eatToken(TokenKind.RPAREN);
|
|
||||||
constructedNodes.push(new TypeReference(toPos(typeName),node));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean maybeEatNullReference() {
|
|
||||||
if (peekToken(TokenKind.IDENTIFIER)) {
|
|
||||||
Token nullToken = peekToken();
|
|
||||||
if (!nullToken.stringValue().equals("null")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
nextToken();
|
|
||||||
constructedNodes.push(new NullLiteral(toPos(nullToken)));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//projection: PROJECT^ expression RCURLY!;
|
|
||||||
private boolean maybeEatProjection(boolean nullSafeNavigation) {
|
|
||||||
Token t = peekToken();
|
|
||||||
if (!peekToken(TokenKind.PROJECT,true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
SpelNodeImpl expr = eatExpression();
|
|
||||||
eatToken(TokenKind.RSQUARE);
|
|
||||||
constructedNodes.push(new Projection(nullSafeNavigation, toPos(t), expr));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean maybeEatIndexer() {
|
|
||||||
Token t = peekToken();
|
|
||||||
if (!peekToken(TokenKind.LSQUARE,true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
SpelNodeImpl expr = eatExpression();
|
|
||||||
eatToken(TokenKind.RSQUARE);
|
|
||||||
constructedNodes.push(new Indexer(toPos(t),expr));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean maybeEatSelection(boolean nullSafeNavigation) {
|
|
||||||
Token t = peekToken();
|
|
||||||
if (!peekSelectToken()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
nextToken();
|
|
||||||
SpelNodeImpl expr = eatExpression();
|
|
||||||
eatToken(TokenKind.RSQUARE);
|
|
||||||
if (t.kind==TokenKind.SELECT_FIRST) {
|
|
||||||
constructedNodes.push(new Selection(nullSafeNavigation,Selection.FIRST,toPos(t),expr));
|
|
||||||
} else if (t.kind==TokenKind.SELECT_LAST) {
|
|
||||||
constructedNodes.push(new Selection(nullSafeNavigation,Selection.LAST,toPos(t),expr));
|
|
||||||
} else {
|
|
||||||
constructedNodes.push(new Selection(nullSafeNavigation,Selection.ALL,toPos(t),expr));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eat an identifier, possibly qualified (meaning that it is dotted). If the dollarAllowed parameter is true then
|
|
||||||
* it will process any dollar characters found between names, and this allows it to support inner type references
|
|
||||||
* correctly. For example 'com.foo.bar.Outer$Inner' will produce the identifier sequence com, foo, bar, Outer, $Inner,
|
|
||||||
* note that the $ has been prefixed onto the Inner identifier. The code in TypeReference which reforms this into
|
|
||||||
* a typename copes with the $ prefixed identifiers.
|
|
||||||
* TODO AndyC Could create complete identifiers (a.b.c) here rather than a sequence of them? (a, b, c)
|
|
||||||
*/
|
|
||||||
private SpelNodeImpl eatPossiblyQualifiedId(boolean dollarAllowed) {
|
|
||||||
List<SpelNodeImpl> qualifiedIdPieces = new ArrayList<SpelNodeImpl>();
|
|
||||||
Token startnode = eatToken(TokenKind.IDENTIFIER);
|
|
||||||
qualifiedIdPieces.add(new Identifier(startnode.stringValue(),toPos(startnode)));
|
|
||||||
boolean dollar = false;
|
|
||||||
while (peekToken(TokenKind.DOT,true) || (dollarAllowed && (dollar = peekToken(TokenKind.DOLLAR,true)))) {
|
|
||||||
Token node = eatToken(TokenKind.IDENTIFIER);
|
|
||||||
if (dollar) {
|
|
||||||
qualifiedIdPieces.add(new Identifier("$"+node.stringValue(),((node.startpos-1)<<16)+node.endpos));
|
|
||||||
} else {
|
|
||||||
qualifiedIdPieces.add(new Identifier(node.stringValue(),toPos(node)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new QualifiedIdentifier(toPos(startnode.startpos,qualifiedIdPieces.get(qualifiedIdPieces.size()-1).getEndPosition()),qualifiedIdPieces.toArray(new SpelNodeImpl[qualifiedIdPieces.size()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean maybeEatMethodOrProperty(boolean nullSafeNavigation) {
|
|
||||||
if (peekToken(TokenKind.IDENTIFIER)) {
|
|
||||||
Token methodOrPropertyName = nextToken();
|
|
||||||
SpelNodeImpl[] args = maybeEatMethodArgs();
|
|
||||||
if (args==null) {
|
|
||||||
// property
|
|
||||||
push(new PropertyOrFieldReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName)));
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// methodreference
|
|
||||||
push(new MethodReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName),args));
|
|
||||||
// TODO what is the end position for a method reference? the name or the last arg?
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//constructor
|
|
||||||
//: ('new' qualifiedId LPAREN) => 'new' qualifiedId ctorArgs -> ^(CONSTRUCTOR qualifiedId ctorArgs)
|
|
||||||
private boolean maybeEatConstructorReference() {
|
|
||||||
if (peekIdentifierToken("new")) {
|
|
||||||
Token newToken = nextToken();
|
|
||||||
SpelNodeImpl possiblyQualifiedConstructorName = eatPossiblyQualifiedId(true);
|
|
||||||
List<SpelNodeImpl> nodes = new ArrayList<SpelNodeImpl>();
|
|
||||||
nodes.add(possiblyQualifiedConstructorName);
|
|
||||||
eatConstructorArgs(nodes);
|
|
||||||
push(new ConstructorReference(toPos(newToken),nodes.toArray(new SpelNodeImpl[nodes.size()]))); // TODO correct end position?
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void push(SpelNodeImpl newNode) {
|
|
||||||
constructedNodes.push(newNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SpelNodeImpl pop() {
|
|
||||||
return constructedNodes.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// literal
|
|
||||||
// : INTEGER_LITERAL
|
|
||||||
// | boolLiteral
|
|
||||||
// | STRING_LITERAL
|
|
||||||
// | HEXADECIMAL_INTEGER_LITERAL
|
|
||||||
// | REAL_LITERAL
|
|
||||||
// | DQ_STRING_LITERAL
|
|
||||||
// | NULL_LITERAL
|
|
||||||
private boolean maybeEatLiteral() {
|
|
||||||
Token t = peekToken();
|
|
||||||
if (t==null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (t.kind==TokenKind.LITERAL_INT) {
|
|
||||||
push(Literal.getIntLiteral(t.data, toPos(t), 10));
|
|
||||||
} else if (t.kind==TokenKind.LITERAL_LONG) {
|
|
||||||
push(Literal.getLongLiteral(t.data, toPos(t), 10));
|
|
||||||
} else if (t.kind==TokenKind.LITERAL_HEXINT) {
|
|
||||||
push(Literal.getIntLiteral(t.data, toPos(t), 16));
|
|
||||||
} else if (t.kind==TokenKind.LITERAL_HEXLONG) {
|
|
||||||
push(Literal.getLongLiteral(t.data, toPos(t), 16));
|
|
||||||
} else if (t.kind==TokenKind.LITERAL_REAL) {
|
|
||||||
push(Literal.getRealLiteral(t.data, toPos(t),false));
|
|
||||||
} else if (t.kind==TokenKind.LITERAL_REAL_FLOAT) {
|
|
||||||
push(Literal.getRealLiteral(t.data, toPos(t), true));
|
|
||||||
} else if (peekIdentifierToken("true")) {
|
|
||||||
push(new BooleanLiteral(t.data,toPos(t),true));
|
|
||||||
} else if (peekIdentifierToken("false")) {
|
|
||||||
push(new BooleanLiteral(t.data,toPos(t),false));
|
|
||||||
} else if (t.kind==TokenKind.LITERAL_STRING) {
|
|
||||||
push(new StringLiteral(t.data,toPos(t),t.data));
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
nextToken();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//parenExpr : LPAREN! expression RPAREN!;
|
|
||||||
private boolean maybeEatParenExpression() {
|
|
||||||
if (peekToken(TokenKind.LPAREN)) {
|
|
||||||
nextToken();
|
|
||||||
SpelNodeImpl expr = eatExpression();
|
|
||||||
eatToken(TokenKind.RPAREN);
|
|
||||||
push(expr);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// relationalOperator
|
|
||||||
// : EQUAL | NOT_EQUAL | LESS_THAN | LESS_THAN_OR_EQUAL | GREATER_THAN
|
|
||||||
// | GREATER_THAN_OR_EQUAL | INSTANCEOF | BETWEEN | MATCHES
|
|
||||||
private Token maybeEatRelationalOperator() {
|
|
||||||
Token t = peekToken();
|
|
||||||
if (t==null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (t.isNumericRelationalOperator()) {
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
if (t.isIdentifier()) {
|
|
||||||
String idString = t.stringValue();
|
|
||||||
if (idString.equalsIgnoreCase("instanceof")) {
|
|
||||||
return t.asInstanceOfToken();
|
|
||||||
} else if (idString.equalsIgnoreCase("matches")) {
|
|
||||||
return t.asMatchesToken();
|
|
||||||
} else if (idString.equalsIgnoreCase("between")) {
|
|
||||||
return t.asBetweenToken();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Token eatToken(TokenKind expectedKind) {
|
|
||||||
assert moreTokens();
|
|
||||||
Token t = nextToken();
|
|
||||||
if (t==null) {
|
|
||||||
raiseInternalException( expressionString.length(), SpelMessage.OOD);
|
|
||||||
}
|
|
||||||
if (t.kind!=expectedKind) {
|
|
||||||
raiseInternalException(t.startpos,SpelMessage.NOT_EXPECTED_TOKEN, expectedKind.toString().toLowerCase(),t.getKind().toString().toLowerCase());
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean peekToken(TokenKind desiredTokenKind) {
|
|
||||||
return peekToken(desiredTokenKind,false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean peekToken(TokenKind desiredTokenKind, boolean consumeIfMatched) {
|
|
||||||
if (!moreTokens()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Token t = peekToken();
|
|
||||||
if (t.kind==desiredTokenKind) {
|
|
||||||
if (consumeIfMatched) {
|
|
||||||
tokenStreamPointer++;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean peekToken(TokenKind possible1,TokenKind possible2) {
|
|
||||||
if (!moreTokens()) return false;
|
|
||||||
Token t = peekToken();
|
|
||||||
return t.kind==possible1 || t.kind==possible2;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean peekToken(TokenKind possible1,TokenKind possible2, TokenKind possible3) {
|
|
||||||
if (!moreTokens()) return false;
|
|
||||||
Token t = peekToken();
|
|
||||||
return t.kind==possible1 || t.kind==possible2 || t.kind==possible3;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean peekIdentifierToken(String identifierString) {
|
|
||||||
if (!moreTokens()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Token t = peekToken();
|
|
||||||
return t.kind==TokenKind.IDENTIFIER && t.stringValue().equalsIgnoreCase(identifierString);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean peekSelectToken() {
|
|
||||||
if (!moreTokens()) return false;
|
|
||||||
Token t = peekToken();
|
|
||||||
return t.kind==TokenKind.SELECT || t.kind==TokenKind.SELECT_FIRST || t.kind==TokenKind.SELECT_LAST;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean moreTokens() {
|
|
||||||
return tokenStreamPointer<tokenStream.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Token nextToken() {
|
|
||||||
if (tokenStreamPointer>=tokenStreamLength) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return tokenStream.get(tokenStreamPointer++);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Token peekToken() {
|
|
||||||
if (tokenStreamPointer>=tokenStreamLength) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return tokenStream.get(tokenStreamPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void raiseInternalException(int pos, SpelMessage message,Object... inserts) {
|
|
||||||
throw new InternalParseException(new SpelParseException(expressionString,pos,message,inserts));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString(Token t) {
|
|
||||||
if (t.getKind().hasPayload()) {
|
|
||||||
return t.stringValue();
|
|
||||||
} else {
|
|
||||||
return t.kind.toString().toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkRightOperand(Token token, SpelNodeImpl operandExpression) {
|
|
||||||
if (operandExpression==null) {
|
|
||||||
raiseInternalException(token.startpos,SpelMessage.RIGHT_OPERAND_PROBLEM);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compress the start and end of a token into a single int
|
|
||||||
*/
|
|
||||||
private int toPos(Token t) {
|
|
||||||
return (t.startpos<<16)+t.endpos;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int toPos(int start,int end) {
|
|
||||||
return (start<<16)+end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@ public interface SpelExpressionParserConfiguration {
|
||||||
*/
|
*/
|
||||||
static final int CreateObjectIfAttemptToReferenceNull = 0x0001;
|
static final int CreateObjectIfAttemptToReferenceNull = 0x0001;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This option allows collections to grow if an attempt is made to index an element beyond the current size. Rather than fail the
|
||||||
|
* collection is populated with elements up to the specified index.
|
||||||
|
*/
|
||||||
static final int GrowListsOnIndexBeyondSize = 0x0002;
|
static final int GrowListsOnIndexBeyondSize = 0x0002;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,769 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2008-2009 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.expression.spel.standard.internal;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import org.springframework.expression.ParseException;
|
||||||
|
import org.springframework.expression.ParserContext;
|
||||||
|
import org.springframework.expression.common.TemplateAwareExpressionParser;
|
||||||
|
import org.springframework.expression.spel.SpelExpression;
|
||||||
|
import org.springframework.expression.spel.SpelMessage;
|
||||||
|
import org.springframework.expression.spel.SpelParseException;
|
||||||
|
import org.springframework.expression.spel.ast.Assign;
|
||||||
|
import org.springframework.expression.spel.ast.BooleanLiteral;
|
||||||
|
import org.springframework.expression.spel.ast.CompoundExpression;
|
||||||
|
import org.springframework.expression.spel.ast.ConstructorReference;
|
||||||
|
import org.springframework.expression.spel.ast.Elvis;
|
||||||
|
import org.springframework.expression.spel.ast.FunctionReference;
|
||||||
|
import org.springframework.expression.spel.ast.Identifier;
|
||||||
|
import org.springframework.expression.spel.ast.Indexer;
|
||||||
|
import org.springframework.expression.spel.ast.Literal;
|
||||||
|
import org.springframework.expression.spel.ast.MethodReference;
|
||||||
|
import org.springframework.expression.spel.ast.NullLiteral;
|
||||||
|
import org.springframework.expression.spel.ast.OpAnd;
|
||||||
|
import org.springframework.expression.spel.ast.OpDivide;
|
||||||
|
import org.springframework.expression.spel.ast.OpEQ;
|
||||||
|
import org.springframework.expression.spel.ast.OpGE;
|
||||||
|
import org.springframework.expression.spel.ast.OpGT;
|
||||||
|
import org.springframework.expression.spel.ast.OpLE;
|
||||||
|
import org.springframework.expression.spel.ast.OpLT;
|
||||||
|
import org.springframework.expression.spel.ast.OpMinus;
|
||||||
|
import org.springframework.expression.spel.ast.OpModulus;
|
||||||
|
import org.springframework.expression.spel.ast.OpMultiply;
|
||||||
|
import org.springframework.expression.spel.ast.OpNE;
|
||||||
|
import org.springframework.expression.spel.ast.OpOr;
|
||||||
|
import org.springframework.expression.spel.ast.OpPlus;
|
||||||
|
import org.springframework.expression.spel.ast.OperatorInstanceof;
|
||||||
|
import org.springframework.expression.spel.ast.OperatorMatches;
|
||||||
|
import org.springframework.expression.spel.ast.OperatorNot;
|
||||||
|
import org.springframework.expression.spel.ast.OperatorPower;
|
||||||
|
import org.springframework.expression.spel.ast.Projection;
|
||||||
|
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
|
||||||
|
import org.springframework.expression.spel.ast.QualifiedIdentifier;
|
||||||
|
import org.springframework.expression.spel.ast.Selection;
|
||||||
|
import org.springframework.expression.spel.ast.SpelNodeImpl;
|
||||||
|
import org.springframework.expression.spel.ast.StringLiteral;
|
||||||
|
import org.springframework.expression.spel.ast.Ternary;
|
||||||
|
import org.springframework.expression.spel.ast.TypeReference;
|
||||||
|
import org.springframework.expression.spel.ast.VariableReference;
|
||||||
|
import org.springframework.expression.spel.standard.InternalParseException;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hand written SpEL parser. Instances are reusable but are not thread safe.
|
||||||
|
*
|
||||||
|
* @author Andy Clement
|
||||||
|
* @since 3.0
|
||||||
|
*/
|
||||||
|
public class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
|
|
||||||
|
// The expression being parsed
|
||||||
|
private String expressionString;
|
||||||
|
|
||||||
|
// The token stream constructed from that expression string
|
||||||
|
private List<Token> tokenStream;
|
||||||
|
|
||||||
|
// length of a populated token stream
|
||||||
|
private int tokenStreamLength;
|
||||||
|
|
||||||
|
// Current location in the token stream when processing tokens
|
||||||
|
private int tokenStreamPointer;
|
||||||
|
|
||||||
|
// For rules that build nodes, they are stacked here for return
|
||||||
|
private Stack<SpelNodeImpl> constructedNodes = new Stack<SpelNodeImpl>();
|
||||||
|
|
||||||
|
private int configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a parser.
|
||||||
|
*/
|
||||||
|
public InternalSpelExpressionParser() {
|
||||||
|
this(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a parser with some configured behaviour. Supported configuration
|
||||||
|
* bit flags can be seen in {@link SpelExpressionParserConfiguration}
|
||||||
|
* @param configuration bitflags for configuration options
|
||||||
|
*/
|
||||||
|
public InternalSpelExpressionParser(int configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpelExpression parse(String expressionString) throws ParseException {
|
||||||
|
return doParseExpression(expressionString, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException {
|
||||||
|
try {
|
||||||
|
this.expressionString = expressionString;
|
||||||
|
Tokenizer tokenizer = new Tokenizer(expressionString);
|
||||||
|
tokenizer.process();
|
||||||
|
tokenStream = tokenizer.getTokens();
|
||||||
|
tokenStreamLength = tokenStream.size();
|
||||||
|
tokenStreamPointer = 0;
|
||||||
|
constructedNodes.clear();
|
||||||
|
SpelNodeImpl ast = eatExpression();
|
||||||
|
if (moreTokens()) {
|
||||||
|
throw new SpelParseException(peekToken().startpos,SpelMessage.MORE_INPUT,toString(nextToken()));
|
||||||
|
}
|
||||||
|
assert constructedNodes.isEmpty();
|
||||||
|
return new SpelExpression(expressionString, ast, configuration);
|
||||||
|
} catch (InternalParseException ipe) {
|
||||||
|
throw ipe.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expression
|
||||||
|
// : logicalOrExpression
|
||||||
|
// ( (ASSIGN^ logicalOrExpression)
|
||||||
|
// | (DEFAULT^ logicalOrExpression)
|
||||||
|
// | (QMARK^ expression COLON! expression)
|
||||||
|
// | (ELVIS^ expression))?;
|
||||||
|
private SpelNodeImpl eatExpression() {
|
||||||
|
SpelNodeImpl expr = eatLogicalOrExpression();
|
||||||
|
if (moreTokens()) {
|
||||||
|
Token t = peekToken();
|
||||||
|
if (t.kind==TokenKind.ASSIGN) { // a=b
|
||||||
|
nextToken();
|
||||||
|
SpelNodeImpl assignedValue = eatLogicalOrExpression();
|
||||||
|
return new Assign(toPos(t),expr,assignedValue);
|
||||||
|
} else if (t.kind==TokenKind.ELVIS) { // a?:b (a if it isn't null, otherwise b)
|
||||||
|
nextToken(); // elvis has left the building
|
||||||
|
SpelNodeImpl valueIfNull = eatExpression();
|
||||||
|
return new Elvis(toPos(t),expr,valueIfNull);
|
||||||
|
} else if (t.kind==TokenKind.QMARK) { // a?b:c
|
||||||
|
nextToken();
|
||||||
|
SpelNodeImpl ifTrueExprValue = eatExpression();
|
||||||
|
eatToken(TokenKind.COLON);
|
||||||
|
SpelNodeImpl ifFalseExprValue = eatExpression();
|
||||||
|
return new Ternary(toPos(t),expr,ifTrueExprValue,ifFalseExprValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//logicalOrExpression : logicalAndExpression (OR^ logicalAndExpression)*;
|
||||||
|
private SpelNodeImpl eatLogicalOrExpression() {
|
||||||
|
SpelNodeImpl expr = eatLogicalAndExpression();
|
||||||
|
while (peekIdentifierToken("or")) {
|
||||||
|
Token t = nextToken(); //consume OR
|
||||||
|
SpelNodeImpl rhExpr = eatLogicalAndExpression();
|
||||||
|
checkRightOperand(t,rhExpr);
|
||||||
|
expr = new OpOr(toPos(t),expr,rhExpr);
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// logicalAndExpression : relationalExpression (AND^ relationalExpression)*;
|
||||||
|
private SpelNodeImpl eatLogicalAndExpression() {
|
||||||
|
SpelNodeImpl expr = eatRelationalExpression();
|
||||||
|
while (peekIdentifierToken("and")) {
|
||||||
|
Token t = nextToken();// consume 'AND'
|
||||||
|
SpelNodeImpl rhExpr = eatRelationalExpression();
|
||||||
|
checkRightOperand(t,rhExpr);
|
||||||
|
expr = new OpAnd(toPos(t),expr,rhExpr);
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// relationalExpression : sumExpression (relationalOperator^ sumExpression)?;
|
||||||
|
private SpelNodeImpl eatRelationalExpression() {
|
||||||
|
SpelNodeImpl expr = eatSumExpression();
|
||||||
|
Token relationalOperatorToken = maybeEatRelationalOperator();
|
||||||
|
if (relationalOperatorToken!=null) {
|
||||||
|
Token t = nextToken(); //consume relational operator token
|
||||||
|
SpelNodeImpl rhExpr = eatSumExpression();
|
||||||
|
checkRightOperand(t,rhExpr);
|
||||||
|
TokenKind tk = relationalOperatorToken.kind;
|
||||||
|
if (relationalOperatorToken.isNumericRelationalOperator()) {
|
||||||
|
int pos = toPos(t);
|
||||||
|
if (tk==TokenKind.GT) {
|
||||||
|
return new OpGT(pos,expr,rhExpr);
|
||||||
|
} else if (tk==TokenKind.LT) {
|
||||||
|
return new OpLT(pos,expr,rhExpr);
|
||||||
|
} else if (tk==TokenKind.LE) {
|
||||||
|
return new OpLE(pos,expr,rhExpr);
|
||||||
|
} else if (tk==TokenKind.GE) {
|
||||||
|
return new OpGE(pos,expr,rhExpr);
|
||||||
|
} else if (tk==TokenKind.EQ) {
|
||||||
|
return new OpEQ(pos,expr,rhExpr);
|
||||||
|
} else {
|
||||||
|
assert tk==TokenKind.NE;
|
||||||
|
return new OpNE(pos,expr,rhExpr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tk==TokenKind.INSTANCEOF) {
|
||||||
|
return new OperatorInstanceof(toPos(t),expr,rhExpr);
|
||||||
|
} else if (tk==TokenKind.MATCHES) {
|
||||||
|
return new OperatorMatches(toPos(t),expr,rhExpr);
|
||||||
|
} else {
|
||||||
|
assert tk==TokenKind.BETWEEN;
|
||||||
|
return new org.springframework.expression.spel.ast.OperatorBetween(toPos(t),expr,rhExpr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*;
|
||||||
|
private SpelNodeImpl eatSumExpression() {
|
||||||
|
SpelNodeImpl expr = eatProductExpression();
|
||||||
|
while (peekToken(TokenKind.PLUS,TokenKind.MINUS)) {
|
||||||
|
Token t = nextToken();//consume PLUS or MINUS
|
||||||
|
SpelNodeImpl rhExpr = eatProductExpression();
|
||||||
|
checkRightOperand(t,rhExpr);
|
||||||
|
if (t.kind==TokenKind.PLUS) {
|
||||||
|
expr = new OpPlus(toPos(t),expr,rhExpr);
|
||||||
|
} else {
|
||||||
|
assert t.kind==TokenKind.MINUS;
|
||||||
|
expr = new OpMinus(toPos(t),expr,rhExpr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
|
||||||
|
private SpelNodeImpl eatProductExpression() {
|
||||||
|
SpelNodeImpl expr = eatPowerExpression();
|
||||||
|
while (peekToken(TokenKind.STAR,TokenKind.DIV,TokenKind.MOD)) {
|
||||||
|
Token t = nextToken(); // consume STAR/DIV/MOD
|
||||||
|
SpelNodeImpl rhExpr = eatPowerExpression();
|
||||||
|
checkRightOperand(t,rhExpr);
|
||||||
|
if (t.kind==TokenKind.STAR) {
|
||||||
|
expr = new OpMultiply(toPos(t),expr,rhExpr);
|
||||||
|
} else if (t.kind==TokenKind.DIV) {
|
||||||
|
expr = new OpDivide(toPos(t),expr,rhExpr);
|
||||||
|
} else {
|
||||||
|
assert t.kind==TokenKind.MOD;
|
||||||
|
expr = new OpModulus(toPos(t),expr,rhExpr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// powerExpr : unaryExpression (POWER^ unaryExpression)? ;
|
||||||
|
private SpelNodeImpl eatPowerExpression() {
|
||||||
|
SpelNodeImpl expr = eatUnaryExpression();
|
||||||
|
if (peekToken(TokenKind.POWER)) {
|
||||||
|
Token t = nextToken();//consume POWER
|
||||||
|
SpelNodeImpl rhExpr = eatUnaryExpression();
|
||||||
|
checkRightOperand(t,rhExpr);
|
||||||
|
return new OperatorPower(toPos(t),expr, rhExpr);
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unaryExpression: (PLUS^ | MINUS^ | BANG^) unaryExpression | primaryExpression ;
|
||||||
|
private SpelNodeImpl eatUnaryExpression() {
|
||||||
|
if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.NOT)) {
|
||||||
|
Token t = nextToken();
|
||||||
|
SpelNodeImpl expr = eatUnaryExpression();
|
||||||
|
if (t.kind==TokenKind.NOT) {
|
||||||
|
return new OperatorNot(toPos(t),expr);
|
||||||
|
} else if (t.kind==TokenKind.PLUS) {
|
||||||
|
return new OpPlus(toPos(t),expr);
|
||||||
|
} else {
|
||||||
|
assert t.kind==TokenKind.MINUS;
|
||||||
|
return new OpMinus(toPos(t),expr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return eatPrimaryExpression();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// primaryExpression : startNode (node)? -> ^(EXPRESSION startNode (node)?);
|
||||||
|
private SpelNodeImpl eatPrimaryExpression() {
|
||||||
|
List<SpelNodeImpl> nodes = new ArrayList<SpelNodeImpl>();
|
||||||
|
SpelNodeImpl start = eatStartNode(); // always a start node
|
||||||
|
nodes.add(start);
|
||||||
|
while (maybeEatNode()) {
|
||||||
|
nodes.add(pop());
|
||||||
|
}
|
||||||
|
if (nodes.size()==1) {
|
||||||
|
return nodes.get(0);
|
||||||
|
} else {
|
||||||
|
return new CompoundExpression(toPos(start.getStartPosition(),nodes.get(nodes.size()-1).getEndPosition()),nodes.toArray(new SpelNodeImpl[nodes.size()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// node : ((DOT dottedNode) | (SAFE_NAVI dottedNode) | nonDottedNode)+;
|
||||||
|
private boolean maybeEatNode() {
|
||||||
|
SpelNodeImpl expr = null;
|
||||||
|
if (peekToken(TokenKind.DOT,TokenKind.SAFE_NAVI)) {
|
||||||
|
expr = eatDottedNode();
|
||||||
|
} else {
|
||||||
|
expr = maybeEatNonDottedNode();
|
||||||
|
}
|
||||||
|
if (expr==null) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
push(expr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nonDottedNode: indexer;
|
||||||
|
private SpelNodeImpl maybeEatNonDottedNode() {
|
||||||
|
if (peekToken(TokenKind.LSQUARE)) {
|
||||||
|
if (maybeEatIndexer()) {
|
||||||
|
return pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//dottedNode
|
||||||
|
// : ((methodOrProperty
|
||||||
|
// | functionOrVar
|
||||||
|
// | projection
|
||||||
|
// | selection
|
||||||
|
// | firstSelection
|
||||||
|
// | lastSelection
|
||||||
|
// ))
|
||||||
|
// ;
|
||||||
|
private SpelNodeImpl eatDottedNode() {
|
||||||
|
Token t = nextToken();// it was a '.' or a '?.'
|
||||||
|
boolean nullSafeNavigation = t.kind==TokenKind.SAFE_NAVI;
|
||||||
|
if (maybeEatMethodOrProperty(nullSafeNavigation) || maybeEatFunctionOrVar() || maybeEatProjection(nullSafeNavigation) || maybeEatSelection(nullSafeNavigation)) {
|
||||||
|
return pop();
|
||||||
|
}
|
||||||
|
raiseInternalException(t.startpos,SpelMessage.UNEXPECTED_DATA_AFTER_DOT,toString(peekToken()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// functionOrVar
|
||||||
|
// : (POUND ID LPAREN) => function
|
||||||
|
// | var
|
||||||
|
//
|
||||||
|
// function : POUND id=ID methodArgs -> ^(FUNCTIONREF[$id] methodArgs);
|
||||||
|
// var : POUND id=ID -> ^(VARIABLEREF[$id]);
|
||||||
|
private boolean maybeEatFunctionOrVar() {
|
||||||
|
if (!peekToken(TokenKind.HASH)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Token t = nextToken();
|
||||||
|
Token functionOrVariableName = eatToken(TokenKind.IDENTIFIER);
|
||||||
|
SpelNodeImpl[] args = maybeEatMethodArgs();
|
||||||
|
if (args==null) {
|
||||||
|
push(new VariableReference(functionOrVariableName.data,toPos(t.startpos,functionOrVariableName.endpos)));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
push(new FunctionReference(functionOrVariableName.data,toPos(t.startpos,functionOrVariableName.endpos),args));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!;
|
||||||
|
private SpelNodeImpl[] maybeEatMethodArgs() {
|
||||||
|
if (!peekToken(TokenKind.LPAREN)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<SpelNodeImpl> args = new ArrayList<SpelNodeImpl>();
|
||||||
|
consumeArguments(args);
|
||||||
|
eatToken(TokenKind.RPAREN);
|
||||||
|
return args.toArray(new SpelNodeImpl[args.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void eatConstructorArgs(List<SpelNodeImpl> accumulatedArguments) {
|
||||||
|
if (!peekToken(TokenKind.LPAREN)) {
|
||||||
|
throw new InternalParseException(new SpelParseException(expressionString,positionOf(peekToken()),SpelMessage.MISSING_CONSTRUCTOR_ARGS));
|
||||||
|
}
|
||||||
|
consumeArguments(accumulatedArguments);
|
||||||
|
eatToken(TokenKind.RPAREN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for consuming arguments for either a method or a constructor call
|
||||||
|
*/
|
||||||
|
private void consumeArguments(List<SpelNodeImpl> accumulatedArguments) {
|
||||||
|
int pos = peekToken().startpos;
|
||||||
|
Token next = null;
|
||||||
|
do {
|
||||||
|
nextToken();// consume ( (first time through) or comma (subsequent times)
|
||||||
|
Token t = peekToken();
|
||||||
|
if (t==null) {
|
||||||
|
raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS);
|
||||||
|
}
|
||||||
|
if (t.kind!=TokenKind.RPAREN) {
|
||||||
|
accumulatedArguments.add(eatExpression());
|
||||||
|
}
|
||||||
|
next = peekToken();
|
||||||
|
} while (next!=null && next.kind==TokenKind.COMMA);
|
||||||
|
if (next==null) {
|
||||||
|
raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int positionOf(Token t) {
|
||||||
|
if (t==null) {
|
||||||
|
// if null assume the problem is because the right token was
|
||||||
|
// not found at the end of the expression
|
||||||
|
return expressionString.length();
|
||||||
|
} else {
|
||||||
|
return t.startpos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//startNode
|
||||||
|
// : parenExpr | literal
|
||||||
|
// | type
|
||||||
|
// | methodOrProperty
|
||||||
|
// | functionOrVar
|
||||||
|
// | projection
|
||||||
|
// | selection
|
||||||
|
// | firstSelection
|
||||||
|
// | lastSelection
|
||||||
|
// | indexer
|
||||||
|
// | constructor
|
||||||
|
private SpelNodeImpl eatStartNode() {
|
||||||
|
if (maybeEatLiteral()) {
|
||||||
|
return pop();
|
||||||
|
} else if (maybeEatParenExpression()) {
|
||||||
|
return pop();
|
||||||
|
} else if (maybeEatTypeReference() || maybeEatNullReference() || maybeEatConstructorReference() || maybeEatMethodOrProperty(false) || maybeEatFunctionOrVar()) {
|
||||||
|
return pop();
|
||||||
|
} else if (maybeEatProjection(false) || maybeEatSelection(false) || maybeEatIndexer()) {
|
||||||
|
return pop();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private boolean maybeEatTypeReference() {
|
||||||
|
if (peekToken(TokenKind.IDENTIFIER)) {
|
||||||
|
Token typeName = peekToken();
|
||||||
|
if (!typeName.stringValue().equals("T")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nextToken();
|
||||||
|
eatToken(TokenKind.LPAREN);
|
||||||
|
SpelNodeImpl node = eatPossiblyQualifiedId(true);
|
||||||
|
// dotted qualified id
|
||||||
|
eatToken(TokenKind.RPAREN);
|
||||||
|
constructedNodes.push(new TypeReference(toPos(typeName),node));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean maybeEatNullReference() {
|
||||||
|
if (peekToken(TokenKind.IDENTIFIER)) {
|
||||||
|
Token nullToken = peekToken();
|
||||||
|
if (!nullToken.stringValue().equals("null")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nextToken();
|
||||||
|
constructedNodes.push(new NullLiteral(toPos(nullToken)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//projection: PROJECT^ expression RCURLY!;
|
||||||
|
private boolean maybeEatProjection(boolean nullSafeNavigation) {
|
||||||
|
Token t = peekToken();
|
||||||
|
if (!peekToken(TokenKind.PROJECT,true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SpelNodeImpl expr = eatExpression();
|
||||||
|
eatToken(TokenKind.RSQUARE);
|
||||||
|
constructedNodes.push(new Projection(nullSafeNavigation, toPos(t), expr));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean maybeEatIndexer() {
|
||||||
|
Token t = peekToken();
|
||||||
|
if (!peekToken(TokenKind.LSQUARE,true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SpelNodeImpl expr = eatExpression();
|
||||||
|
eatToken(TokenKind.RSQUARE);
|
||||||
|
constructedNodes.push(new Indexer(toPos(t),expr));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean maybeEatSelection(boolean nullSafeNavigation) {
|
||||||
|
Token t = peekToken();
|
||||||
|
if (!peekSelectToken()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nextToken();
|
||||||
|
SpelNodeImpl expr = eatExpression();
|
||||||
|
eatToken(TokenKind.RSQUARE);
|
||||||
|
if (t.kind==TokenKind.SELECT_FIRST) {
|
||||||
|
constructedNodes.push(new Selection(nullSafeNavigation,Selection.FIRST,toPos(t),expr));
|
||||||
|
} else if (t.kind==TokenKind.SELECT_LAST) {
|
||||||
|
constructedNodes.push(new Selection(nullSafeNavigation,Selection.LAST,toPos(t),expr));
|
||||||
|
} else {
|
||||||
|
constructedNodes.push(new Selection(nullSafeNavigation,Selection.ALL,toPos(t),expr));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eat an identifier, possibly qualified (meaning that it is dotted). If the dollarAllowed parameter is true then
|
||||||
|
* it will process any dollar characters found between names, and this allows it to support inner type references
|
||||||
|
* correctly. For example 'com.foo.bar.Outer$Inner' will produce the identifier sequence com, foo, bar, Outer, $Inner,
|
||||||
|
* note that the $ has been prefixed onto the Inner identifier. The code in TypeReference which reforms this into
|
||||||
|
* a typename copes with the $ prefixed identifiers.
|
||||||
|
* TODO AndyC Could create complete identifiers (a.b.c) here rather than a sequence of them? (a, b, c)
|
||||||
|
*/
|
||||||
|
private SpelNodeImpl eatPossiblyQualifiedId(boolean dollarAllowed) {
|
||||||
|
List<SpelNodeImpl> qualifiedIdPieces = new ArrayList<SpelNodeImpl>();
|
||||||
|
Token startnode = eatToken(TokenKind.IDENTIFIER);
|
||||||
|
qualifiedIdPieces.add(new Identifier(startnode.stringValue(),toPos(startnode)));
|
||||||
|
boolean dollar = false;
|
||||||
|
while (peekToken(TokenKind.DOT,true) || (dollarAllowed && (dollar = peekToken(TokenKind.DOLLAR,true)))) {
|
||||||
|
Token node = eatToken(TokenKind.IDENTIFIER);
|
||||||
|
if (dollar) {
|
||||||
|
qualifiedIdPieces.add(new Identifier("$"+node.stringValue(),((node.startpos-1)<<16)+node.endpos));
|
||||||
|
} else {
|
||||||
|
qualifiedIdPieces.add(new Identifier(node.stringValue(),toPos(node)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new QualifiedIdentifier(toPos(startnode.startpos,qualifiedIdPieces.get(qualifiedIdPieces.size()-1).getEndPosition()),qualifiedIdPieces.toArray(new SpelNodeImpl[qualifiedIdPieces.size()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean maybeEatMethodOrProperty(boolean nullSafeNavigation) {
|
||||||
|
if (peekToken(TokenKind.IDENTIFIER)) {
|
||||||
|
Token methodOrPropertyName = nextToken();
|
||||||
|
SpelNodeImpl[] args = maybeEatMethodArgs();
|
||||||
|
if (args==null) {
|
||||||
|
// property
|
||||||
|
push(new PropertyOrFieldReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName)));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// methodreference
|
||||||
|
push(new MethodReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName),args));
|
||||||
|
// TODO what is the end position for a method reference? the name or the last arg?
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//constructor
|
||||||
|
//: ('new' qualifiedId LPAREN) => 'new' qualifiedId ctorArgs -> ^(CONSTRUCTOR qualifiedId ctorArgs)
|
||||||
|
private boolean maybeEatConstructorReference() {
|
||||||
|
if (peekIdentifierToken("new")) {
|
||||||
|
Token newToken = nextToken();
|
||||||
|
SpelNodeImpl possiblyQualifiedConstructorName = eatPossiblyQualifiedId(true);
|
||||||
|
List<SpelNodeImpl> nodes = new ArrayList<SpelNodeImpl>();
|
||||||
|
nodes.add(possiblyQualifiedConstructorName);
|
||||||
|
eatConstructorArgs(nodes);
|
||||||
|
push(new ConstructorReference(toPos(newToken),nodes.toArray(new SpelNodeImpl[nodes.size()]))); // TODO correct end position?
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void push(SpelNodeImpl newNode) {
|
||||||
|
constructedNodes.push(newNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpelNodeImpl pop() {
|
||||||
|
return constructedNodes.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// literal
|
||||||
|
// : INTEGER_LITERAL
|
||||||
|
// | boolLiteral
|
||||||
|
// | STRING_LITERAL
|
||||||
|
// | HEXADECIMAL_INTEGER_LITERAL
|
||||||
|
// | REAL_LITERAL
|
||||||
|
// | DQ_STRING_LITERAL
|
||||||
|
// | NULL_LITERAL
|
||||||
|
private boolean maybeEatLiteral() {
|
||||||
|
Token t = peekToken();
|
||||||
|
if (t==null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (t.kind==TokenKind.LITERAL_INT) {
|
||||||
|
push(Literal.getIntLiteral(t.data, toPos(t), 10));
|
||||||
|
} else if (t.kind==TokenKind.LITERAL_LONG) {
|
||||||
|
push(Literal.getLongLiteral(t.data, toPos(t), 10));
|
||||||
|
} else if (t.kind==TokenKind.LITERAL_HEXINT) {
|
||||||
|
push(Literal.getIntLiteral(t.data, toPos(t), 16));
|
||||||
|
} else if (t.kind==TokenKind.LITERAL_HEXLONG) {
|
||||||
|
push(Literal.getLongLiteral(t.data, toPos(t), 16));
|
||||||
|
} else if (t.kind==TokenKind.LITERAL_REAL) {
|
||||||
|
push(Literal.getRealLiteral(t.data, toPos(t),false));
|
||||||
|
} else if (t.kind==TokenKind.LITERAL_REAL_FLOAT) {
|
||||||
|
push(Literal.getRealLiteral(t.data, toPos(t), true));
|
||||||
|
} else if (peekIdentifierToken("true")) {
|
||||||
|
push(new BooleanLiteral(t.data,toPos(t),true));
|
||||||
|
} else if (peekIdentifierToken("false")) {
|
||||||
|
push(new BooleanLiteral(t.data,toPos(t),false));
|
||||||
|
} else if (t.kind==TokenKind.LITERAL_STRING) {
|
||||||
|
push(new StringLiteral(t.data,toPos(t),t.data));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nextToken();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//parenExpr : LPAREN! expression RPAREN!;
|
||||||
|
private boolean maybeEatParenExpression() {
|
||||||
|
if (peekToken(TokenKind.LPAREN)) {
|
||||||
|
nextToken();
|
||||||
|
SpelNodeImpl expr = eatExpression();
|
||||||
|
eatToken(TokenKind.RPAREN);
|
||||||
|
push(expr);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// relationalOperator
|
||||||
|
// : EQUAL | NOT_EQUAL | LESS_THAN | LESS_THAN_OR_EQUAL | GREATER_THAN
|
||||||
|
// | GREATER_THAN_OR_EQUAL | INSTANCEOF | BETWEEN | MATCHES
|
||||||
|
private Token maybeEatRelationalOperator() {
|
||||||
|
Token t = peekToken();
|
||||||
|
if (t==null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (t.isNumericRelationalOperator()) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
if (t.isIdentifier()) {
|
||||||
|
String idString = t.stringValue();
|
||||||
|
if (idString.equalsIgnoreCase("instanceof")) {
|
||||||
|
return t.asInstanceOfToken();
|
||||||
|
} else if (idString.equalsIgnoreCase("matches")) {
|
||||||
|
return t.asMatchesToken();
|
||||||
|
} else if (idString.equalsIgnoreCase("between")) {
|
||||||
|
return t.asBetweenToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token eatToken(TokenKind expectedKind) {
|
||||||
|
assert moreTokens();
|
||||||
|
Token t = nextToken();
|
||||||
|
if (t==null) {
|
||||||
|
raiseInternalException( expressionString.length(), SpelMessage.OOD);
|
||||||
|
}
|
||||||
|
if (t.kind!=expectedKind) {
|
||||||
|
raiseInternalException(t.startpos,SpelMessage.NOT_EXPECTED_TOKEN, expectedKind.toString().toLowerCase(),t.getKind().toString().toLowerCase());
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean peekToken(TokenKind desiredTokenKind) {
|
||||||
|
return peekToken(desiredTokenKind,false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean peekToken(TokenKind desiredTokenKind, boolean consumeIfMatched) {
|
||||||
|
if (!moreTokens()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Token t = peekToken();
|
||||||
|
if (t.kind==desiredTokenKind) {
|
||||||
|
if (consumeIfMatched) {
|
||||||
|
tokenStreamPointer++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean peekToken(TokenKind possible1,TokenKind possible2) {
|
||||||
|
if (!moreTokens()) return false;
|
||||||
|
Token t = peekToken();
|
||||||
|
return t.kind==possible1 || t.kind==possible2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean peekToken(TokenKind possible1,TokenKind possible2, TokenKind possible3) {
|
||||||
|
if (!moreTokens()) return false;
|
||||||
|
Token t = peekToken();
|
||||||
|
return t.kind==possible1 || t.kind==possible2 || t.kind==possible3;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean peekIdentifierToken(String identifierString) {
|
||||||
|
if (!moreTokens()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Token t = peekToken();
|
||||||
|
return t.kind==TokenKind.IDENTIFIER && t.stringValue().equalsIgnoreCase(identifierString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean peekSelectToken() {
|
||||||
|
if (!moreTokens()) return false;
|
||||||
|
Token t = peekToken();
|
||||||
|
return t.kind==TokenKind.SELECT || t.kind==TokenKind.SELECT_FIRST || t.kind==TokenKind.SELECT_LAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean moreTokens() {
|
||||||
|
return tokenStreamPointer<tokenStream.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token nextToken() {
|
||||||
|
if (tokenStreamPointer>=tokenStreamLength) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return tokenStream.get(tokenStreamPointer++);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token peekToken() {
|
||||||
|
if (tokenStreamPointer>=tokenStreamLength) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return tokenStream.get(tokenStreamPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void raiseInternalException(int pos, SpelMessage message,Object... inserts) {
|
||||||
|
throw new InternalParseException(new SpelParseException(expressionString,pos,message,inserts));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(Token t) {
|
||||||
|
if (t.getKind().hasPayload()) {
|
||||||
|
return t.stringValue();
|
||||||
|
} else {
|
||||||
|
return t.kind.toString().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkRightOperand(Token token, SpelNodeImpl operandExpression) {
|
||||||
|
if (operandExpression==null) {
|
||||||
|
raiseInternalException(token.startpos,SpelMessage.RIGHT_OPERAND_PROBLEM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress the start and end of a token into a single int
|
||||||
|
*/
|
||||||
|
private int toPos(Token t) {
|
||||||
|
return (t.startpos<<16)+t.endpos;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int toPos(int start,int end) {
|
||||||
|
return (start<<16)+end;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.expression.spel.standard;
|
package org.springframework.expression.spel.standard.internal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holder for a kind of token, the associated data and its position in the input data stream (start/end).
|
* Holder for a kind of token, the associated data and its position in the input data stream (start/end).
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.expression.spel.standard;
|
package org.springframework.expression.spel.standard.internal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.expression.spel.standard;
|
package org.springframework.expression.spel.standard.internal;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -21,6 +21,7 @@ import java.util.List;
|
||||||
|
|
||||||
import org.springframework.expression.spel.SpelMessage;
|
import org.springframework.expression.spel.SpelMessage;
|
||||||
import org.springframework.expression.spel.SpelParseException;
|
import org.springframework.expression.spel.SpelParseException;
|
||||||
|
import org.springframework.expression.spel.standard.InternalParseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lex some input data into a stream of tokens that can then be parsed.
|
* Lex some input data into a stream of tokens that can then be parsed.
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.expression.spel.standard;
|
package org.springframework.expression.spel.standard.internal;
|
||||||
import junit.framework.Assert;
|
import junit.framework.Assert;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
@ -27,6 +27,7 @@ import org.springframework.expression.spel.SpelNode;
|
||||||
import org.springframework.expression.spel.SpelParseException;
|
import org.springframework.expression.spel.SpelParseException;
|
||||||
import org.springframework.expression.spel.ast.OpAnd;
|
import org.springframework.expression.spel.ast.OpAnd;
|
||||||
import org.springframework.expression.spel.ast.OpOr;
|
import org.springframework.expression.spel.ast.OpOr;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue