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);
     }

Reply via email to