Skip to content

Commit eda7af7

Browse files
committed
Merge branch '6.2.x'
2 parents 1763334 + 82bc4ff commit eda7af7

File tree

2 files changed

+48
-20
lines changed

2 files changed

+48
-20
lines changed

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

+27-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -173,9 +173,8 @@ else if (isEscaped(value, startIndex)) { // Not a valid index, accumulate and sk
173173
}
174174

175175
private SimplePlaceholderPart createSimplePlaceholderPart(String text) {
176-
String[] keyAndDefault = splitKeyAndDefault(text);
177-
return ((keyAndDefault != null) ? new SimplePlaceholderPart(text, keyAndDefault[0], keyAndDefault[1]) :
178-
new SimplePlaceholderPart(text, text, null));
176+
ParsedSection section = parseSection(text);
177+
return new SimplePlaceholderPart(text, section.key(), section.fallback());
179178
}
180179

181180
private NestedPlaceholderPart createNestedPlaceholderPart(String text, List<Part> parts) {
@@ -191,27 +190,32 @@ private NestedPlaceholderPart createNestedPlaceholderPart(String text, List<Part
191190
}
192191
else {
193192
String candidate = part.text();
194-
String[] keyAndDefault = splitKeyAndDefault(candidate);
195-
if (keyAndDefault != null) {
196-
keyParts.add(new TextPart(keyAndDefault[0]));
197-
if (keyAndDefault[1] != null) {
198-
defaultParts.add(new TextPart(keyAndDefault[1]));
199-
}
193+
ParsedSection section = parseSection(candidate);
194+
keyParts.add(new TextPart(section.key()));
195+
if (section.fallback() != null) {
196+
defaultParts.add(new TextPart(section.fallback()));
200197
defaultParts.addAll(parts.subList(i + 1, parts.size()));
201198
return new NestedPlaceholderPart(text, keyParts, defaultParts);
202199
}
203-
else {
204-
keyParts.add(part);
205-
}
206200
}
207201
}
208-
// No separator found
209-
return new NestedPlaceholderPart(text, parts, null);
202+
return new NestedPlaceholderPart(text, keyParts, null);
210203
}
211204

212-
private String @Nullable [] splitKeyAndDefault(String value) {
205+
/**
206+
* Parse an input value that may contain a separator character and return a
207+
* {@link ParsedValue}. If a valid separator character has been identified, the
208+
* given {@code value} is split between a {@code key} and a {@code fallback}. If not,
209+
* only the {@code key} is set.
210+
* <p>
211+
* The returned key may be different from the original value as escaped
212+
* separators, if any, are resolved.
213+
* @param value the value to parse
214+
* @return the parsed section
215+
*/
216+
private ParsedSection parseSection(String value) {
213217
if (this.separator == null || !value.contains(this.separator)) {
214-
return null;
218+
return new ParsedSection(value, null);
215219
}
216220
int position = 0;
217221
int index = value.indexOf(this.separator, position);
@@ -228,11 +232,11 @@ private NestedPlaceholderPart createNestedPlaceholderPart(String text, List<Part
228232
buffer.append(value, position, index);
229233
String key = buffer.toString();
230234
String fallback = value.substring(index + this.separator.length());
231-
return new String[] { key, fallback };
235+
return new ParsedSection(key, fallback);
232236
}
233237
}
234238
buffer.append(value, position, value.length());
235-
return new String[] { buffer.toString(), null };
239+
return new ParsedSection(buffer.toString(), null);
236240
}
237241

238242
private static void addText(String value, int start, int end, LinkedList<Part> parts) {
@@ -290,6 +294,10 @@ private boolean isEscaped(String value, int index) {
290294
return (this.escape != null && index > 0 && value.charAt(index - 1) == this.escape);
291295
}
292296

297+
record ParsedSection(String key, @Nullable String fallback) {
298+
299+
}
300+
293301

294302
/**
295303
* Provide the necessary context to handle and resolve underlying placeholders.

spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -278,6 +278,26 @@ class EscapedTests {
278278

279279
private final PlaceholderParser parser = new PlaceholderParser("${", "}", ":", '\\', true);
280280

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+
281301
@ParameterizedTest(name = "{0} -> {1}")
282302
@MethodSource("escapedPlaceholders")
283303
void escapedPlaceholderIsNotReplaced(String text, String expected) {

0 commit comments

Comments
 (0)