From 73a18046c3945bcd296fe0b3a82ad3fec8812fa6 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 7 Dec 2022 10:51:56 -0500 Subject: [PATCH] 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 --- .../expression/spel/ast/StringLiteral.java | 21 +- .../expression/spel/EvaluationTests.java | 49 + .../expression/spel/ParsingTests.java | 890 ++++++++++-------- .../expression/spel/SpelReproTests.java | 2 +- 4 files changed, 552 insertions(+), 410 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java index 3fc939c2b5a7..5bc43729ac83 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java @@ -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"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class StringLiteral extends Literal { @@ -36,9 +37,19 @@ public class StringLiteral extends Literal { public StringLiteral(String payload, int startPos, int endPos, String value) { 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); - 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.exitTypeDescriptor = "Ljava/lang/String"; @@ -52,7 +63,9 @@ public TypedValue getLiteralValue() { @Override public String toString() { - return "'" + getLiteralValue().getValue() + "'"; + String ast = String.valueOf(getLiteralValue().getValue()); + ast = StringUtils.replace(ast, "'", "''"); + return "'" + ast + "'"; } @Override diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index ecb29fb6a0f9..5e721e396c98 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -364,6 +364,55 @@ void limitCollectionGrowing() { } + @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 class RelationalOperatorTests { diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java index 075688ea8449..c0e85480fd11 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java @@ -16,6 +16,8 @@ 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.springframework.expression.spel.standard.SpelExpression; @@ -37,413 +39,491 @@ class ParsingTests { private final SpelExpressionParser parser = new SpelExpressionParser(); - // literals - @Test - void literalBoolean01() { - parseCheck("false"); + @Nested + class Miscellaneous { + + @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')}"); + } + } + + @Nested + class LiteralBooleans { + + @Test + void literalBooleanFalse() { + parseCheck("false"); + } + + @Test + void literalBooleanTrue() { + parseCheck("true"); + } + + @Test + void literalBooleanNotTrue() { + parseCheck("!true"); + parseCheck("not true", "!true"); + } + } + + @Nested + class LiteralNumbers { + + @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"); + } + } + + @Nested + class LiteralStrings { + + @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')\ + """); + } + } + + @Nested + class BooleanOperators { + + @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))"); + } + } + + @Nested + class RelationalOperators { + + @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})?$')"); + } + } + + @Nested + class MathematicalOperators { + + @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)"); + } + } + + @Nested + class References { + + @Test + void references() { + parseCheck("@foo"); + parseCheck("@'foo.bar'"); + parseCheck("@\"foo.bar.goo\"" , "@'foo.bar.goo'"); + parseCheck("@$$foo"); + } + } + + @Nested + class Properties { + + @Test + void propertiesSingle() { + parseCheck("name"); + } + + @Test + void propertiesDouble() { + parseCheck("placeofbirth.CitY"); + } + + @Test + void propertiesMultiple() { + parseCheck("a.b.c.d.e"); + } + } + + @Nested + class InlineCollections { + + @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'}"); + } + } + + @Nested + class MethodsConstructorsAndArrays { + + @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'}"); + } + } + + @Nested + class VariablesAndFunctions { + + @Test + void variables() { + parseCheck("#foo"); + } + + @Test + void functions() { + parseCheck("#fn(1,2,3)"); + parseCheck("#fn('hello')"); + } + } + + @Nested + class ElvisAndTernaryOperators { + + @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')"); + } + } + + @Nested + class TypeReferences { + + @Test + void typeReferences01() { + parseCheck("T(java.lang.String)"); + } + + @Test + void typeReferences02() { + parseCheck("T(String)"); + } } - @Test - void literalLong01() { - parseCheck("37L", "37"); - } - - @Test - void literalBoolean02() { - parseCheck("true"); - } - - @Test - void literalBoolean03() { - parseCheck("!true"); - } - - @Test - void literalInteger01() { - parseCheck("1"); - } - - @Test - void literalInteger02() { - parseCheck("1415"); - } - - @Test - void literalString01() { - parseCheck("'hello'"); - } - - @Test - void literalString02() { - parseCheck("'joe bloggs'"); - } - - @Test - void literalString03() { - parseCheck("'Tony''s Pizza'", "'Tony's Pizza'"); - } - - @Test - void literalReal01() { - parseCheck("6.0221415E+23", "6.0221415E23"); - } - - @Test - void literalHex01() { - parseCheck("0x7FFFFFFF", "2147483647"); - } - - @Test - void literalDate01() { - parseCheck("date('1974/08/24')"); - } - - @Test - void literalDate02() { - parseCheck("date('19740824T131030','yyyyMMddTHHmmss')"); - } - - @Test - void literalNull01() { - parseCheck("null"); - } - - // 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 diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java index d4d6dcd5cb28..d7ba60c704bb 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java @@ -209,7 +209,7 @@ void NPE_SPR5673() { checkTemplateParsingError("abc${ } }", "No expression defined within delimiter '${}' at character 3"); 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("}", "}"); checkTemplateParsing("${'hello'} world", "hello world");