Fix SpEL support for quotes within String literals
Prior to this commit, there were two bugs in the support for quotes
within String literals in SpEL expressions.
- Two double quotes ("") or two single quotes ('') were always replaced
with one double quote or one single quote, respectively, regardless
of which quote character was used to enclose the original String
literal. This resulted in the loss of one of the double quotes when
the String literal was enclosed in single quotes, and vice versa. For
example, 'x "" y' became 'x " y'.
- A single quote which was properly escaped in a String literal
enclosed within single quotes was not escaped in the AST string
representation of the expression. For example, 'x '' y' became 'x ' y'.
This commit fixes both of these related issues in StringLiteral and
overhauls the structure of ParsingTests.
Closes gh-29604, gh-28356
This commit is contained in:
parent
c899af0e03
commit
73a18046c3
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -26,6 +26,7 @@ import org.springframework.util.StringUtils;
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public class StringLiteral extends Literal {
|
public class StringLiteral extends Literal {
|
||||||
|
|
@ -36,9 +37,19 @@ public class StringLiteral extends Literal {
|
||||||
public StringLiteral(String payload, int startPos, int endPos, String value) {
|
public StringLiteral(String payload, int startPos, int endPos, String value) {
|
||||||
super(payload, startPos, endPos);
|
super(payload, startPos, endPos);
|
||||||
|
|
||||||
|
// The original enclosing quote character for the string literal: ' or ".
|
||||||
|
char quoteCharacter = value.charAt(0);
|
||||||
|
|
||||||
|
// Remove enclosing quotes
|
||||||
String valueWithinQuotes = value.substring(1, value.length() - 1);
|
String valueWithinQuotes = value.substring(1, value.length() - 1);
|
||||||
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'");
|
|
||||||
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\"");
|
// Replace escaped internal quote characters
|
||||||
|
if (quoteCharacter == '\'') {
|
||||||
|
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\"");
|
||||||
|
}
|
||||||
|
|
||||||
this.value = new TypedValue(valueWithinQuotes);
|
this.value = new TypedValue(valueWithinQuotes);
|
||||||
this.exitTypeDescriptor = "Ljava/lang/String";
|
this.exitTypeDescriptor = "Ljava/lang/String";
|
||||||
|
|
@ -52,7 +63,9 @@ public class StringLiteral extends Literal {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "'" + getLiteralValue().getValue() + "'";
|
String ast = String.valueOf(getLiteralValue().getValue());
|
||||||
|
ast = StringUtils.replace(ast, "'", "''");
|
||||||
|
return "'" + ast + "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -364,6 +364,55 @@ class EvaluationTests extends AbstractExpressionTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class StringLiterals {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void insideSingleQuotes() {
|
||||||
|
evaluate("'hello'", "hello", String.class);
|
||||||
|
evaluate("'hello world'", "hello world", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void insideDoubleQuotes() {
|
||||||
|
evaluate("\"hello\"", "hello", String.class);
|
||||||
|
evaluate("\"hello world\"", "hello world", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void singleQuotesInsideSingleQuotes() {
|
||||||
|
evaluate("'Tony''s Pizza'", "Tony's Pizza", String.class);
|
||||||
|
evaluate("'big ''''pizza'''' parlor'", "big ''pizza'' parlor", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doubleQuotesInsideDoubleQuotes() {
|
||||||
|
evaluate("\"big \"\"pizza\"\" parlor\"", "big \"pizza\" parlor", String.class);
|
||||||
|
evaluate("\"big \"\"\"\"pizza\"\"\"\" parlor\"", "big \"\"pizza\"\" parlor", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void singleQuotesInsideDoubleQuotes() {
|
||||||
|
evaluate("\"Tony's Pizza\"", "Tony's Pizza", String.class);
|
||||||
|
evaluate("\"big ''pizza'' parlor\"", "big ''pizza'' parlor", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doubleQuotesInsideSingleQuotes() {
|
||||||
|
evaluate("'big \"pizza\" parlor'", "big \"pizza\" parlor", String.class);
|
||||||
|
evaluate("'two double \"\" quotes'", "two double \"\" quotes", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void inCompoundExpressions() {
|
||||||
|
evaluate("'123''4' == '123''4'", true, Boolean.class);
|
||||||
|
evaluate("""
|
||||||
|
"123""4" == "123""4"\
|
||||||
|
""", true, Boolean.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
class RelationalOperatorTests {
|
class RelationalOperatorTests {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.expression.spel;
|
package org.springframework.expression.spel;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.expression.spel.standard.SpelExpression;
|
import org.springframework.expression.spel.standard.SpelExpression;
|
||||||
|
|
@ -37,413 +39,491 @@ class ParsingTests {
|
||||||
private final SpelExpressionParser parser = new SpelExpressionParser();
|
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
|
|
||||||
|
|
||||||
// literals
|
@Nested
|
||||||
@Test
|
class Miscellaneous {
|
||||||
void literalBoolean01() {
|
|
||||||
parseCheck("false");
|
@Test
|
||||||
|
void literalNull() {
|
||||||
|
parseCheck("null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void literalDate01() {
|
||||||
|
parseCheck("date('1974/08/24')");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void literalDate02() {
|
||||||
|
parseCheck("date('19740824T131030','yyyyMMddTHHmmss')");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mixedOperators() {
|
||||||
|
parseCheck("true and 5>3", "(true and (5 > 3))");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assignmentToVariables() {
|
||||||
|
parseCheck("#var1='value1'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("toStringAST() is broken for array construction")
|
||||||
|
@Test
|
||||||
|
void collectionProcessorsCountStringArray() {
|
||||||
|
parseCheck("new String[] {'abc','def','xyz'}.count()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("toStringAST() is broken for array construction")
|
||||||
|
@Test
|
||||||
|
void collectionProcessorsCountIntArray() {
|
||||||
|
parseCheck("new int[] {1,2,3}.count()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("toStringAST() is broken for array construction")
|
||||||
|
@Test
|
||||||
|
void collectionProcessorsMax() {
|
||||||
|
parseCheck("new int[] {1,2,3}.max()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("toStringAST() is broken for array construction")
|
||||||
|
@Test
|
||||||
|
void collectionProcessorsMin() {
|
||||||
|
parseCheck("new int[] {1,2,3}.min()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("toStringAST() is broken for array construction")
|
||||||
|
@Test
|
||||||
|
void collectionProcessorsAverage() {
|
||||||
|
parseCheck("new int[] {1,2,3}.average()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("toStringAST() is broken for array construction")
|
||||||
|
@Test
|
||||||
|
void collectionProcessorsSort() {
|
||||||
|
parseCheck("new int[] {3,2,1}.sort()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void collectionProcessorsNonNull() {
|
||||||
|
parseCheck("{'a','b',null,'d',null}.nonNull()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void collectionProcessorsDistinct() {
|
||||||
|
parseCheck("{'a','b','a','d','e'}.distinct()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("Unsupported syntax/feature")
|
||||||
|
@Test
|
||||||
|
void lambdaMax() {
|
||||||
|
parseCheck("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))",
|
||||||
|
"(#max={|x,y| ($x > $y) ? $x : $y };#max(5,25))");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("Unsupported syntax/feature")
|
||||||
|
@Test
|
||||||
|
void lambdaFactorial() {
|
||||||
|
parseCheck("(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))",
|
||||||
|
"(#fact={|n| ($n <= 1) ? 1 : ($n * #fact(($n - 1))) };#fact(5))");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("Unsupported syntax/feature")
|
||||||
|
@Test
|
||||||
|
void projection() {
|
||||||
|
parseCheck("{1,2,3,4,5,6,7,8,9,10}.!{#isEven()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("Unsupported syntax/feature")
|
||||||
|
@Test
|
||||||
|
void selection() {
|
||||||
|
parseCheck("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}",
|
||||||
|
"{1,2,3,4,5,6,7,8,9,10}.?{(#isEven(#this) == 'y')}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("Unsupported syntax/feature")
|
||||||
|
@Test
|
||||||
|
void selectionFirst() {
|
||||||
|
parseCheck("{1,2,3,4,5,6,7,8,9,10}.^{#isEven(#this) == 'y'}",
|
||||||
|
"{1,2,3,4,5,6,7,8,9,10}.^{(#isEven(#this) == 'y')}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("Unsupported syntax/feature")
|
||||||
|
@Test
|
||||||
|
void selectionLast() {
|
||||||
|
parseCheck("{1,2,3,4,5,6,7,8,9,10}.${#isEven(#this) == 'y'}",
|
||||||
|
"{1,2,3,4,5,6,7,8,9,10}.${(#isEven(#this) == 'y')}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalLong01() {
|
class LiteralBooleans {
|
||||||
parseCheck("37L", "37");
|
|
||||||
|
@Test
|
||||||
|
void literalBooleanFalse() {
|
||||||
|
parseCheck("false");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void literalBooleanTrue() {
|
||||||
|
parseCheck("true");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void literalBooleanNotTrue() {
|
||||||
|
parseCheck("!true");
|
||||||
|
parseCheck("not true", "!true");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalBoolean02() {
|
class LiteralNumbers {
|
||||||
parseCheck("true");
|
|
||||||
|
@Test
|
||||||
|
void literalLong() {
|
||||||
|
parseCheck("37L", "37");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void literalIntegers() {
|
||||||
|
parseCheck("1");
|
||||||
|
parseCheck("1415");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void literalReal() {
|
||||||
|
parseCheck("6.0221415E+23", "6.0221415E23");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void literalHex() {
|
||||||
|
parseCheck("0x7FFFFFFF", "2147483647");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalBoolean03() {
|
class LiteralStrings {
|
||||||
parseCheck("!true");
|
|
||||||
|
@Test
|
||||||
|
void insideSingleQuotes() {
|
||||||
|
parseCheck("'hello'");
|
||||||
|
parseCheck("'hello world'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void insideDoubleQuotes() {
|
||||||
|
parseCheck("\"hello\"", "'hello'");
|
||||||
|
parseCheck("\"hello world\"", "'hello world'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void singleQuotesInsideSingleQuotes() {
|
||||||
|
parseCheck("'Tony''s Pizza'");
|
||||||
|
parseCheck("'big ''''pizza'''' parlor'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doubleQuotesInsideDoubleQuotes() {
|
||||||
|
parseCheck("\"big \"\"pizza\"\" parlor\"", "'big \"pizza\" parlor'");
|
||||||
|
parseCheck("\"big \"\"\"\"pizza\"\"\"\" parlor\"", "'big \"\"pizza\"\" parlor'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void singleQuotesInsideDoubleQuotes() {
|
||||||
|
parseCheck("\"Tony's Pizza\"", "'Tony''s Pizza'");
|
||||||
|
parseCheck("\"big ''pizza'' parlor\"", "'big ''''pizza'''' parlor'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doubleQuotesInsideSingleQuotes() {
|
||||||
|
parseCheck("'big \"pizza\" parlor'");
|
||||||
|
parseCheck("'two double \"\" quotes'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void inCompoundExpressions() {
|
||||||
|
parseCheck("'123''4' == '123''4'", "('123''4' == '123''4')");
|
||||||
|
parseCheck("('123''4'=='123''4')", "('123''4' == '123''4')");
|
||||||
|
parseCheck(
|
||||||
|
"""
|
||||||
|
"123""4" == "123""4"\
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
('123"4' == '123"4')\
|
||||||
|
""");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalInteger01() {
|
class BooleanOperators {
|
||||||
parseCheck("1");
|
|
||||||
|
@Test
|
||||||
|
void booleanOperatorsOr01() {
|
||||||
|
parseCheck("false or false", "(false or false)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void booleanOperatorsOr02() {
|
||||||
|
parseCheck("false or true", "(false or true)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void booleanOperatorsOr03() {
|
||||||
|
parseCheck("true or false", "(true or false)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void booleanOperatorsOr04() {
|
||||||
|
parseCheck("true or false", "(true or false)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void booleanOperatorsMix() {
|
||||||
|
parseCheck("false or true and false", "(false or (true and false))");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalInteger02() {
|
class RelationalOperators {
|
||||||
parseCheck("1415");
|
|
||||||
|
@Test
|
||||||
|
void relOperatorsGT() {
|
||||||
|
parseCheck("3>6", "(3 > 6)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void relOperatorsLT() {
|
||||||
|
parseCheck("3<6", "(3 < 6)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void relOperatorsLE() {
|
||||||
|
parseCheck("3<=6", "(3 <= 6)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void relOperatorsGE01() {
|
||||||
|
parseCheck("3>=6", "(3 >= 6)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void relOperatorsGE02() {
|
||||||
|
parseCheck("3>=3", "(3 >= 3)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("Unsupported syntax/feature")
|
||||||
|
@Test
|
||||||
|
void relOperatorsIn() {
|
||||||
|
parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void relOperatorsBetweenNumbers() {
|
||||||
|
parseCheck("1 between {1, 5}", "(1 between {1,5})");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void relOperatorsBetweenStrings() {
|
||||||
|
parseCheck("'efg' between {'abc', 'xyz'}", "('efg' between {'abc','xyz'})");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void relOperatorsInstanceOfInt() {
|
||||||
|
parseCheck("'xyz' instanceof int", "('xyz' instanceof int)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void relOperatorsInstanceOfList() {
|
||||||
|
parseCheck("{1, 2, 3, 4, 5} instanceof List", "({1,2,3,4,5} instanceof List)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void relOperatorsMatches() {
|
||||||
|
parseCheck("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "('5.0067' matches '^-?\\d+(\\.\\d{2})?$')");
|
||||||
|
parseCheck("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "('5.00' matches '^-?\\d+(\\.\\d{2})?$')");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalString01() {
|
class MathematicalOperators {
|
||||||
parseCheck("'hello'");
|
|
||||||
|
@Test
|
||||||
|
void mathOperatorsAddIntegers() {
|
||||||
|
parseCheck("2+4", "(2 + 4)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mathOperatorsAddStrings() {
|
||||||
|
parseCheck("'a'+'b'", "('a' + 'b')");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mathOperatorsAddMultipleStrings() {
|
||||||
|
parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mathOperatorsSubtract() {
|
||||||
|
parseCheck("5-4", "(5 - 4)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mathOperatorsMultiply() {
|
||||||
|
parseCheck("7*4", "(7 * 4)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mathOperatorsDivide() {
|
||||||
|
parseCheck("8/4", "(8 / 4)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mathOperatorModulus() {
|
||||||
|
parseCheck("7 % 4", "(7 % 4)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalString02() {
|
class References {
|
||||||
parseCheck("'joe bloggs'");
|
|
||||||
|
@Test
|
||||||
|
void references() {
|
||||||
|
parseCheck("@foo");
|
||||||
|
parseCheck("@'foo.bar'");
|
||||||
|
parseCheck("@\"foo.bar.goo\"" , "@'foo.bar.goo'");
|
||||||
|
parseCheck("@$$foo");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalString03() {
|
class Properties {
|
||||||
parseCheck("'Tony''s Pizza'", "'Tony's Pizza'");
|
|
||||||
|
@Test
|
||||||
|
void propertiesSingle() {
|
||||||
|
parseCheck("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void propertiesDouble() {
|
||||||
|
parseCheck("placeofbirth.CitY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void propertiesMultiple() {
|
||||||
|
parseCheck("a.b.c.d.e");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalReal01() {
|
class InlineCollections {
|
||||||
parseCheck("6.0221415E+23", "6.0221415E23");
|
|
||||||
|
@Test
|
||||||
|
void inlineListOfIntegers() {
|
||||||
|
parseCheck("{1,2,3,4}");
|
||||||
|
parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void inlineListOfStrings() {
|
||||||
|
parseCheck("{'abc','xyz'}", "{'abc','xyz'}");
|
||||||
|
parseCheck("{\"abc\", 'xyz'}", "{'abc','xyz'}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void inlineMapStringToObject() {
|
||||||
|
parseCheck("{'key1':'Value 1','today':DateTime.Today}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void inlineMapIntegerToString() {
|
||||||
|
parseCheck("{1:'January',2:'February',3:'March'}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalHex01() {
|
class MethodsConstructorsAndArrays {
|
||||||
parseCheck("0x7FFFFFFF", "2147483647");
|
|
||||||
|
@Test
|
||||||
|
void methods() {
|
||||||
|
parseCheck("echo(12)");
|
||||||
|
parseCheck("echo(name)");
|
||||||
|
parseCheck("age.doubleItAndAdd(12)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void constructors() {
|
||||||
|
parseCheck("new String('hello')");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("toStringAST() is broken for array construction")
|
||||||
|
@Test
|
||||||
|
void arrayConstruction01() {
|
||||||
|
parseCheck("new String[3]");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("toStringAST() is broken for array construction")
|
||||||
|
@Test
|
||||||
|
void arrayConstruction02() {
|
||||||
|
parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("toStringAST() is broken for array construction")
|
||||||
|
@Test
|
||||||
|
void arrayConstruction03() {
|
||||||
|
parseCheck("new String[] {'abc','xyz'}", "new String[] {'abc','xyz'}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalDate01() {
|
class VariablesAndFunctions {
|
||||||
parseCheck("date('1974/08/24')");
|
|
||||||
|
@Test
|
||||||
|
void variables() {
|
||||||
|
parseCheck("#foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void functions() {
|
||||||
|
parseCheck("#fn(1,2,3)");
|
||||||
|
parseCheck("#fn('hello')");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalDate02() {
|
class ElvisAndTernaryOperators {
|
||||||
parseCheck("date('19740824T131030','yyyyMMddTHHmmss')");
|
|
||||||
|
@Test
|
||||||
|
void elvis() {
|
||||||
|
parseCheck("3?:1", "(3 ?: 1)");
|
||||||
|
parseCheck("(2*3)?:1*10", "((2 * 3) ?: (1 * 10))");
|
||||||
|
parseCheck("((2*3)?:1)*10", "(((2 * 3) ?: 1) * 10)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void ternary() {
|
||||||
|
parseCheck("1>2?3:4", "((1 > 2) ? 3 : 4)");
|
||||||
|
parseCheck("(a ? 1 : 0) * 10", "((a ? 1 : 0) * 10)");
|
||||||
|
parseCheck("(a?1:0)*10", "((a ? 1 : 0) * 10)");
|
||||||
|
parseCheck("(4 % 2 == 0 ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
|
||||||
|
parseCheck("((4 % 2 == 0) ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
|
||||||
|
parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'",
|
||||||
|
"(({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd')");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void literalNull01() {
|
class TypeReferences {
|
||||||
parseCheck("null");
|
|
||||||
|
@Test
|
||||||
|
void typeReferences01() {
|
||||||
|
parseCheck("T(java.lang.String)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void typeReferences02() {
|
||||||
|
parseCheck("T(String)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// boolean operators
|
|
||||||
@Test
|
|
||||||
void booleanOperatorsOr01() {
|
|
||||||
parseCheck("false or false", "(false or false)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void booleanOperatorsOr02() {
|
|
||||||
parseCheck("false or true", "(false or true)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void booleanOperatorsOr03() {
|
|
||||||
parseCheck("true or false", "(true or false)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void booleanOperatorsOr04() {
|
|
||||||
parseCheck("true or false", "(true or false)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void booleanOperatorsMix01() {
|
|
||||||
parseCheck("false or true and false", "(false or (true and false))");
|
|
||||||
}
|
|
||||||
|
|
||||||
// relational operators
|
|
||||||
@Test
|
|
||||||
void relOperatorsGT01() {
|
|
||||||
parseCheck("3>6", "(3 > 6)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void relOperatorsLT01() {
|
|
||||||
parseCheck("3<6", "(3 < 6)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void relOperatorsLE01() {
|
|
||||||
parseCheck("3<=6", "(3 <= 6)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void relOperatorsGE01() {
|
|
||||||
parseCheck("3>=6", "(3 >= 6)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void relOperatorsGE02() {
|
|
||||||
parseCheck("3>=3", "(3 >= 3)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void elvis() {
|
|
||||||
parseCheck("3?:1", "(3 ?: 1)");
|
|
||||||
parseCheck("(2*3)?:1*10", "((2 * 3) ?: (1 * 10))");
|
|
||||||
parseCheck("((2*3)?:1)*10", "(((2 * 3) ?: 1) * 10)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// void relOperatorsIn01() {
|
|
||||||
// parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void relOperatorsBetween01() {
|
|
||||||
// parseCheck("1 between {1, 5}", "(1 between {1,5})");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void relOperatorsBetween02() {
|
|
||||||
// parseCheck("'efg' between {'abc', 'xyz'}", "('efg' between {'abc','xyz'})");
|
|
||||||
// }// true
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void relOperatorsIs01() {
|
|
||||||
parseCheck("'xyz' instanceof int", "('xyz' instanceof int)");
|
|
||||||
}// false
|
|
||||||
|
|
||||||
// void relOperatorsIs02() {
|
|
||||||
// parseCheck("{1, 2, 3, 4, 5} instanceof List", "({1,2,3,4,5} instanceof List)");
|
|
||||||
// }// true
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void relOperatorsMatches01() {
|
|
||||||
parseCheck("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "('5.0067' matches '^-?\\d+(\\.\\d{2})?$')");
|
|
||||||
}// false
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void relOperatorsMatches02() {
|
|
||||||
parseCheck("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "('5.00' matches '^-?\\d+(\\.\\d{2})?$')");
|
|
||||||
}// true
|
|
||||||
|
|
||||||
// mathematical operators
|
|
||||||
@Test
|
|
||||||
void mathOperatorsAdd01() {
|
|
||||||
parseCheck("2+4", "(2 + 4)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void mathOperatorsAdd02() {
|
|
||||||
parseCheck("'a'+'b'", "('a' + 'b')");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void mathOperatorsAdd03() {
|
|
||||||
parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void mathOperatorsSubtract01() {
|
|
||||||
parseCheck("5-4", "(5 - 4)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void mathOperatorsMultiply01() {
|
|
||||||
parseCheck("7*4", "(7 * 4)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void mathOperatorsDivide01() {
|
|
||||||
parseCheck("8/4", "(8 / 4)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void mathOperatorModulus01() {
|
|
||||||
parseCheck("7 % 4", "(7 % 4)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// mixed operators
|
|
||||||
@Test
|
|
||||||
void mixedOperators01() {
|
|
||||||
parseCheck("true and 5>3", "(true and (5 > 3))");
|
|
||||||
}
|
|
||||||
|
|
||||||
// collection processors
|
|
||||||
// void collectionProcessorsCount01() {
|
|
||||||
// parseCheck("new String[] {'abc','def','xyz'}.count()");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void collectionProcessorsCount02() {
|
|
||||||
// parseCheck("new int[] {1,2,3}.count()");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void collectionProcessorsMax01() {
|
|
||||||
// parseCheck("new int[] {1,2,3}.max()");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void collectionProcessorsMin01() {
|
|
||||||
// parseCheck("new int[] {1,2,3}.min()");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void collectionProcessorsAverage01() {
|
|
||||||
// parseCheck("new int[] {1,2,3}.average()");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void collectionProcessorsSort01() {
|
|
||||||
// parseCheck("new int[] {3,2,1}.sort()");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void collectionProcessorsNonNull01() {
|
|
||||||
// parseCheck("{'a','b',null,'d',null}.nonNull()");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void collectionProcessorsDistinct01() {
|
|
||||||
// parseCheck("{'a','b','a','d','e'}.distinct()");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// references
|
|
||||||
@Test
|
|
||||||
void references01() {
|
|
||||||
parseCheck("@foo");
|
|
||||||
parseCheck("@'foo.bar'");
|
|
||||||
parseCheck("@\"foo.bar.goo\"" , "@'foo.bar.goo'");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void references03() {
|
|
||||||
parseCheck("@$$foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
// properties
|
|
||||||
@Test
|
|
||||||
void properties01() {
|
|
||||||
parseCheck("name");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void properties02() {
|
|
||||||
parseCheck("placeofbirth.CitY");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void properties03() {
|
|
||||||
parseCheck("a.b.c.d.e");
|
|
||||||
}
|
|
||||||
|
|
||||||
// inline list creation
|
|
||||||
@Test
|
|
||||||
void inlineListCreation01() {
|
|
||||||
parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void inlineListCreation02() {
|
|
||||||
parseCheck("{'abc','xyz'}", "{'abc','xyz'}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// inline map creation
|
|
||||||
@Test
|
|
||||||
void inlineMapCreation01() {
|
|
||||||
parseCheck("{'key1':'Value 1','today':DateTime.Today}");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void inlineMapCreation02() {
|
|
||||||
parseCheck("{1:'January',2:'February',3:'March'}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods
|
|
||||||
@Test
|
|
||||||
void methods01() {
|
|
||||||
parseCheck("echo(12)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void methods02() {
|
|
||||||
parseCheck("echo(name)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void methods03() {
|
|
||||||
parseCheck("age.doubleItAndAdd(12)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// constructors
|
|
||||||
@Test
|
|
||||||
void constructors01() {
|
|
||||||
parseCheck("new String('hello')");
|
|
||||||
}
|
|
||||||
|
|
||||||
// void constructors02() {
|
|
||||||
// parseCheck("new String[3]");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// array construction
|
|
||||||
// void arrayConstruction01() {
|
|
||||||
// parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void arrayConstruction02() {
|
|
||||||
// parseCheck("new String[] {'abc','xyz'}", "new String[] {'abc','xyz'}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// variables and functions
|
|
||||||
@Test
|
|
||||||
void variables01() {
|
|
||||||
parseCheck("#foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void functions01() {
|
|
||||||
parseCheck("#fn(1,2,3)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void functions02() {
|
|
||||||
parseCheck("#fn('hello')");
|
|
||||||
}
|
|
||||||
|
|
||||||
// projections and selections
|
|
||||||
// void projections01() {
|
|
||||||
// parseCheck("{1,2,3,4,5,6,7,8,9,10}.!{#isEven()}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void selections01() {
|
|
||||||
// parseCheck("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}",
|
|
||||||
// "{1,2,3,4,5,6,7,8,9,10}.?{(#isEven(#this) == 'y')}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void selectionsFirst01() {
|
|
||||||
// parseCheck("{1,2,3,4,5,6,7,8,9,10}.^{#isEven(#this) == 'y'}",
|
|
||||||
// "{1,2,3,4,5,6,7,8,9,10}.^{(#isEven(#this) == 'y')}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void selectionsLast01() {
|
|
||||||
// parseCheck("{1,2,3,4,5,6,7,8,9,10}.${#isEven(#this) == 'y'}",
|
|
||||||
// "{1,2,3,4,5,6,7,8,9,10}.${(#isEven(#this) == 'y')}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// assignment
|
|
||||||
@Test
|
|
||||||
void assignmentToVariables01() {
|
|
||||||
parseCheck("#var1='value1'");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ternary operator
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void ternaryOperator01() {
|
|
||||||
parseCheck("1>2?3:4", "((1 > 2) ? 3 : 4)");
|
|
||||||
parseCheck("(a ? 1 : 0) * 10", "((a ? 1 : 0) * 10)");
|
|
||||||
parseCheck("(a?1:0)*10", "((a ? 1 : 0) * 10)");
|
|
||||||
parseCheck("(4 % 2 == 0 ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
|
|
||||||
parseCheck("((4 % 2 == 0) ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void ternaryOperator02() {
|
|
||||||
parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'",
|
|
||||||
"(({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd')");
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// void lambdaMax() {
|
|
||||||
// parseCheck("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", "(#max={|x,y| ($x > $y) ? $x : $y };#max(5,25))");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void lambdaFactorial() {
|
|
||||||
// parseCheck("(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))",
|
|
||||||
// "(#fact={|n| ($n <= 1) ? 1 : ($n * #fact(($n - 1))) };#fact(5))");
|
|
||||||
// } // 120
|
|
||||||
|
|
||||||
// Type references
|
|
||||||
@Test
|
|
||||||
void typeReferences01() {
|
|
||||||
parseCheck("T(java.lang.String)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void typeReferences02() {
|
|
||||||
parseCheck("T(String)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void inlineList1() {
|
|
||||||
parseCheck("{1,2,3,4}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the supplied expression and then create a string representation of the resultant AST, it should be the same
|
* Parse the supplied expression and then create a string representation of the resultant AST, it should be the same
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ class SpelReproTests extends AbstractExpressionTests {
|
||||||
checkTemplateParsingError("abc${ } }", "No expression defined within delimiter '${}' at character 3");
|
checkTemplateParsingError("abc${ } }", "No expression defined within delimiter '${}' at character 3");
|
||||||
checkTemplateParsingError("abc$[ } ]", DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT, "Found closing '}' at position 6 without an opening '{'");
|
checkTemplateParsingError("abc$[ } ]", DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT, "Found closing '}' at position 6 without an opening '{'");
|
||||||
|
|
||||||
checkTemplateParsing("abc ${\"def''g}hi\"} jkl", "abc def'g}hi jkl");
|
checkTemplateParsing("abc ${\"def''g}hi\"} jkl", "abc def''g}hi jkl");
|
||||||
checkTemplateParsing("abc ${'def''g}hi'} jkl", "abc def'g}hi jkl");
|
checkTemplateParsing("abc ${'def''g}hi'} jkl", "abc def'g}hi jkl");
|
||||||
checkTemplateParsing("}", "}");
|
checkTemplateParsing("}", "}");
|
||||||
checkTemplateParsing("${'hello'} world", "hello world");
|
checkTemplateParsing("${'hello'} world", "hello world");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue