Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,31 @@ public List<String> getPlaceholders(@Nullable String value) {
return placeholders;
}

/**
* Replace placeholders in the given value, resolving each against the supplied properties.
* A backslash before the placeholder prefix (e.g. {@code \${...}}) escapes it as a literal.
*/
public String replacePlaceholders(String value, final Properties properties) {
return replacePlaceholders(value, properties::getProperty);
}

/**
* Replace placeholders in the given value, resolving each via {@code placeholderResolver}.
* A backslash before the placeholder prefix (e.g. {@code \${...}}) escapes it as a literal.
*/
public String replacePlaceholders(String value, Function<String, @Nullable String> placeholderResolver) {
return parseStringValue(value, placeholderResolver, null);
// Support escaping: a backslash before the placeholder prefix produces a literal
// prefix. E.g., for prefix "${", writing "\${" produces literal "${".
String escapePrefix = "\\" + placeholderPrefix;
boolean hasEscaped = value.contains(escapePrefix);
if (hasEscaped) {
value = value.replace(escapePrefix, "\u0000\u0001\u0002");
}
String result = parseStringValue(value, placeholderResolver, null);
if (hasEscaped) {
result = result.replace("\u0000\u0001\u0002", placeholderPrefix);
}
return result;
}

protected String parseStringValue(String value, Function<String, @Nullable String> placeholderResolver,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,52 @@ void withValueSeparatorAndValueReplacement() {
assertThat(s).isEqualTo("hi jon");
}

@Test
void escapedPlaceholder() {
var helper = new PropertyPlaceholderHelper("${", "}", null);
var s = helper.replacePlaceholders("\\${java.version}", k -> "should-not-resolve");
assertThat(s).isEqualTo("${java.version}");
}

@Test
void escapedPlaceholderWithOtherPrefix() {
var helper = new PropertyPlaceholderHelper("%%{", "}", null);
var s = helper.replacePlaceholders("\\%%{k1}", k -> "should-not-resolve");
assertThat(s).isEqualTo("%%{k1}");
}

@Test
void mixedEscapedAndResolvedPlaceholders() {
var helper = new PropertyPlaceholderHelper("${", "}", null);
var s = helper.replacePlaceholders("${greeting} \\${java.version}", k -> "greeting".equals(k) ? "hello" : null);
assertThat(s).isEqualTo("hello ${java.version}");
}

@Test
void unresolvedPlaceholderLeftAsIs() {
var helper = new PropertyPlaceholderHelper("${", "}", null);
var s = helper.replacePlaceholders("${unresolved}", k -> null);
assertThat(s).isEqualTo("${unresolved}");
}

@Test
void normalResolutionStillWorks() {
var helper = new PropertyPlaceholderHelper("${", "}", null);
var s = helper.replacePlaceholders("${greeting} ${name}", k -> switch (k) {
case "greeting" -> "hello";
case "name" -> "world";
default -> null;
});
assertThat(s).isEqualTo("hello world");
}

@Test
void backslashNotBeforePrefixIsPreserved() {
var helper = new PropertyPlaceholderHelper("${", "}", null);
var s = helper.replacePlaceholders("path\\to\\file", k -> null);
assertThat(s).isEqualTo("path\\to\\file");
}

@Test
void withValueSeparatorAndNullReplacement() {
var helper = new PropertyPlaceholderHelper("%%{", "}", ",");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ public class ChangePluginConfiguration extends Recipe {
@Option(displayName = "Configuration",
description = "Plugin configuration provided as raw XML overriding any existing configuration. " +
"Configuration inside `<executions>` blocks will not be altered. " +
"Supplying `null` will remove any existing configuration.",
"Supplying `null` will remove any existing configuration. " +
"To include a literal `${...}` property reference in the configuration " +
"(e.g. a Maven property like `${java.version}`), escape it as `\\${...}` " +
"in your recipe YAML to prevent it from being resolved as a recipe placeholder.",
example = "<foo>bar</foo>",
required = false)
@Nullable
Expand Down
Loading