Polishing

This commit is contained in:
Sam Brannen
2023-04-04 13:29:00 +02:00
parent 95883b9eb7
commit 6bfc70b61e
3 changed files with 46 additions and 85 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2023 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.
@@ -27,10 +27,11 @@ package org.springframework.expression;
public interface ParserContext {
/**
* Whether the expression being parsed is a template. A template expression
* consists of literal text that can be mixed with evaluatable blocks. Some examples:
* Whether the expression being parsed is a template.
* <p>A template consists of literal text that can be mixed with expressions.
* Some examples:
* <pre class="code">
* Some literal text
* Some literal text
* Hello #{name.firstName}!
* #{3 + 4}
* </pre>

View File

@@ -51,6 +51,7 @@ import org.springframework.expression.MethodResolver;
import org.springframework.expression.ParserContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
@@ -77,6 +78,9 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*/
class SpelReproTests extends AbstractExpressionTests {
private static final ParserContext DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT = new TemplateParserContext("$[", "]");
@Test
void NPE_SPR5661() {
evaluate("joinThreeStrings('a',null,'c')", "anullc", String.class);
@@ -193,8 +197,8 @@ class SpelReproTests extends AbstractExpressionTests {
@Test
void NPE_SPR5673() {
ParserContext hashes = TemplateExpressionParsingTests.HASH_DELIMITED_PARSER_CONTEXT;
ParserContext dollars = TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT;
ParserContext hashes = ParserContext.TEMPLATE_EXPRESSION;
ParserContext dollars = TemplateExpressionParsingTests.DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT;
checkTemplateParsing("abc${'def'} ghi", "abcdef ghi");
@@ -378,7 +382,7 @@ class SpelReproTests extends AbstractExpressionTests {
}
private void checkTemplateParsing(String expression, String expectedValue) {
checkTemplateParsing(expression, TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT, expectedValue);
checkTemplateParsing(expression, TemplateExpressionParsingTests.DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT, expectedValue);
}
private void checkTemplateParsing(String expression, ParserContext context, String expectedValue) {
@@ -388,7 +392,7 @@ class SpelReproTests extends AbstractExpressionTests {
}
private void checkTemplateParsingError(String expression, String expectedMessage) {
checkTemplateParsingError(expression, TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT, expectedMessage);
checkTemplateParsingError(expression, TemplateExpressionParsingTests.DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT, expectedMessage);
}
private void checkTemplateParsingError(String expression, ParserContext context, String expectedMessage) {
@@ -404,22 +408,6 @@ class SpelReproTests extends AbstractExpressionTests {
});
}
private static final ParserContext DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT = new ParserContext() {
@Override
public String getExpressionPrefix() {
return "$[";
}
@Override
public String getExpressionSuffix() {
return "]";
}
@Override
public boolean isTemplate() {
return true;
}
};
@Test
void beanResolution() {
StandardEvaluationContext context = new StandardEvaluationContext(new XX());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -34,89 +34,62 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
*/
public class TemplateExpressionParsingTests extends AbstractExpressionTests {
class TemplateExpressionParsingTests extends AbstractExpressionTests {
public static final ParserContext DEFAULT_TEMPLATE_PARSER_CONTEXT = new ParserContext() {
@Override
public String getExpressionPrefix() {
return "${";
}
@Override
public String getExpressionSuffix() {
return "}";
}
@Override
public boolean isTemplate() {
return true;
}
};
public static final ParserContext HASH_DELIMITED_PARSER_CONTEXT = new ParserContext() {
@Override
public String getExpressionPrefix() {
return "#{";
}
@Override
public String getExpressionSuffix() {
return "}";
}
@Override
public boolean isTemplate() {
return true;
}
};
static final ParserContext DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT = new TemplateParserContext("${", "}");
@Test
public void testParsingSimpleTemplateExpression01() throws Exception {
void parsingSimpleTemplateExpression01() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("hello ${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT);
Expression expr = parser.parseExpression("hello ${'world'}", DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
Object o = expr.getValue();
assertThat(o.toString()).isEqualTo("hello world");
}
@Test
public void testParsingSimpleTemplateExpression02() throws Exception {
void parsingSimpleTemplateExpression02() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("hello ${'to'} you", DEFAULT_TEMPLATE_PARSER_CONTEXT);
Expression expr = parser.parseExpression("hello ${'to'} you", DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
Object o = expr.getValue();
assertThat(o.toString()).isEqualTo("hello to you");
}
@Test
public void testParsingSimpleTemplateExpression03() throws Exception {
void parsingSimpleTemplateExpression03() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("The quick ${'brown'} fox jumped over the ${'lazy'} dog",
DEFAULT_TEMPLATE_PARSER_CONTEXT);
DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
Object o = expr.getValue();
assertThat(o.toString()).isEqualTo("The quick brown fox jumped over the lazy dog");
}
@Test
public void testParsingSimpleTemplateExpression04() throws Exception {
void parsingSimpleTemplateExpression04() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("${'hello'} world", DEFAULT_TEMPLATE_PARSER_CONTEXT);
Expression expr = parser.parseExpression("${'hello'} world", DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
Object o = expr.getValue();
assertThat(o.toString()).isEqualTo("hello world");
expr = parser.parseExpression("", DEFAULT_TEMPLATE_PARSER_CONTEXT);
expr = parser.parseExpression("", DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
o = expr.getValue();
assertThat(o.toString()).isEqualTo("");
expr = parser.parseExpression("abc", DEFAULT_TEMPLATE_PARSER_CONTEXT);
expr = parser.parseExpression("abc", DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
o = expr.getValue();
assertThat(o.toString()).isEqualTo("abc");
expr = parser.parseExpression("abc", DEFAULT_TEMPLATE_PARSER_CONTEXT);
expr = parser.parseExpression("abc", DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
o = expr.getValue((Object)null);
assertThat(o.toString()).isEqualTo("abc");
}
@Test
public void testCompositeStringExpression() throws Exception {
void compositeStringExpression() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
Expression ex = parser.parseExpression("hello ${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT);
Expression ex = parser.parseExpression("hello ${'world'}", DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
assertThat(ex.getValue()).isInstanceOf(String.class).isEqualTo("hello world");
assertThat(ex.getValue(String.class)).isInstanceOf(String.class).isEqualTo("hello world");
assertThat(ex.getValue((Object)null, String.class)).isInstanceOf(String.class).isEqualTo("hello world");
@@ -154,15 +127,15 @@ public class TemplateExpressionParsingTests extends AbstractExpressionTests {
static class Rooty {}
@Test
public void testNestedExpressions() throws Exception {
void nestedExpressions() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
// treat the nested ${..} as a part of the expression
Expression ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#this<5]} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
Expression ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#this<5]} world",DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
String s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
assertThat(s).isEqualTo("hello 4 world");
// not a useful expression but tests nested expression syntax that clashes with template prefix/suffix
ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#root.listOfNumbersUpToTen.$[#this%2==1]==3]} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#root.listOfNumbersUpToTen.$[#this%2==1]==3]} world",DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
assertThat(ex.getClass()).isEqualTo(CompositeStringExpression.class);
CompositeStringExpression cse = (CompositeStringExpression)ex;
Expression[] exprs = cse.getExpressions();
@@ -171,60 +144,59 @@ public class TemplateExpressionParsingTests extends AbstractExpressionTests {
s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
assertThat(s).isEqualTo("hello world");
ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#this<5]} ${listOfNumbersUpToTen.$[#this>5]} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#this<5]} ${listOfNumbersUpToTen.$[#this>5]} world",DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
assertThat(s).isEqualTo("hello 4 10 world");
assertThatExceptionOfType(ParseException.class).isThrownBy(() ->
parser.parseExpression("hello ${listOfNumbersUpToTen.$[#this<5]} ${listOfNumbersUpToTen.$[#this>5] world",DEFAULT_TEMPLATE_PARSER_CONTEXT))
parser.parseExpression("hello ${listOfNumbersUpToTen.$[#this<5]} ${listOfNumbersUpToTen.$[#this>5] world",DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT))
.satisfies(pex -> assertThat(pex.getSimpleMessage()).isEqualTo("No ending suffix '}' for expression starting at character 41: ${listOfNumbersUpToTen.$[#this>5] world"));
assertThatExceptionOfType(ParseException.class).isThrownBy(() ->
parser.parseExpression("hello ${listOfNumbersUpToTen.$[#root.listOfNumbersUpToTen.$[#this%2==1==3]} world",DEFAULT_TEMPLATE_PARSER_CONTEXT))
parser.parseExpression("hello ${listOfNumbersUpToTen.$[#root.listOfNumbersUpToTen.$[#this%2==1==3]} world",DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT))
.satisfies(pex -> assertThat(pex.getSimpleMessage()).isEqualTo("Found closing '}' at position 74 but most recent opening is '[' at position 30"));
}
@Test
public void testClashingWithSuffixes() throws Exception {
void clashingWithSuffixes() throws Exception {
// Just wanting to use the prefix or suffix within the template:
Expression ex = parser.parseExpression("hello ${3+4} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
Expression ex = parser.parseExpression("hello ${3+4} world",DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
String s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
assertThat(s).isEqualTo("hello 7 world");
ex = parser.parseExpression("hello ${3+4} wo${'${'}rld",DEFAULT_TEMPLATE_PARSER_CONTEXT);
ex = parser.parseExpression("hello ${3+4} wo${'${'}rld",DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
assertThat(s).isEqualTo("hello 7 wo${rld");
ex = parser.parseExpression("hello ${3+4} wo}rld",DEFAULT_TEMPLATE_PARSER_CONTEXT);
ex = parser.parseExpression("hello ${3+4} wo}rld",DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT);
s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
assertThat(s).isEqualTo("hello 7 wo}rld");
}
@Test
public void testParsingNormalExpressionThroughTemplateParser() throws Exception {
void parsingNormalExpressionThroughTemplateParser() throws Exception {
Expression expr = parser.parseExpression("1+2+3");
assertThat(expr.getValue()).isEqualTo(6);
}
@Test
public void testErrorCases() throws Exception {
void errorCases() throws Exception {
assertThatExceptionOfType(ParseException.class).isThrownBy(() ->
parser.parseExpression("hello ${'world'", DEFAULT_TEMPLATE_PARSER_CONTEXT))
parser.parseExpression("hello ${'world'", DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT))
.satisfies(pex -> {
assertThat(pex.getSimpleMessage()).isEqualTo("No ending suffix '}' for expression starting at character 6: ${'world'");
assertThat(pex.getExpressionString()).isEqualTo("hello ${'world'");
});
assertThatExceptionOfType(ParseException.class).isThrownBy(() ->
parser.parseExpression("hello ${'wibble'${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT))
parser.parseExpression("hello ${'wibble'${'world'}", DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT))
.satisfies(pex -> assertThat(pex.getSimpleMessage()).isEqualTo("No ending suffix '}' for expression starting at character 6: ${'wibble'${'world'}"));
assertThatExceptionOfType(ParseException.class).isThrownBy(() ->
parser.parseExpression("hello ${} world", DEFAULT_TEMPLATE_PARSER_CONTEXT))
parser.parseExpression("hello ${} world", DOLLAR_SIGN_TEMPLATE_PARSER_CONTEXT))
.satisfies(pex -> assertThat(pex.getSimpleMessage()).isEqualTo("No expression defined within delimiter '${}' at character 6"));
}
@Test
public void testTemplateParserContext() {
void templateParserContext() {
TemplateParserContext tpc = new TemplateParserContext("abc","def");
assertThat(tpc.getExpressionPrefix()).isEqualTo("abc");
assertThat(tpc.getExpressionSuffix()).isEqualTo("def");