1616
1717package org .springframework .util ;
1818
19- import java .util .Properties ;
19+ import java .util .Map ;
2020import java .util .stream .Stream ;
2121
2222import org .junit .jupiter .api .Nested ;
3636import static org .mockito .BDDMockito .given ;
3737import static org .mockito .Mockito .inOrder ;
3838import static org .mockito .Mockito .mock ;
39- import static org .mockito .Mockito .verify ;
4039import static org .mockito .Mockito .verifyNoMoreInteractions ;
4140
4241/**
4342 * Tests for {@link PlaceholderParser}.
4443 *
4544 * @author Stephane Nicoll
45+ * @author Sam Brannen
4646 */
4747class PlaceholderParserTests {
4848
@@ -54,11 +54,11 @@ class OnlyPlaceholderTests {
5454 @ ParameterizedTest (name = "{0} -> {1}" )
5555 @ MethodSource ("placeholders" )
5656 void placeholderIsReplaced (String text , String expected ) {
57- Properties properties = new Properties ();
58- properties . setProperty ( "firstName" , "John" );
59- properties . setProperty ( "nested0" , "first" );
60- properties . setProperty ( "nested1" , "Name" );
61- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
57+ Map < String , String > properties = Map . of (
58+ "firstName" , "John" ,
59+ "nested0" , "first" ,
60+ "nested1" , "Name" );
61+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
6262 }
6363
6464 static Stream <Arguments > placeholders () {
@@ -79,13 +79,13 @@ static Stream<Arguments> placeholders() {
7979 @ ParameterizedTest (name = "{0} -> {1}" )
8080 @ MethodSource ("nestedPlaceholders" )
8181 void nestedPlaceholdersAreReplaced (String text , String expected ) {
82- Properties properties = new Properties ();
83- properties . setProperty ( "p1" , "v1" );
84- properties . setProperty ( "p2" , "v2" );
85- properties . setProperty ( "p3" , "${p1}:${p2}" ); // nested placeholders
86- properties . setProperty ( "p4" , "${p3}" ); // deeply nested placeholders
87- properties . setProperty ( "p5" , "${p1}:${p2}:${bogus}" ); // unresolvable placeholder
88- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
82+ Map < String , String > properties = Map . of (
83+ "p1" , "v1" ,
84+ "p2" , "v2" ,
85+ "p3" , "${p1}:${p2}" , // nested placeholders
86+ "p4" , "${p3}" , // deeply nested placeholders
87+ "p5" , "${p1}:${p2}:${bogus}" ); // unresolvable placeholder
88+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
8989 }
9090
9191 static Stream <Arguments > nestedPlaceholders () {
@@ -101,69 +101,59 @@ static Stream<Arguments> nestedPlaceholders() {
101101 @ Test
102102 void parseWithSinglePlaceholder () {
103103 PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "John" );
104- assertThat (this .parser .replacePlaceholders ("${firstName}" , resolver ))
105- .isEqualTo ("John" );
106- verify (resolver ).resolvePlaceholder ("firstName" );
107- verifyNoMoreInteractions (resolver );
104+ assertThat (this .parser .replacePlaceholders ("${firstName}" , resolver )).isEqualTo ("John" );
105+ verifyPlaceholderResolutions (resolver , "firstName" );
108106 }
109107
110108 @ Test
111109 void parseWithPlaceholderAndPrefixText () {
112110 PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "John" );
113- assertThat (this .parser .replacePlaceholders ("This is ${firstName}" , resolver ))
114- .isEqualTo ("This is John" );
115- verify (resolver ).resolvePlaceholder ("firstName" );
116- verifyNoMoreInteractions (resolver );
111+ assertThat (this .parser .replacePlaceholders ("This is ${firstName}" , resolver )).isEqualTo ("This is John" );
112+ verifyPlaceholderResolutions (resolver , "firstName" );
117113 }
118114
119115 @ Test
120116 void parseWithMultiplePlaceholdersAndText () {
121117 PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "John" , "lastName" , "Smith" );
122118 assertThat (this .parser .replacePlaceholders ("User: ${firstName} - ${lastName}." , resolver ))
123119 .isEqualTo ("User: John - Smith." );
124- verify (resolver ).resolvePlaceholder ("firstName" );
125- verify (resolver ).resolvePlaceholder ("lastName" );
126- verifyNoMoreInteractions (resolver );
120+ verifyPlaceholderResolutions (resolver , "firstName" , "lastName" );
127121 }
128122
129123 @ Test
130124 void parseWithNestedPlaceholderInKey () {
131- PlaceholderResolver resolver = mockPlaceholderResolver (
132- "nested" , "Name" , "firstName" , "John" );
133- assertThat (this .parser .replacePlaceholders ("${first${nested}}" , resolver ))
134- .isEqualTo ("John" );
125+ PlaceholderResolver resolver = mockPlaceholderResolver ("nested" , "Name" , "firstName" , "John" );
126+ assertThat (this .parser .replacePlaceholders ("${first${nested}}" , resolver )).isEqualTo ("John" );
135127 verifyPlaceholderResolutions (resolver , "nested" , "firstName" );
136128 }
137129
138130 @ Test
139131 void parseWithMultipleNestedPlaceholdersInKey () {
140- PlaceholderResolver resolver = mockPlaceholderResolver (
141- "nested0" , "first" , "nested1" , "Name" , "firstName" , "John" );
142- assertThat (this .parser .replacePlaceholders ("${${nested0}${nested1}}" , resolver ))
143- .isEqualTo ("John" );
132+ PlaceholderResolver resolver = mockPlaceholderResolver ("nested0" , "first" , "nested1" , "Name" , "firstName" , "John" );
133+ assertThat (this .parser .replacePlaceholders ("${${nested0}${nested1}}" , resolver )).isEqualTo ("John" );
144134 verifyPlaceholderResolutions (resolver , "nested0" , "nested1" , "firstName" );
145135 }
146136
147137 @ Test
148- void placeholdersWithSeparatorAreHandledAsIs () {
138+ void placeholderValueContainingSeparatorIsHandledAsIs () {
149139 PlaceholderResolver resolver = mockPlaceholderResolver ("my:test" , "value" );
150140 assertThat (this .parser .replacePlaceholders ("${my:test}" , resolver )).isEqualTo ("value" );
151141 verifyPlaceholderResolutions (resolver , "my:test" );
152142 }
153143
154144 @ Test
155145 void placeholdersWithoutEscapeCharAreNotEscaped () {
156- PlaceholderResolver resolver = mockPlaceholderResolver ("test " , "value " );
157- assertThat (this .parser .replacePlaceholders ("\\ ${test }" , resolver )).isEqualTo ("\\ value " );
158- verifyPlaceholderResolutions (resolver , "test " );
146+ PlaceholderResolver resolver = mockPlaceholderResolver ("p1 " , "v1 " );
147+ assertThat (this .parser .replacePlaceholders ("\\ ${p1 }" , resolver )).isEqualTo ("\\ v1 " );
148+ verifyPlaceholderResolutions (resolver , "p1 " );
159149 }
160150
161151 @ Test
162- void textWithInvalidPlaceholderIsMerged () {
152+ void textWithInvalidPlaceholderSyntaxIsMerged () {
163153 String text = "test${of${with${and${" ;
164154 ParsedValue parsedValue = this .parser .parse (text );
165- assertThat (parsedValue .parts ()).singleElement ().isInstanceOfSatisfying (
166- TextPart . class , textPart -> assertThat (textPart .text ()).isEqualTo (text ));
155+ assertThat (parsedValue .parts ()).singleElement ().isInstanceOfSatisfying (TextPart . class ,
156+ textPart -> assertThat (textPart .text ()).isEqualTo (text ));
167157 }
168158
169159 }
@@ -176,11 +166,11 @@ class DefaultValueTests {
176166 @ ParameterizedTest (name = "{0} -> {1}" )
177167 @ MethodSource ("placeholders" )
178168 void placeholderIsReplaced (String text , String expected ) {
179- Properties properties = new Properties ();
180- properties . setProperty ( "firstName" , "John" );
181- properties . setProperty ( "nested0" , "first" );
182- properties . setProperty ( "nested1" , "Name" );
183- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
169+ Map < String , String > properties = Map . of (
170+ "firstName" , "John" ,
171+ "nested0" , "first" ,
172+ "nested1" , "Name" );
173+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
184174 }
185175
186176 static Stream <Arguments > placeholders () {
@@ -199,14 +189,14 @@ static Stream<Arguments> placeholders() {
199189 @ ParameterizedTest (name = "{0} -> {1}" )
200190 @ MethodSource ("nestedPlaceholders" )
201191 void nestedPlaceholdersAreReplaced (String text , String expected ) {
202- Properties properties = new Properties ();
203- properties . setProperty ( "p1" , "v1" );
204- properties . setProperty ( "p2" , "v2" );
205- properties . setProperty ( "p3" , "${p1}:${p2}" ); // nested placeholders
206- properties . setProperty ( "p4" , "${p3}" ); // deeply nested placeholders
207- properties . setProperty ( "p5" , "${p1}:${p2}:${bogus}" ); // unresolvable placeholder
208- properties . setProperty ( "p6" , "${p1}:${p2}:${bogus:def}" ); // unresolvable w/ default
209- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
192+ Map < String , String > properties = Map . of (
193+ "p1" , "v1" ,
194+ "p2" , "v2" ,
195+ "p3" , "${p1}:${p2}" , // nested placeholders
196+ "p4" , "${p3}" , // deeply nested placeholders
197+ "p5" , "${p1}:${p2}:${bogus}" , // unresolvable placeholder
198+ "p6" , "${p1}:${p2}:${bogus:def}" ); // unresolvable w/ default
199+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
210200 }
211201
212202 static Stream <Arguments > nestedPlaceholders () {
@@ -225,11 +215,11 @@ static Stream<Arguments> nestedPlaceholders() {
225215 @ ParameterizedTest (name = "{0} -> {1}" )
226216 @ MethodSource ("exactMatchPlaceholders" )
227217 void placeholdersWithExactMatchAreConsidered (String text , String expected ) {
228- Properties properties = new Properties ();
229- properties . setProperty ( "prefix://my-service" , "example-service" );
230- properties . setProperty ( "px" , "prefix" );
231- properties . setProperty ( "p1" , "${prefix://my-service}" );
232- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
218+ Map < String , String > properties = Map . of (
219+ "prefix://my-service" , "example-service" ,
220+ "px" , "prefix" ,
221+ "p1" , "${prefix://my-service}" );
222+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
233223 }
234224
235225 static Stream <Arguments > exactMatchPlaceholders () {
@@ -242,74 +232,55 @@ static Stream<Arguments> exactMatchPlaceholders() {
242232 @ Test
243233 void parseWithKeyEqualsToText () {
244234 PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "Steve" );
245- assertThat (this .parser .replacePlaceholders ("${firstName}" , resolver ))
246- .isEqualTo ("Steve" );
235+ assertThat (this .parser .replacePlaceholders ("${firstName}" , resolver )).isEqualTo ("Steve" );
247236 verifyPlaceholderResolutions (resolver , "firstName" );
248237 }
249238
250239 @ Test
251240 void parseWithHardcodedFallback () {
252241 PlaceholderResolver resolver = mockPlaceholderResolver ();
253- assertThat (this .parser .replacePlaceholders ("${firstName:Steve}" , resolver ))
254- .isEqualTo ("Steve" );
242+ assertThat (this .parser .replacePlaceholders ("${firstName:Steve}" , resolver )).isEqualTo ("Steve" );
255243 verifyPlaceholderResolutions (resolver , "firstName:Steve" , "firstName" );
256244 }
257245
258246 @ Test
259247 void parseWithNestedPlaceholderInKeyUsingFallback () {
260248 PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "John" );
261- assertThat (this .parser .replacePlaceholders ("${first${invalid:Name}}" , resolver ))
262- .isEqualTo ("John" );
249+ assertThat (this .parser .replacePlaceholders ("${first${invalid:Name}}" , resolver )).isEqualTo ("John" );
263250 verifyPlaceholderResolutions (resolver , "invalid:Name" , "invalid" , "firstName" );
264251 }
265252
266253 @ Test
267254 void parseWithFallbackUsingPlaceholder () {
268255 PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "John" );
269- assertThat (this .parser .replacePlaceholders ("${invalid:${firstName}}" , resolver ))
270- .isEqualTo ("John" );
256+ assertThat (this .parser .replacePlaceholders ("${invalid:${firstName}}" , resolver )).isEqualTo ("John" );
271257 verifyPlaceholderResolutions (resolver , "invalid" , "firstName" );
272258 }
273259
274260 }
275261
276- @ Nested // Tests with the use of the escape character
262+ /**
263+ * Tests that use the escape character.
264+ */
265+ @ Nested
277266 class EscapedTests {
278267
279268 private final PlaceholderParser parser = new PlaceholderParser ("${" , "}" , ":" , '\\' , true );
280269
281- @ ParameterizedTest (name = "{0} -> {1}" )
282- @ MethodSource ("escapedInNestedPlaceholders" )
283- void escapedSeparatorInNestedPlaceholder (String text , String expected ) {
284- Properties properties = new Properties ();
285- properties .setProperty ("app.environment" , "qa" );
286- properties .setProperty ("app.service" , "protocol" );
287- properties .setProperty ("protocol://host/qa/name" , "protocol://example.com/qa/name" );
288- properties .setProperty ("service/host/qa/name" , "https://example.com/qa/name" );
289- properties .setProperty ("service/host/qa/name:value" , "https://example.com/qa/name-value" );
290- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
291- }
292-
293- static Stream <Arguments > escapedInNestedPlaceholders () {
294- return Stream .of (
295- Arguments .of ("${protocol\\ ://host/${app.environment}/name}" , "protocol://example.com/qa/name" ),
296- Arguments .of ("${${app.service}\\ ://host/${app.environment}/name}" , "protocol://example.com/qa/name" ),
297- Arguments .of ("${service/host/${app.environment}/name:\\ value}" , "https://example.com/qa/name" ),
298- Arguments .of ("${service/host/${name\\ :value}/}" , "${service/host/${name:value}/}" ));
299- }
300-
301270 @ ParameterizedTest (name = "{0} -> {1}" )
302271 @ MethodSource ("escapedPlaceholders" )
303272 void escapedPlaceholderIsNotReplaced (String text , String expected ) {
304- PlaceholderResolver resolver = mockPlaceholderResolver (
305- "firstName" , "John" , "nested0" , "first" , "nested1" , "Name" ,
273+ Map < String , String > properties = Map . of (
274+ "firstName" , "John" ,
306275 "${test}" , "John" ,
307- "p1" , "v1" , "p2" , "\\ ${p1:default}" , "p3" , "${p2}" ,
276+ "p1" , "v1" ,
277+ "p2" , "\\ ${p1:default}" ,
278+ "p3" , "${p2}" ,
308279 "p4" , "adc${p0:\\ ${p1}}" ,
309280 "p5" , "adc${\\ ${p0}:${p1}}" ,
310281 "p6" , "adc${p0:def\\ ${p1}}" ,
311282 "p7" , "adc\\ ${" );
312- assertThat (this .parser .replacePlaceholders (text , resolver )).isEqualTo (expected );
283+ assertThat (this .parser .replacePlaceholders (text , properties :: get )).isEqualTo (expected );
313284 }
314285
315286 static Stream <Arguments > escapedPlaceholders () {
@@ -324,18 +295,15 @@ static Stream<Arguments> escapedPlaceholders() {
324295 Arguments .of ("${p4}" , "adc${p1}" ),
325296 Arguments .of ("${p5}" , "adcv1" ),
326297 Arguments .of ("${p6}" , "adcdef${p1}" ),
327- Arguments .of ("${p7}" , "adc\\ ${" ));
328-
298+ Arguments .of ("${p7}" , "adc\\ ${" )
299+ );
329300 }
330301
331302 @ ParameterizedTest (name = "{0} -> {1}" )
332303 @ MethodSource ("escapedSeparators" )
333304 void escapedSeparatorIsNotReplaced (String text , String expected ) {
334- Properties properties = new Properties ();
335- properties .setProperty ("first:Name" , "John" );
336- properties .setProperty ("nested0" , "first" );
337- properties .setProperty ("nested1" , "Name" );
338- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
305+ Map <String , String > properties = Map .of ("first:Name" , "John" );
306+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
339307 }
340308
341309 static Stream <Arguments > escapedSeparators () {
@@ -345,6 +313,26 @@ static Stream<Arguments> escapedSeparators() {
345313 );
346314 }
347315
316+ @ ParameterizedTest (name = "{0} -> {1}" )
317+ @ MethodSource ("escapedSeparatorsInNestedPlaceholders" )
318+ void escapedSeparatorInNestedPlaceholderIsNotReplaced (String text , String expected ) {
319+ Map <String , String > properties = Map .of (
320+ "app.environment" , "qa" ,
321+ "app.service" , "protocol" ,
322+ "protocol://host/qa/name" , "protocol://example.com/qa/name" ,
323+ "service/host/qa/name" , "https://example.com/qa/name" ,
324+ "service/host/qa/name:value" , "https://example.com/qa/name-value" );
325+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
326+ }
327+
328+ static Stream <Arguments > escapedSeparatorsInNestedPlaceholders () {
329+ return Stream .of (
330+ Arguments .of ("${protocol\\ ://host/${app.environment}/name}" , "protocol://example.com/qa/name" ),
331+ Arguments .of ("${${app.service}\\ ://host/${app.environment}/name}" , "protocol://example.com/qa/name" ),
332+ Arguments .of ("${service/host/${app.environment}/name:\\ value}" , "https://example.com/qa/name" ),
333+ Arguments .of ("${service/host/${name\\ :value}/}" , "${service/host/${name:value}/}" ));
334+ }
335+
348336 }
349337
350338 @ Nested
@@ -354,34 +342,38 @@ class ExceptionTests {
354342
355343 @ Test
356344 void textWithCircularReference () {
357- PlaceholderResolver resolver = mockPlaceholderResolver ("pL" , "${pR}" , "pR" , "${pL}" );
358- assertThatThrownBy (() -> this .parser .replacePlaceholders ("${pL}" , resolver ))
345+ Map <String , String > properties = Map .of (
346+ "pL" , "${pR}" ,
347+ "pR" , "${pL}" );
348+ assertThatThrownBy (() -> this .parser .replacePlaceholders ("${pL}" , properties ::get ))
359349 .isInstanceOf (PlaceholderResolutionException .class )
360350 .hasMessage ("Circular placeholder reference 'pL' in value \" ${pL}\" <-- \" ${pR}\" <-- \" ${pL}\" " );
361351 }
362352
363353 @ Test
364354 void unresolvablePlaceholderIsReported () {
365- PlaceholderResolver resolver = mockPlaceholderResolver ();
366355 assertThatExceptionOfType (PlaceholderResolutionException .class )
367- .isThrownBy (() -> this .parser .replacePlaceholders ("${bogus}" , resolver ))
368- .withMessage ("Could not resolve placeholder 'bogus' in value \" ${bogus}\" " )
356+ .isThrownBy (() -> this .parser .replacePlaceholders ("X ${bogus}Z " , placeholderName -> null ))
357+ .withMessage ("Could not resolve placeholder 'bogus' in value \" X ${bogus}Z \" " )
369358 .withNoCause ();
370359 }
371360
372361 @ Test
373362 void unresolvablePlaceholderInNestedPlaceholderIsReportedWithChain () {
374- PlaceholderResolver resolver = mockPlaceholderResolver ("p1" , "v1" , "p2" , "v2" ,
363+ Map <String , String > properties = Map .of (
364+ "p1" , "v1" ,
365+ "p2" , "v2" ,
375366 "p3" , "${p1}:${p2}:${bogus}" );
376367 assertThatExceptionOfType (PlaceholderResolutionException .class )
377- .isThrownBy (() -> this .parser .replacePlaceholders ("${p3}" , resolver ))
368+ .isThrownBy (() -> this .parser .replacePlaceholders ("${p3}" , properties :: get ))
378369 .withMessage ("Could not resolve placeholder 'bogus' in value \" ${p1}:${p2}:${bogus}\" <-- \" ${p3}\" " )
379370 .withNoCause ();
380371 }
381372
382373 }
383374
384- PlaceholderResolver mockPlaceholderResolver (String ... pairs ) {
375+
376+ private static PlaceholderResolver mockPlaceholderResolver (String ... pairs ) {
385377 if (pairs .length % 2 == 1 ) {
386378 throw new IllegalArgumentException ("size must be even, it is a set of key=value pairs" );
387379 }
@@ -394,7 +386,7 @@ PlaceholderResolver mockPlaceholderResolver(String... pairs) {
394386 return resolver ;
395387 }
396388
397- void verifyPlaceholderResolutions (PlaceholderResolver mock , String ... placeholders ) {
389+ private static void verifyPlaceholderResolutions (PlaceholderResolver mock , String ... placeholders ) {
398390 InOrder ordered = inOrder (mock );
399391 for (String placeholder : placeholders ) {
400392 ordered .verify (mock ).resolvePlaceholder (placeholder );
0 commit comments