diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpressionParserFactory.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpressionParserFactory.java index f11e6e4bfbe..1f31a333947 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpressionParserFactory.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpressionParserFactory.java @@ -28,4 +28,12 @@ public class SpelExpressionParserFactory { 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); + } + } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java index 786c6cc6815..edf13a3628e 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java @@ -15,750 +15,46 @@ */ package org.springframework.expression.spel.standard; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; - +import org.springframework.expression.Expression; 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.internal.InternalSpelExpressionParser; /** - * Hand written SpEL parser. Instances are reusable. + * SpEL parser. Instances are reusable and thread safe. * * @author Andy Clement * @since 3.0 */ public class SpelExpressionParser extends TemplateAwareExpressionParser { - // The expression being parsed - private String expressionString; - - // The token stream constructed from that expression string - private List 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 constructedNodes = new Stack(); - private int configuration; - - public SpelExpressionParser() { - this(0); - } - + /** * 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 */ public SpelExpressionParser(int 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 { - return doParseExpression(expressionString, null); - } - - 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 nodes = new ArrayList(); - 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 args = new ArrayList(); - consumeArguments(args); - eatToken(TokenKind.RPAREN); - return args.toArray(new SpelNodeImpl[args.size()]); - } - - private void eatConstructorArgs(List 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 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 qualifiedIdPieces = new ArrayList(); - 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 nodes = new ArrayList(); - 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=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; + return new InternalSpelExpressionParser(configuration).parse(expressionString); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParserConfiguration.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParserConfiguration.java index 9589a25ef06..5b18b9278be 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParserConfiguration.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParserConfiguration.java @@ -31,6 +31,10 @@ public interface SpelExpressionParserConfiguration { */ 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; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/InternalSpelExpressionParser.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/InternalSpelExpressionParser.java new file mode 100644 index 00000000000..3e2cedcaabf --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/InternalSpelExpressionParser.java @@ -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 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 constructedNodes = new Stack(); + + 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 nodes = new ArrayList(); + 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 args = new ArrayList(); + consumeArguments(args); + eatToken(TokenKind.RPAREN); + return args.toArray(new SpelNodeImpl[args.size()]); + } + + private void eatConstructorArgs(List 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 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 qualifiedIdPieces = new ArrayList(); + 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 nodes = new ArrayList(); + 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=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; + } + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/Token.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/Token.java similarity index 93% rename from org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/Token.java rename to org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/Token.java index 09cd24ee92c..5925c4ef833 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/Token.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/Token.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * 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). diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/TokenKind.java similarity index 93% rename from org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java rename to org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/TokenKind.java index b386fbdd351..ff2b02a06ed 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/TokenKind.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.expression.spel.standard; + package org.springframework.expression.spel.standard.internal; /** * @author Andy Clement diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/Tokenizer.java similarity index 95% rename from org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java rename to org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/Tokenizer.java index 229c3998720..24eaa10e2bf 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/internal/Tokenizer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.expression.spel.standard; + package org.springframework.expression.spel.standard.internal; import java.util.ArrayList; import java.util.Arrays; @@ -21,6 +21,7 @@ import java.util.List; import org.springframework.expression.spel.SpelMessage; 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. diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/standard/internal/SpelParserTests.java similarity index 96% rename from org.springframework.expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java rename to org.springframework.expression/src/test/java/org/springframework/expression/spel/standard/internal/SpelParserTests.java index 12a5c9ef9d3..c19c862d398 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/standard/internal/SpelParserTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.expression.spel.standard; + package org.springframework.expression.spel.standard.internal; import junit.framework.Assert; 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.ast.OpAnd; import org.springframework.expression.spel.ast.OpOr; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext;