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 0747c0566cf..c6ddb9ffa6d 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 @@ -267,10 +267,10 @@ public class SpelExpressionParser extends TemplateAwareExpressionParser { // unaryExpression: (PLUS^ | MINUS^ | BANG^) unaryExpression | primaryExpression ; private SpelNodeImpl eatUnaryExpression() { - if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.BANG)) { + if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.NOT)) { Token t = nextToken(); SpelNodeImpl expr = eatUnaryExpression(); - if (t.kind==TokenKind.BANG) { + if (t.kind==TokenKind.NOT) { return new OperatorNot(toPos(t),expr); } else if (t.kind==TokenKind.PLUS) { return new OpPlus(toPos(t),expr); 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/TokenKind.java index fd73c571e74..b386fbdd351 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/TokenKind.java @@ -24,7 +24,7 @@ enum TokenKind { LITERAL_INT, LITERAL_LONG, LITERAL_HEXINT, LITERAL_HEXLONG, LITERAL_STRING, LITERAL_REAL, LITERAL_REAL_FLOAT, LPAREN("("), RPAREN(")"), COMMA(","), IDENTIFIER, COLON(":"),HASH("#"),RSQUARE("]"), LSQUARE("["), - DOT("."), PLUS("+"), STAR("*"), DIV("/"), BANG("!"), MINUS("-"), SELECT_FIRST("^["), SELECT_LAST("$["), QMARK("?"), PROJECT("!["), + DOT("."), PLUS("+"), STAR("*"), DIV("/"), NOT("!"), MINUS("-"), SELECT_FIRST("^["), SELECT_LAST("$["), QMARK("?"), PROJECT("!["), GE(">="),GT(">"),LE("<="),LT("<"),EQ("=="),NE("!="),ASSIGN("="), INSTANCEOF("instanceof"), MATCHES("matches"), BETWEEN("between"), SELECT("?["), MOD("%"), POWER("^"), DOLLAR("$"), ELVIS("?:"), SAFE_NAVI("?."); @@ -49,4 +49,8 @@ enum TokenKind { public boolean hasPayload() { return hasPayload; } + + public int getLength() { + return tokenChars.length; + } } \ No newline at end of file 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/Tokenizer.java index 6774e0f2dc3..229c3998720 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/Tokenizer.java @@ -16,6 +16,7 @@ package org.springframework.expression.spel.standard; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.springframework.expression.spel.SpelMessage; @@ -102,7 +103,7 @@ public class Tokenizer { } else if (isTwoCharToken(TokenKind.PROJECT)) { pushPairToken(TokenKind.PROJECT); } else { - pushCharToken(TokenKind.BANG); + pushCharToken(TokenKind.NOT); } break; case '=': @@ -329,13 +330,26 @@ public 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 { pos++; } while (isIdentifier(toProcess[pos])); - tokens.add(new Token(TokenKind.IDENTIFIER,subarray(start,pos),start,pos)); + char[] subarray = subarray(start,pos); + + // Check if this is the alternative (textual) representation of an operator (see alternativeOperatorNames) + if ((pos-start)==2 || (pos-start)==3) { + String asString = new String(subarray).toUpperCase(); + int idx = Arrays.binarySearch(alternativeOperatorNames,asString); + if (idx>=0) { + pushOneCharOrTwoCharToken(TokenKind.valueOf(asString),start); + return; + } + } + tokens.add(new Token(TokenKind.IDENTIFIER,subarray,start,pos)); } private void pushIntToken(char[] data,boolean isLong, int start, int end) { @@ -399,6 +413,10 @@ public class Tokenizer { tokens.add(new Token(kind,pos,pos+2)); pos+=2; } + + private void pushOneCharOrTwoCharToken(TokenKind kind, int pos) { + tokens.add(new Token(kind,pos,pos+kind.getLength())); + } // ID: ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9'|DOT_ESCAPED)*; private boolean isIdentifier(char ch) { diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/BooleanExpressionTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/BooleanExpressionTests.java index 0661ac550c0..30b3210b8f8 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/BooleanExpressionTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/BooleanExpressionTests.java @@ -55,6 +55,9 @@ public class BooleanExpressionTests extends ExpressionTestCase { public void testNot() { evaluate("!false", Boolean.TRUE, Boolean.class); evaluate("!true", Boolean.FALSE, Boolean.class); + + evaluate("not false", Boolean.TRUE, Boolean.class); + evaluate("NoT true", Boolean.FALSE, Boolean.class); } @Test diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java index c6bbb612778..17b72a75448 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java @@ -48,6 +48,15 @@ public class OperatorTests extends ExpressionTestCase { evaluate("5.0d < 3.0d", false, Boolean.class); evaluate("'abc' < 'def'",true,Boolean.class); evaluate("'def' < 'abc'",false,Boolean.class); + + evaluate("3 lt 5", true, Boolean.class); + evaluate("5 lt 3", false, Boolean.class); + evaluate("3L lt 5L", true, Boolean.class); + evaluate("5L lt 3L", false, Boolean.class); + evaluate("3.0d lT 5.0d", true, Boolean.class); + evaluate("5.0d Lt 3.0d", false, Boolean.class); + evaluate("'abc' LT 'def'",true,Boolean.class); + evaluate("'def' lt 'abc'",false,Boolean.class); } @Test @@ -64,6 +73,19 @@ public class OperatorTests extends ExpressionTestCase { evaluate("'abc' <= 'def'",true,Boolean.class); evaluate("'def' <= 'abc'",false,Boolean.class); evaluate("'abc' <= 'abc'",true,Boolean.class); + + evaluate("3 le 5", true, Boolean.class); + evaluate("5 le 3", false, Boolean.class); + evaluate("6 Le 6", true, Boolean.class); + evaluate("3L lE 5L", true, Boolean.class); + evaluate("5L LE 3L", false, Boolean.class); + evaluate("5L le 5L", true, Boolean.class); + evaluate("3.0d LE 5.0d", true, Boolean.class); + evaluate("5.0d lE 3.0d", false, Boolean.class); + evaluate("5.0d Le 5.0d", true, Boolean.class); + evaluate("'abc' Le 'def'",true,Boolean.class); + evaluate("'def' LE 'abc'",false,Boolean.class); + evaluate("'abc' le 'abc'",true,Boolean.class); } @Test @@ -74,6 +96,13 @@ public class OperatorTests extends ExpressionTestCase { evaluate("3.0f == 5.0f", false, Boolean.class); evaluate("3.0f == 3.0f", true, Boolean.class); evaluate("'abc' == null", false, Boolean.class); + + evaluate("3 eq 5", false, Boolean.class); + evaluate("5 eQ 3", false, Boolean.class); + evaluate("6 Eq 6", true, Boolean.class); + evaluate("3.0f eq 5.0f", false, Boolean.class); + evaluate("3.0f EQ 3.0f", true, Boolean.class); + evaluate("'abc' EQ null", false, Boolean.class); } @Test @@ -83,6 +112,12 @@ public class OperatorTests extends ExpressionTestCase { evaluate("6 != 6", false, Boolean.class); evaluate("3.0f != 5.0f", true, Boolean.class); evaluate("3.0f != 3.0f", false, Boolean.class); + + evaluate("3 ne 5", true, Boolean.class); + evaluate("5 nE 3", true, Boolean.class); + evaluate("6 Ne 6", false, Boolean.class); + evaluate("3.0f NE 5.0f", true, Boolean.class); + evaluate("3.0f ne 3.0f", false, Boolean.class); } @Test @@ -100,6 +135,10 @@ public class OperatorTests extends ExpressionTestCase { evaluate("'def' >= 'abc'",true,Boolean.class); evaluate("'abc' >= 'abc'",true,Boolean.class); + evaluate("3 GE 5", false, Boolean.class); + evaluate("5 gE 3", true, Boolean.class); + evaluate("6 Ge 6", true, Boolean.class); + evaluate("3L ge 5L", false, Boolean.class); } @Test @@ -112,6 +151,11 @@ public class OperatorTests extends ExpressionTestCase { evaluate("5.0d > 3.0d", true, Boolean.class); evaluate("'abc' > 'def'",false,Boolean.class); evaluate("'def' > 'abc'",true,Boolean.class); + + evaluate("3.0d gt 5.0d", false, Boolean.class); + evaluate("5.0d gT 3.0d", true, Boolean.class); + evaluate("'abc' Gt 'def'",false,Boolean.class); + evaluate("'def' GT 'abc'",true,Boolean.class); } @Test @@ -145,6 +189,10 @@ public class OperatorTests extends ExpressionTestCase { evaluate("3 * 5f", 15d, Double.class); evaluate("3 / 1", 3, Integer.class); evaluate("3 % 2", 1, Integer.class); + evaluate("3 mod 2", 1, Integer.class); + evaluate("3 mOd 2", 1, Integer.class); + evaluate("3 Mod 2", 1, Integer.class); + evaluate("3 MOD 2", 1, Integer.class); } @Test @@ -207,6 +255,8 @@ public class OperatorTests extends ExpressionTestCase { public void testDivide() { evaluate("3.0f / 5.0f", 0.6d, Double.class); evaluate("4L/2L",2L,Long.class); + evaluate("3.0f div 5.0f", 0.6d, Double.class); + evaluate("4L DIV 2L",2L,Long.class); evaluateAndCheckError("'abc'/'def'",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES); } 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/SpelParserTests.java index c542832055f..12a5c9ef9d3 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/SpelParserTests.java @@ -274,9 +274,9 @@ public class SpelParserTests { @Test public void testTokenKind() { - TokenKind tk = TokenKind.BANG; + TokenKind tk = TokenKind.NOT; Assert.assertFalse(tk.hasPayload()); - Assert.assertEquals("BANG(!)",tk.toString()); + Assert.assertEquals("NOT(!)",tk.toString()); tk = TokenKind.MINUS; Assert.assertFalse(tk.hasPayload()); @@ -289,11 +289,11 @@ public class SpelParserTests { @Test public void testToken() { - Token token = new Token(TokenKind.BANG,0,3); - Assert.assertEquals(TokenKind.BANG,token.kind); + Token token = new Token(TokenKind.NOT,0,3); + Assert.assertEquals(TokenKind.NOT,token.kind); Assert.assertEquals(0,token.startpos); Assert.assertEquals(3,token.endpos); - Assert.assertEquals("[BANG(!)](0,3)",token.toString()); + Assert.assertEquals("[NOT(!)](0,3)",token.toString()); token = new Token(TokenKind.LITERAL_STRING,"abc".toCharArray(),0,3); Assert.assertEquals(TokenKind.LITERAL_STRING,token.kind);