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");