diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java index 84c3c273500..a62477f611b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java @@ -169,7 +169,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { //logicalOrExpression : logicalAndExpression (OR^ logicalAndExpression)*; private SpelNodeImpl eatLogicalOrExpression() { SpelNodeImpl expr = eatLogicalAndExpression(); - while (peekIdentifierToken("or")) { + while (peekIdentifierToken("or") || peekToken(TokenKind.SYMBOLIC_OR)) { Token t = nextToken(); //consume OR SpelNodeImpl rhExpr = eatLogicalAndExpression(); checkRightOperand(t,rhExpr); @@ -181,7 +181,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { // logicalAndExpression : relationalExpression (AND^ relationalExpression)*; private SpelNodeImpl eatLogicalAndExpression() { SpelNodeImpl expr = eatRelationalExpression(); - while (peekIdentifierToken("and")) { + while (peekIdentifierToken("and") || peekToken(TokenKind.SYMBOLIC_AND)) { Token t = nextToken();// consume 'AND' SpelNodeImpl rhExpr = eatRelationalExpression(); checkRightOperand(t,rhExpr); @@ -432,7 +432,6 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } } - //startNode // : parenExpr | literal // | type @@ -513,7 +512,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { private boolean maybeEatNullReference() { if (peekToken(TokenKind.IDENTIFIER)) { Token nullToken = peekToken(); - if (!nullToken.stringValue().toLowerCase().equals("null")) { + if (!nullToken.stringValue().equalsIgnoreCase("null")) { return false; } nextToken(); @@ -805,7 +804,6 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return t.kind==TokenKind.SELECT || t.kind==TokenKind.SELECT_FIRST || t.kind==TokenKind.SELECT_LAST; } - private boolean moreTokens() { return tokenStreamPointer="), GT(">"), LE("<="), LT("<"), EQ("=="), NE("!="), MOD("%"), NOT("!"), ASSIGN("="), INSTANCEOF("instanceof"), MATCHES("matches"), BETWEEN("between"), SELECT("?["), POWER("^"), - ELVIS("?:"), SAFE_NAVI("?."), BEAN_REF("@") + ELVIS("?:"), SAFE_NAVI("?."), BEAN_REF("@"), SYMBOLIC_OR("||"), SYMBOLIC_AND("&&") ; char[] tokenChars; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java index d064a0fb1c8..5471e4c5fc1 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java @@ -38,7 +38,7 @@ class Tokenizer { int pos; int max; List tokens = new ArrayList(); - + public Tokenizer(String inputdata) { this.expressionString = inputdata; this.toProcess = (inputdata+"\0").toCharArray(); @@ -46,7 +46,7 @@ class Tokenizer { this.pos = 0; process(); } - + public void process() { while (pos getTokens() { return tokens; } - - + // STRING_LITERAL: '\''! (APOS|~'\'')* '\''!; private void lexQuotedStringLiteral() { int start = pos; @@ -223,7 +232,7 @@ class Tokenizer { pos++; tokens.add(new Token(TokenKind.LITERAL_STRING, subarray(start,pos), start, pos)); } - + // DQ_STRING_LITERAL: '"'! (~'"')* '"'!; private void lexDoubleQuotedStringLiteral() { int start = pos; @@ -241,8 +250,7 @@ class Tokenizer { pos++; tokens.add(new Token(TokenKind.LITERAL_STRING, subarray(start,pos), start, pos)); } - - + // REAL_LITERAL : // ('.' (DECIMAL_DIGIT)+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?) | // ((DECIMAL_DIGIT)+ '.' (DECIMAL_DIGIT)+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?) | @@ -256,7 +264,7 @@ class Tokenizer { // fragment REAL_TYPE_SUFFIX : 'F' | 'f' | 'D' | 'd'; // INTEGER_LITERAL // : (DECIMAL_DIGIT)+ (INTEGER_TYPE_SUFFIX)?; - + private void lexNumericLiteral(boolean firstCharIsZero) { boolean isReal = false; int start = pos; @@ -353,10 +361,10 @@ class Tokenizer { } } } - + // if this is changed, it must remain sorted private static final String[] alternativeOperatorNames = { "DIV","EQ","GE","GT","LE","LT","MOD","NE","NOT"}; - + private void lexIdentifier() { int start = pos; do { @@ -375,7 +383,7 @@ class Tokenizer { } tokens.add(new Token(TokenKind.IDENTIFIER,subarray,start,pos)); } - + private void pushIntToken(char[] data,boolean isLong, int start, int end) { if (isLong) { tokens.add(new Token(TokenKind.LITERAL_LONG,data, start, end)); @@ -398,7 +406,7 @@ class Tokenizer { tokens.add(new Token(TokenKind.LITERAL_HEXINT, data, start, end)); } } - + private void pushRealToken(char[] data, boolean isFloat, int start, int end) { if (isFloat) { tokens.add(new Token(TokenKind.LITERAL_REAL_FLOAT, data, start, end)); @@ -406,13 +414,13 @@ class Tokenizer { tokens.add(new Token(TokenKind.LITERAL_REAL, data, start, end)); } } - + private char[] subarray(int start, int end) { char[] result = new char[end - start]; System.arraycopy(toProcess, start, result, 0, end - start); return result; } - + /** * Check if this might be a two character token. */ @@ -421,7 +429,7 @@ class Tokenizer { Assert.isTrue(toProcess[pos] == kind.tokenChars[0]); return toProcess[pos+1] == kind.tokenChars[1]; } - + /** * Push a token of just one character in length. */ @@ -429,7 +437,7 @@ class Tokenizer { tokens.add(new Token(kind,pos,pos+1)); pos++; } - + /** * Push a token of two characters in length. */ @@ -437,7 +445,7 @@ class Tokenizer { tokens.add(new Token(kind,pos,pos+2)); pos+=2; } - + private void pushOneCharOrTwoCharToken(TokenKind kind, int pos, char[] data) { tokens.add(new Token(kind,data,pos,pos+kind.getLength())); } @@ -446,7 +454,7 @@ class Tokenizer { private boolean isIdentifier(char ch) { return isAlphabetic(ch) || isDigit(ch) || ch=='_' || ch=='$'; } - + private boolean isChar(char a,char b) { char ch = toProcess[pos]; return ch==a || ch==b; @@ -467,7 +475,7 @@ class Tokenizer { private boolean isSign(char ch) { return ch=='+' || ch=='-'; } - + private boolean isDigit(char ch) { if (ch>255) { return false; @@ -481,14 +489,14 @@ class Tokenizer { } return (flags[ch] & IS_ALPHA)!=0; } - + private boolean isHexadecimalDigit(char ch) { if (ch>255) { return false; } return (flags[ch] & IS_HEXDIGIT)!=0; } - + private static final byte flags[] = new byte[256]; private static final byte IS_DIGIT=0x01; private static final byte IS_HEXDIGIT=0x02; diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java index 098b868e967..3ee78dbd819 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java @@ -70,7 +70,7 @@ public class SpelParserTests { assertEquals(5, expr.getValue()); expr = parser.parseRaw("2 + 3"); assertEquals(5, expr.getValue()); - expr = parser.parseRaw("2\n+ 3"); + expr = parser.parseRaw("2\n+\t3"); assertEquals(5, expr.getValue()); expr = parser.parseRaw("2\r\n+\t3"); assertEquals(5, expr.getValue()); @@ -229,6 +229,24 @@ public class SpelParserTests { assertEquals(Boolean.FALSE, expr.getValue(Boolean.class)); } + @Test + public void booleanOperators_symbolic_spr9614() throws EvaluationException, ParseException { + SpelExpression expr = new SpelExpressionParser().parseRaw("true"); + assertEquals(Boolean.TRUE, expr.getValue(Boolean.class)); + expr = new SpelExpressionParser().parseRaw("false"); + assertEquals(Boolean.FALSE, expr.getValue(Boolean.class)); + expr = new SpelExpressionParser().parseRaw("false && false"); + assertEquals(Boolean.FALSE, expr.getValue(Boolean.class)); + expr = new SpelExpressionParser().parseRaw("true && (true || false)"); + assertEquals(Boolean.TRUE, expr.getValue(Boolean.class)); + expr = new SpelExpressionParser().parseRaw("true && true || false"); + assertEquals(Boolean.TRUE, expr.getValue(Boolean.class)); + expr = new SpelExpressionParser().parseRaw("!true"); + assertEquals(Boolean.FALSE, expr.getValue(Boolean.class)); + expr = new SpelExpressionParser().parseRaw("!(false || true)"); + assertEquals(Boolean.FALSE, expr.getValue(Boolean.class)); + } + @Test public void stringLiterals() throws EvaluationException, ParseException { SpelExpression expr = new SpelExpressionParser().parseRaw("'howdy'"); diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index bcb7659bd66..69c7f0decc9 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -8,7 +8,8 @@ Changes in version 3.2 M2 (2012-08-xx) * spring-test module now depends on junit:junit-dep (SPR-6966) * now inferring return type of generic factory methods (SPR-9493) -* SpEL Tokenizer now supports methods on integers (SPR-9612) +* SpEL now supports method invocations on integers (SPR-9612) +* SpEL now supports symbolic boolean operators for OR and AND (SPR-9614) * introduced support for case-insensitive null literals in SpEL expressions (SPR-9613) * now using BufferedInputStream in SimpleMetaDataReader to double performance (SPR-9528) * introduced "repeatCount" property in Quartz SimpleTriggerFactoryBean (SPR-9521)