This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push: new 54df597018 [MNG-8344] Support multiple operators in variable expansion (#1832) 54df597018 is described below commit 54df597018c1928694d37285a9d7d51787f7547c Author: Guillaume Nodet <gno...@gmail.com> AuthorDate: Thu Oct 24 13:54:34 2024 +0200 [MNG-8344] Support multiple operators in variable expansion (#1832) --- .../internal/impl/model/DefaultInterpolator.java | 147 ++++++++++++++------- .../impl/model/DefaultInterpolatorTest.java | 32 +++++ 2 files changed, 129 insertions(+), 50 deletions(-) diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultInterpolator.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultInterpolator.java index 343f7c13a7..191b7f11f4 100644 --- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultInterpolator.java +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultInterpolator.java @@ -268,80 +268,127 @@ public class DefaultInterpolator implements Interpolator { String variable = val.substring(startDelim + DELIM_START.length(), stopDelim); String org = variable; - // Strip expansion modifiers - int idx1 = variable.lastIndexOf(":-"); - int idx2 = variable.lastIndexOf(":+"); - int idx = idx1 >= 0 ? idx2 >= 0 ? Math.min(idx1, idx2) : idx1 : idx2; - String op = null; - if (idx >= 0) { - op = variable.substring(idx); - variable = variable.substring(0, idx); - } + String substValue = processSubstitution( + variable, org, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString); - // Verify that this is not a recursive variable reference. - if (!cycleMap.add(variable)) { - throw new InterpolatorException("recursive variable reference: " + variable); - } + // Append the leading characters, the substituted value of + // the variable, and the trailing characters to get the new + // value. + val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length()); + + // Now perform substitution again, since there could still + // be substitutions to make. + val = doSubstVars(val, currentKey, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString); + + cycleMap.remove(currentKey); + + // Return the value. + return val; + } + private static String processSubstitution( + String variable, + String org, + Set<String> cycleMap, + Map<String, String> configProps, + Function<String, String> callback, + BiFunction<String, String, String> postprocessor, + boolean defaultsToEmptyString) { + + // Process chained operators from left to right + int startIdx = 0; + String currentVar = variable; String substValue = null; - // Get the value of the deepest nested variable placeholder. - // Try to configuration properties first. - if (configProps != null) { - substValue = configProps.get(variable); - } - if (substValue == null) { - if (!variable.isEmpty()) { - if (callback != null) { - String s1 = callback.apply(variable); - String s2 = doSubstVars( - s1, variable, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString); - substValue = postprocessor != null ? postprocessor.apply(variable, s2) : s2; + + while (startIdx < variable.length()) { + int idx1 = variable.indexOf(":-", startIdx); + int idx2 = variable.indexOf(":+", startIdx); + int idx = idx1 >= 0 ? idx2 >= 0 ? Math.min(idx1, idx2) : idx1 : idx2; + + if (idx < 0) { + // No more operators, process the final variable + if (substValue == null) { + currentVar = variable.substring(startIdx); + substValue = resolveVariable( + currentVar, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString); } + break; } - } - if (op != null) { - if (op.startsWith(":-")) { - if (substValue == null || substValue.isEmpty()) { - substValue = op.substring(":-".length()); - } - } else if (op.startsWith(":+")) { + // Get the current variable part before the operator + String varPart = variable.substring(startIdx, idx); + if (substValue == null) { + substValue = + resolveVariable(varPart, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString); + } + + // Find the end of the current operator's value + int nextIdx1 = variable.indexOf(":-", idx + 2); + int nextIdx2 = variable.indexOf(":+", idx + 2); + int nextIdx = nextIdx1 >= 0 ? nextIdx2 >= 0 ? Math.min(nextIdx1, nextIdx2) : nextIdx1 : nextIdx2; + + String op = variable.substring(idx, idx + 2); + String opValue = variable.substring(idx + 2, nextIdx >= 0 ? nextIdx : variable.length()); + + // Process the operator value through substitution if it contains variables + String processedOpValue = + doSubstVars(opValue, org, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString); + + // Apply the operator + if (":+".equals(op)) { if (substValue != null && !substValue.isEmpty()) { - substValue = op.substring(":+".length()); + substValue = processedOpValue; + } + } else if (":-".equals(op)) { + if (substValue == null || substValue.isEmpty()) { + substValue = processedOpValue; } } else { - throw new InterpolatorException("Bad substitution: ${" + org + "}"); + throw new InterpolatorException("Bad substitution operator in: ${" + org + "}"); } + + startIdx = nextIdx >= 0 ? nextIdx : variable.length(); } if (substValue == null) { if (defaultsToEmptyString) { substValue = ""; } else { - // alters the original token to avoid infinite recursion - // altered tokens are reverted in unescape() substValue = MARKER + "{" + variable + "}"; } } - // Remove the found variable from the cycle map, since - // it may appear more than once in the value and we don't - // want such situations to appear as a recursive reference. - cycleMap.remove(variable); + return substValue; + } - // Append the leading characters, the substituted value of - // the variable, and the trailing characters to get the new - // value. - val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length()); + private static String resolveVariable( + String variable, + Set<String> cycleMap, + Map<String, String> configProps, + Function<String, String> callback, + BiFunction<String, String, String> postprocessor, + boolean defaultsToEmptyString) { - // Now perform substitution again, since there could still - // be substitutions to make. - val = doSubstVars(val, currentKey, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString); + // Verify that this is not a recursive variable reference + if (!cycleMap.add(variable)) { + throw new InterpolatorException("recursive variable reference: " + variable); + } - cycleMap.remove(currentKey); + String substValue = null; + // Try configuration properties first + if (configProps != null) { + substValue = configProps.get(variable); + } + if (substValue == null && !variable.isEmpty() && callback != null) { + String s1 = callback.apply(variable); + String s2 = + doSubstVars(s1, variable, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString); + substValue = postprocessor != null ? postprocessor.apply(variable, s2) : s2; + } - // Return the value. - return val; + // Remove the variable from cycle map + cycleMap.remove(variable); + return substValue; } /** diff --git a/maven-api-impl/src/test/java/org/apache/maven/internal/impl/model/DefaultInterpolatorTest.java b/maven-api-impl/src/test/java/org/apache/maven/internal/impl/model/DefaultInterpolatorTest.java index 7887e8a4b9..ee77b5dc60 100644 --- a/maven-api-impl/src/test/java/org/apache/maven/internal/impl/model/DefaultInterpolatorTest.java +++ b/maven-api-impl/src/test/java/org/apache/maven/internal/impl/model/DefaultInterpolatorTest.java @@ -170,6 +170,38 @@ class DefaultInterpolatorTest { assertEquals("", props.get("c_cp")); } + @Test + void testXdg() { + Map<String, String> props; + + props = new LinkedHashMap<>(); + props.put("user.home", "/Users/gnodet"); + props.put( + "maven.user.config", + "${env.MAVEN_XDG:+${env.XDG_CONFIG_HOME:-${user.home}/.config/maven}:-${user.home}/.m2}"); + performSubstitution(props); + assertEquals("/Users/gnodet/.m2", props.get("maven.user.config")); + + props = new LinkedHashMap<>(); + props.put("user.home", "/Users/gnodet"); + props.put( + "maven.user.config", + "${env.MAVEN_XDG:+${env.XDG_CONFIG_HOME:-${user.home}/.config/maven}:-${user.home}/.m2}"); + props.put("env.MAVEN_XDG", "true"); + performSubstitution(props); + assertEquals("/Users/gnodet/.config/maven", props.get("maven.user.config")); + + props = new LinkedHashMap<>(); + props.put("user.home", "/Users/gnodet"); + props.put( + "maven.user.config", + "${env.MAVEN_XDG:+${env.XDG_CONFIG_HOME:-${user.home}/.config/maven}:-${user.home}/.m2}"); + props.put("env.MAVEN_XDG", "true"); + props.put("env.XDG_CONFIG_HOME", "/Users/gnodet/.xdg/maven"); + performSubstitution(props); + assertEquals("/Users/gnodet/.xdg/maven", props.get("maven.user.config")); + } + private void performSubstitution(Map<String, String> props) { performSubstitution(props, null); }