SPR-5518: textual alternatives for operators (eq, lt, le, gt, ge, ne, div, mod, not) - removes messy escaping for expressions declared in XML.
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1476 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
80424f4423
commit
4662a320d0
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue