Skip to content

Commit 8792b39

Browse files
committed
Merge branch '6.2.x'
2 parents 3819e2d + 449b85f commit 8792b39

File tree

3 files changed

+98
-125
lines changed

3 files changed

+98
-125
lines changed

spring-core/src/main/java/org/springframework/util/PlaceholderParser.java

Lines changed: 85 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.HashSet;
21-
import java.util.LinkedList;
2221
import java.util.List;
2322
import java.util.Map;
2423
import java.util.Set;
@@ -36,10 +35,10 @@
3635
* that can be resolved using a {@link PlaceholderResolver PlaceholderResolver},
3736
* <code>${</code> the prefix, and <code>}</code> the suffix.
3837
*
39-
* <p>A placeholder can also have a default value if its key does not represent a
40-
* known property. The default value is separated from the key using a
41-
* {@code separator}. For instance {@code ${name:John}} resolves to {@code John} if
42-
* the placeholder resolver does not provide a value for the {@code name}
38+
* <p>A placeholder can also have a default value if its key does not represent
39+
* a known property. The default value is separated from the key using a
40+
* {@code separator}. For instance {@code ${name:John}} resolves to {@code John}
41+
* if the placeholder resolver does not provide a value for the {@code name}
4342
* property.
4443
*
4544
* <p>Placeholders can also have a more complex structure, and the resolution of
@@ -50,13 +49,14 @@
5049
* must be rendered as is, the placeholder can be escaped using an {@code escape}
5150
* character. For instance {@code \${name}} resolves as {@code ${name}}.
5251
*
53-
* <p>The prefix, suffix, separator, and escape characters are configurable. Only
54-
* the prefix and suffix are mandatory, and the support for default values or
55-
* escaping is conditional on providing non-null values for them.
52+
* <p>The prefix, suffix, separator, and escape characters are configurable.
53+
* Only the prefix and suffix are mandatory, and the support for default values
54+
* or escaping is conditional on providing non-null values for them.
5655
*
5756
* <p>This parser makes sure to resolves placeholders as lazily as possible.
5857
*
5958
* @author Stephane Nicoll
59+
* @author Juergen Hoeller
6060
* @since 6.2
6161
*/
6262
final class PlaceholderParser {
@@ -118,51 +118,47 @@ final class PlaceholderParser {
118118
* @return the supplied value with placeholders replaced inline
119119
*/
120120
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
121-
Assert.notNull(value, "'value' must not be null");
122-
ParsedValue parsedValue = parse(value);
121+
List<Part> parts = parse(value, false);
122+
if (parts == null) {
123+
return value;
124+
}
125+
ParsedValue parsedValue = new ParsedValue(value, parts);
123126
PartResolutionContext resolutionContext = new PartResolutionContext(placeholderResolver,
124127
this.prefix, this.suffix, this.ignoreUnresolvablePlaceholders,
125128
candidate -> parse(candidate, false));
126129
return parsedValue.resolve(resolutionContext);
127130
}
128131

129-
/**
130-
* Parse the specified value.
131-
* @param value the value containing the placeholders to be replaced
132-
* @return the different parts that have been identified
133-
*/
134-
ParsedValue parse(String value) {
135-
List<Part> parts = parse(value, false);
136-
return new ParsedValue(value, parts);
137-
}
138-
139-
private List<Part> parse(String value, boolean inPlaceholder) {
140-
LinkedList<Part> parts = new LinkedList<>();
132+
private @Nullable List<Part> parse(String value, boolean inPlaceholder) {
141133
int startIndex = nextStartPrefix(value, 0);
142134
if (startIndex == -1) {
143-
Part part = (inPlaceholder ? createSimplePlaceholderPart(value) : new TextPart(value));
144-
parts.add(part);
145-
return parts;
135+
return null;
146136
}
137+
List<Part> parts = new ArrayList<>(4);
147138
int position = 0;
148139
while (startIndex != -1) {
149140
int endIndex = nextValidEndPrefix(value, startIndex);
150-
if (endIndex == -1) { // Not a valid placeholder, consume the prefix and continue
141+
if (endIndex == -1) { // Not a valid placeholder, consume the prefix and continue
151142
addText(value, position, startIndex + this.prefix.length(), parts);
152143
position = startIndex + this.prefix.length();
153144
startIndex = nextStartPrefix(value, position);
154145
}
155-
else if (isEscaped(value, startIndex)) { // Not a valid index, accumulate and skip the escape character
146+
else if (isEscaped(value, startIndex)) { // Not a valid index, accumulate and skip the escape character
156147
addText(value, position, startIndex - 1, parts);
157148
addText(value, startIndex, startIndex + this.prefix.length(), parts);
158149
position = startIndex + this.prefix.length();
159150
startIndex = nextStartPrefix(value, position);
160151
}
161-
else { // Found valid placeholder, recursive parsing
152+
else { // Found valid placeholder, recursive parsing
162153
addText(value, position, startIndex, parts);
163154
String placeholder = value.substring(startIndex + this.prefix.length(), endIndex);
164155
List<Part> placeholderParts = parse(placeholder, true);
165-
parts.addAll(placeholderParts);
156+
if (placeholderParts == null) {
157+
parts.add(createSimplePlaceholderPart(placeholder));
158+
}
159+
else {
160+
parts.addAll(placeholderParts);
161+
}
166162
startIndex = nextStartPrefix(value, endIndex + this.suffix.length());
167163
position = endIndex + this.suffix.length();
168164
}
@@ -239,29 +235,6 @@ private ParsedSection parseSection(String value) {
239235
return new ParsedSection(buffer.toString(), null);
240236
}
241237

242-
private static void addText(String value, int start, int end, LinkedList<Part> parts) {
243-
if (start > end) {
244-
return;
245-
}
246-
String text = value.substring(start, end);
247-
if (!text.isEmpty()) {
248-
if (!parts.isEmpty()) {
249-
Part current = parts.removeLast();
250-
if (current instanceof TextPart textPart) {
251-
parts.add(new TextPart(textPart.text() + text));
252-
}
253-
else {
254-
parts.add(current);
255-
parts.add(new TextPart(text));
256-
}
257-
}
258-
else {
259-
parts.add(new TextPart(text));
260-
}
261-
}
262-
}
263-
264-
265238
private int nextStartPrefix(String value, int index) {
266239
return value.indexOf(this.prefix, index);
267240
}
@@ -294,15 +267,46 @@ private boolean isEscaped(String value, int index) {
294267
return (this.escape != null && index > 0 && value.charAt(index - 1) == this.escape);
295268
}
296269

297-
record ParsedSection(String key, @Nullable String fallback) {
270+
private static void addText(String value, int start, int end, List<Part> parts) {
271+
if (start >= end) {
272+
return;
273+
}
274+
String text = value.substring(start, end);
275+
if (!parts.isEmpty() && parts.get(parts.size() - 1) instanceof TextPart textPart) {
276+
parts.set(parts.size() - 1, new TextPart(textPart.text() + text));
277+
}
278+
else {
279+
parts.add(new TextPart(text));
280+
}
281+
}
282+
283+
284+
/**
285+
* A representation of the parsing of an input string.
286+
* @param text the raw input string
287+
* @param parts the parts that appear in the string, in order
288+
*/
289+
private record ParsedValue(String text, List<Part> parts) {
298290

291+
public String resolve(PartResolutionContext resolutionContext) {
292+
try {
293+
return Part.resolveAll(this.parts, resolutionContext);
294+
}
295+
catch (PlaceholderResolutionException ex) {
296+
throw ex.withValue(this.text);
297+
}
298+
}
299+
}
300+
301+
302+
private record ParsedSection(String key, @Nullable String fallback) {
299303
}
300304

301305

302306
/**
303307
* Provide the necessary context to handle and resolve underlying placeholders.
304308
*/
305-
static class PartResolutionContext implements PlaceholderResolver {
309+
private static class PartResolutionContext implements PlaceholderResolver {
306310

307311
private final String prefix;
308312

@@ -316,7 +320,6 @@ static class PartResolutionContext implements PlaceholderResolver {
316320

317321
private @Nullable Set<String> visitedPlaceholders;
318322

319-
320323
PartResolutionContext(PlaceholderResolver resolver, String prefix, String suffix,
321324
boolean ignoreUnresolvablePlaceholders, Function<String, List<Part>> parser) {
322325
this.prefix = prefix;
@@ -348,7 +351,7 @@ private String toPlaceholderText(String text) {
348351
return this.prefix + text + this.suffix;
349352
}
350353

351-
public List<Part> parse(String text) {
354+
public @Nullable List<Part> parse(String text) {
352355
return this.parser.apply(text);
353356
}
354357

@@ -363,17 +366,17 @@ public void flagPlaceholderAsVisited(String placeholder) {
363366
}
364367

365368
public void removePlaceholder(String placeholder) {
366-
Assert.state(this.visitedPlaceholders != null, "Visited placeholders must not be null");
367-
this.visitedPlaceholders.remove(placeholder);
369+
if (this.visitedPlaceholders != null) {
370+
this.visitedPlaceholders.remove(placeholder);
371+
}
368372
}
369-
370373
}
371374

372375

373376
/**
374377
* A part is a section of a String containing placeholders to replace.
375378
*/
376-
interface Part {
379+
private interface Part {
377380

378381
/**
379382
* Resolve this part using the specified {@link PartResolutionContext}.
@@ -404,30 +407,12 @@ static String resolveAll(Iterable<Part> parts, PartResolutionContext resolutionC
404407
}
405408

406409

407-
/**
408-
* A representation of the parsing of an input string.
409-
* @param text the raw input string
410-
* @param parts the parts that appear in the string, in order
411-
*/
412-
record ParsedValue(String text, List<Part> parts) {
413-
414-
public String resolve(PartResolutionContext resolutionContext) {
415-
try {
416-
return Part.resolveAll(this.parts, resolutionContext);
417-
}
418-
catch (PlaceholderResolutionException ex) {
419-
throw ex.withValue(this.text);
420-
}
421-
}
422-
}
423-
424-
425410
/**
426411
* A base {@link Part} implementation.
427412
*/
428-
abstract static class AbstractPart implements Part {
413+
private abstract static class AbstractPart implements Part {
429414

430-
private final String text;
415+
final String text;
431416

432417
protected AbstractPart(String text) {
433418
this.text = text;
@@ -449,37 +434,27 @@ public String text() {
449434
*/
450435
protected @Nullable String resolveRecursively(PartResolutionContext resolutionContext, String key) {
451436
String resolvedValue = resolutionContext.resolvePlaceholder(key);
452-
if (resolvedValue != null) {
453-
resolutionContext.flagPlaceholderAsVisited(key);
454-
// Let's check if we need to recursively resolve that value
455-
List<Part> nestedParts = resolutionContext.parse(resolvedValue);
456-
String value = toText(nestedParts);
457-
if (!isTextOnly(nestedParts)) {
458-
value = new ParsedValue(resolvedValue, nestedParts).resolve(resolutionContext);
459-
}
460-
resolutionContext.removePlaceholder(key);
461-
return value;
437+
if (resolvedValue == null) {
438+
// Not found
439+
return null;
462440
}
463-
// Not found
464-
return null;
465-
}
466-
467-
private boolean isTextOnly(List<Part> parts) {
468-
return parts.stream().allMatch(TextPart.class::isInstance);
469-
}
470-
471-
private String toText(List<Part> parts) {
472-
StringBuilder sb = new StringBuilder();
473-
parts.forEach(part -> sb.append(part.text()));
474-
return sb.toString();
441+
// Let's check if we need to recursively resolve that value
442+
List<Part> nestedParts = resolutionContext.parse(resolvedValue);
443+
if (nestedParts == null) {
444+
return resolvedValue;
445+
}
446+
resolutionContext.flagPlaceholderAsVisited(key);
447+
String value = new ParsedValue(resolvedValue, nestedParts).resolve(resolutionContext);
448+
resolutionContext.removePlaceholder(key);
449+
return value;
475450
}
476451
}
477452

478453

479454
/**
480455
* A {@link Part} implementation that does not contain a valid placeholder.
481456
*/
482-
static class TextPart extends AbstractPart {
457+
private static class TextPart extends AbstractPart {
483458

484459
/**
485460
* Create a new instance.
@@ -491,7 +466,7 @@ public TextPart(String text) {
491466

492467
@Override
493468
public String resolve(PartResolutionContext resolutionContext) {
494-
return text();
469+
return this.text;
495470
}
496471
}
497472

@@ -500,7 +475,7 @@ public String resolve(PartResolutionContext resolutionContext) {
500475
* A {@link Part} implementation that represents a single placeholder with
501476
* a hard-coded fallback.
502477
*/
503-
static class SimplePlaceholderPart extends AbstractPart {
478+
private static class SimplePlaceholderPart extends AbstractPart {
504479

505480
private final String key;
506481

@@ -527,13 +502,13 @@ public String resolve(PartResolutionContext resolutionContext) {
527502
else if (this.fallback != null) {
528503
return this.fallback;
529504
}
530-
return resolutionContext.handleUnresolvablePlaceholder(this.key, text());
505+
return resolutionContext.handleUnresolvablePlaceholder(this.key, this.text);
531506
}
532507

533508
@Nullable
534509
private String resolveRecursively(PartResolutionContext resolutionContext) {
535-
if (!this.text().equals(this.key)) {
536-
String value = resolveRecursively(resolutionContext, this.text());
510+
if (!this.text.equals(this.key)) {
511+
String value = resolveRecursively(resolutionContext, this.text);
537512
if (value != null) {
538513
return value;
539514
}
@@ -547,7 +522,7 @@ private String resolveRecursively(PartResolutionContext resolutionContext) {
547522
* A {@link Part} implementation that represents a single placeholder
548523
* containing nested placeholders.
549524
*/
550-
static class NestedPlaceholderPart extends AbstractPart {
525+
private static class NestedPlaceholderPart extends AbstractPart {
551526

552527
private final List<Part> keyParts;
553528

@@ -575,7 +550,7 @@ public String resolve(PartResolutionContext resolutionContext) {
575550
else if (this.defaultParts != null) {
576551
return Part.resolveAll(this.defaultParts, resolutionContext);
577552
}
578-
return resolutionContext.handleUnresolvablePlaceholder(resolvedKey, text());
553+
return resolutionContext.handleUnresolvablePlaceholder(resolvedKey, this.text);
579554
}
580555
}
581556

spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuf
7979
* @param properties the {@code Properties} to use for replacement
8080
* @return the supplied value with placeholders replaced inline
8181
*/
82-
public String replacePlaceholders(String value, final Properties properties) {
82+
public String replacePlaceholders(String value, Properties properties) {
8383
Assert.notNull(properties, "'properties' must not be null");
8484
return replacePlaceholders(value, properties::getProperty);
8585
}
@@ -93,9 +93,10 @@ public String replacePlaceholders(String value, final Properties properties) {
9393
*/
9494
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
9595
Assert.notNull(value, "'value' must not be null");
96-
return parseStringValue(value, placeholderResolver);
96+
return this.parser.replacePlaceholders(value, placeholderResolver);
9797
}
9898

99+
@Deprecated(since = "6.2.12", forRemoval = true)
99100
protected String parseStringValue(String value, PlaceholderResolver placeholderResolver) {
100101
return this.parser.replacePlaceholders(value, placeholderResolver);
101102
}

0 commit comments

Comments
 (0)