[FELIX-4759] Support env:XXX subtitution for environment variables
[FELIX-4760] Support default/alternate values for variable substitution

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1650235 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/fileinstall/pom.xml b/fileinstall/pom.xml
index 89adb10..0bcaee1 100644
--- a/fileinstall/pom.xml
+++ b/fileinstall/pom.xml
@@ -57,7 +57,7 @@
     <dependency>
       <groupId>org.apache.felix</groupId>
       <artifactId>org.apache.felix.utils</artifactId>
-      <version>1.6.0</version>
+      <version>1.6.1-SNAPSHOT</version>
     </dependency>
   </dependencies>
   <build>
diff --git a/utils/src/main/java/org/apache/felix/utils/properties/InterpolationHelper.java b/utils/src/main/java/org/apache/felix/utils/properties/InterpolationHelper.java
index a5ff2a6..b28ac66 100644
--- a/utils/src/main/java/org/apache/felix/utils/properties/InterpolationHelper.java
+++ b/utils/src/main/java/org/apache/felix/utils/properties/InterpolationHelper.java
@@ -38,6 +38,7 @@
     private static final String DELIM_START = "${";
     private static final String DELIM_STOP = "}";
     private static final String MARKER = "$__";
+    private static final String ENV_PREFIX = "env:";
 
 
     /**
@@ -299,6 +300,18 @@
         // Using the start and stop delimiter indices, extract
         // the first, deepest nested variable placeholder.
         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 >= 0 ? idx1 : idx2;
+        String op = null;
+        if (idx >= 0 && idx < variable.length())
+        {
+            op = variable.substring(idx);
+            variable = variable.substring(0, idx);
+        }
 
         // Verify that this is not a recursive variable reference.
         if (cycleMap.get(variable) != null)
@@ -315,11 +328,7 @@
         }
         if (substValue == null)
         {
-            if (variable.length() <= 0)
-            {
-                substValue = "";
-            }
-            else
+            if (variable.length() > 0)
             {
                 if (callback != null)
                 {
@@ -329,20 +338,43 @@
                 {
                     substValue = System.getProperty(variable);
                 }
-                if (substValue == null)
+            }
+        }
+
+        if (op != null)
+        {
+            if (op.startsWith(":-"))
+            {
+                if (substValue == null || substValue.isEmpty())
                 {
-                    if (defaultsToEmptyString)
-                    {
-                        substValue = "";
-                    }
-                    else
-                    {
-                        // alters the original token to avoid infinite recursion
-                        // altered tokens are reverted in substVarsPreserveUnresolved()
-                        substValue = MARKER + "{" + variable + "}";
-                    }
+                    substValue = op.substring(":-".length());
                 }
             }
+            else if (op.startsWith(":+"))
+            {
+                if (substValue != null && !substValue.isEmpty())
+                {
+                    substValue = op.substring(":+".length());
+                }
+            }
+            else
+            {
+                throw new IllegalArgumentException("Bad substitution: ${" + org + "}");
+            }
+        }
+
+        if (substValue == null)
+        {
+            if (defaultsToEmptyString)
+            {
+                substValue = "";
+            }
+            else
+            {
+                // alters the original token to avoid infinite recursion
+                // altered tokens are reverted in substVarsPreserveUnresolved()
+                substValue = MARKER + "{" + variable + "}";
+            }
         }
 
         // Remove the found variable from the cycle map, since
@@ -391,13 +423,20 @@
         public String getValue(String key)
         {
             String value = null;
-            if (context != null)
+            if (key.startsWith(ENV_PREFIX))
             {
-                value = context.getProperty(key);
+                value = System.getenv(key.substring(ENV_PREFIX.length()));
             }
-            if (value == null)
+            else
             {
-                value = System.getProperty(key, "");
+                if (context != null)
+                {
+                    value = context.getProperty(key);
+                }
+                if (value == null)
+                {
+                    value = System.getProperty(key);
+                }
             }
             return value;
         }
diff --git a/utils/src/test/java/org/apache/felix/utils/properties/InterpolationHelperTest.java b/utils/src/test/java/org/apache/felix/utils/properties/InterpolationHelperTest.java
index fd773d0..36b527b 100644
--- a/utils/src/test/java/org/apache/felix/utils/properties/InterpolationHelperTest.java
+++ b/utils/src/test/java/org/apache/felix/utils/properties/InterpolationHelperTest.java
@@ -162,7 +162,6 @@
         assertEquals("$\\{var}bc", map1.get("abc"));
     }
 
-    @Test
     public void testPreserveUnresolved() {
         Map<String, String> props = new HashMap<String, String>();
         props.put("a", "${b}");
@@ -176,4 +175,28 @@
         props.put("c", "${d}${d}");
         assertEquals("${d}${d}", InterpolationHelper.substVars("${d}${d}", "c", null, props, null, false, false, false));
     }
+    
+    public void testExpansion() {
+        Map<String, String> props = new LinkedHashMap<String, String>();
+        props.put("a", "foo");
+        props.put("b", "");
+
+        props.put("a_cm", "${a:-bar}");
+        props.put("b_cm", "${b:-bar}");
+        props.put("c_cm", "${c:-bar}");
+
+        props.put("a_cp", "${a:+bar}");
+        props.put("b_cp", "${b:+bar}");
+        props.put("c_cp", "${c:+bar}");
+
+        InterpolationHelper.performSubstitution(props);
+
+        assertEquals("foo", props.get("a_cm"));
+        assertEquals("bar", props.get("b_cm"));
+        assertEquals("bar", props.get("c_cm"));
+
+        assertEquals("bar", props.get("a_cp"));
+        assertEquals("", props.get("b_cp"));
+        assertEquals("", props.get("c_cp"));
+    }
 }