You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2010/08/18 11:30:46 UTC

svn commit: r986609 - in /karaf/trunk/shell/config/src: main/java/org/apache/karaf/shell/config/Properties.java test/java/org/apache/karaf/shell/config/PropertiesTest.java

Author: gnodet
Date: Wed Aug 18 09:30:46 2010
New Revision: 986609

URL: http://svn.apache.org/viewvc?rev=986609&view=rev
Log:
KARAF-140: Enhance Properties to interpolate values so that overriding interpolated values with their own value don't actually change anything

Modified:
    karaf/trunk/shell/config/src/main/java/org/apache/karaf/shell/config/Properties.java
    karaf/trunk/shell/config/src/test/java/org/apache/karaf/shell/config/PropertiesTest.java

Modified: karaf/trunk/shell/config/src/main/java/org/apache/karaf/shell/config/Properties.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/config/src/main/java/org/apache/karaf/shell/config/Properties.java?rev=986609&r1=986608&r2=986609&view=diff
==============================================================================
--- karaf/trunk/shell/config/src/main/java/org/apache/karaf/shell/config/Properties.java (original)
+++ karaf/trunk/shell/config/src/main/java/org/apache/karaf/shell/config/Properties.java Wed Aug 18 09:30:46 2010
@@ -18,13 +18,7 @@ package org.apache.karaf.shell.config;
 
 import java.io.*;
 import java.net.URL;
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 public class Properties extends AbstractMap<String, String> {
 
@@ -120,11 +114,14 @@ public class Properties extends Abstract
 
     @Override
     public String put(String key, String value) {
-        Layout l = layout.get(key);
-        if (l != null) {
-            l.clearValue();
+        String old = storage.put(key, value);
+        if (old == null || !old.equals(value)) {
+            Layout l = layout.get(key);
+            if (l != null) {
+                l.clearValue();
+            }
         }
-        return storage.put(key, value);
+        return old;
     }
 
     @Override
@@ -205,6 +202,7 @@ public class Properties extends Abstract
                                new ArrayList<String>(reader.getValueLines())));
         }
         footer = new ArrayList<String>(reader.getCommentLines());
+        performSubstitution(storage);
     }
 
     /**
@@ -511,6 +509,150 @@ public class Properties extends Abstract
         return false;
     }
 
+    private static final char   ESCAPE_CHAR = '\\';
+    private static final String DELIM_START = "${";
+    private static final String DELIM_STOP = "}";
+
+    private static final String CHECKSUM_SUFFIX = ".checksum";
+
+    /**
+     * Perform substitution on a property set
+     *
+     * @param properties the property set to perform substitution on
+     */
+    public static void performSubstitution(Map<String,String> properties)
+    {
+        for (String name : properties.keySet())
+        {
+            String value = properties.get(name);
+            properties.put(name, substVars(value, name, null, properties));
+        }
+    }
+
+    /**
+     * <p>
+     * This method performs property variable substitution on the
+     * specified value. If the specified value contains the syntax
+     * <tt>${&lt;prop-name&gt;}</tt>, where <tt>&lt;prop-name&gt;</tt>
+     * refers to either a configuration property or a system property,
+     * then the corresponding property value is substituted for the variable
+     * placeholder. Multiple variable placeholders may exist in the
+     * specified value as well as nested variable placeholders, which
+     * are substituted from inner most to outer most. Configuration
+     * properties override system properties.
+     * </p>
+     * @param val The string on which to perform property substitution.
+     * @param currentKey The key of the property being evaluated used to
+     *        detect cycles.
+     * @param cycleMap Map of variable references used to detect nested cycles.
+     * @param configProps Set of configuration properties.
+     * @return The value of the specified string after system property substitution.
+     * @throws IllegalArgumentException If there was a syntax error in the
+     *         property placeholder syntax or a recursive variable reference.
+     **/
+    public static String substVars(String val, String currentKey, Map<String,String> cycleMap, Map<String,String> configProps)
+        throws IllegalArgumentException
+    {
+        if (cycleMap == null)
+        {
+            cycleMap = new HashMap<String,String>();
+        }
+
+        // Put the current key in the cycle map.
+        cycleMap.put(currentKey, currentKey);
+
+        // Assume we have a value that is something like:
+        // "leading ${foo.${bar}} middle ${baz} trailing"
+
+        // Find the first ending '}' variable delimiter, which
+        // will correspond to the first deepest nested variable
+        // placeholder.
+        int stopDelim = val.indexOf(DELIM_STOP);
+        while (stopDelim > 0 && val.charAt(stopDelim - 1) == ESCAPE_CHAR)
+        {
+            stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1);
+        }
+
+        // Find the matching starting "${" variable delimiter
+        // by looping until we find a start delimiter that is
+        // greater than the stop delimiter we have found.
+        int startDelim = val.indexOf(DELIM_START);
+        while (stopDelim >= 0)
+        {
+            int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length());
+            if ((idx < 0) || (idx > stopDelim))
+            {
+                break;
+            }
+            else if (idx < stopDelim)
+            {
+                startDelim = idx;
+            }
+        }
+
+        // If we do not have a start or stop delimiter, then just
+        // return the existing value.
+        if ((startDelim < 0) || (stopDelim < 0))
+        {
+            return unescape(val);
+        }
+
+        // At this point, we have found a variable placeholder so
+        // we must perform a variable substitution on it.
+        // Using the start and stop delimiter indices, extract
+        // the first, deepest nested variable placeholder.
+        String variable = val.substring(startDelim + DELIM_START.length(), stopDelim);
+
+        // Verify that this is not a recursive variable reference.
+        if (cycleMap.get(variable) != null)
+        {
+            throw new IllegalArgumentException("recursive variable reference: " + variable);
+        }
+
+        // Get the value of the deepest nested variable placeholder.
+        // Try to configuration properties first.
+        String substValue = (String) ((configProps != null) ? configProps.get(variable) : null);
+        if (substValue == null)
+        {
+            // Ignore unknown property values.
+            substValue = variable.length() > 0 ? System.getProperty(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);
+
+        // 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(), val.length());
+
+        // Now perform substitution again, since there could still
+        // be substitutions to make.
+        val = substVars(val, currentKey, cycleMap, configProps);
+
+        // Remove escape characters preceding {, } and \
+        val = unescape(val);
+
+        // Return the value.
+        return val;
+    }
+
+    private static String unescape(String val) {
+        int escape = val.indexOf(ESCAPE_CHAR);
+        while (escape >= 0 && escape < val.length() - 1)
+        {
+            char c = val.charAt(escape + 1);
+            if (c == '{' || c == '}' || c == ESCAPE_CHAR)
+            {
+                val = val.substring(0, escape) + val.substring(escape + 1);
+            }
+            escape = val.indexOf(ESCAPE_CHAR, escape + 1);
+        }
+        return val;
+    }
+
     /**
      * This class is used to read properties lines. These lines do
      * not terminate with new-line chars but rather when there is no

Modified: karaf/trunk/shell/config/src/test/java/org/apache/karaf/shell/config/PropertiesTest.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/config/src/test/java/org/apache/karaf/shell/config/PropertiesTest.java?rev=986609&r1=986608&r2=986609&view=diff
==============================================================================
--- karaf/trunk/shell/config/src/test/java/org/apache/karaf/shell/config/PropertiesTest.java (original)
+++ karaf/trunk/shell/config/src/test/java/org/apache/karaf/shell/config/PropertiesTest.java Wed Aug 18 09:30:46 2010
@@ -17,6 +17,10 @@
 package org.apache.karaf.shell.config;
 
 import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
 import java.net.URL;
 
 import junit.framework.TestCase;
@@ -24,13 +28,34 @@ import junit.framework.TestCase;
 public class PropertiesTest extends TestCase {
 
     public void testLoadSave() throws IOException {
-        URL url = getClass().getClassLoader().getResource("OSGI-INF/metatype/metatype.properties");
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        pw.println("# ");
+        pw.println("# The Main  ");
+        pw.println("# ");
+        pw.println("# Comment ");
+        pw.println("# ");
+        pw.println("");
+        pw.println("# Another comment");
+        pw.println("");
+        pw.println("# A value comment");
+        pw.println("key1 = val1");
+        pw.println("");
+        pw.println("# Another value comment");
+        pw.println("key2 = ${key1}/foo");
+        pw.println("");
+        pw.println("# A third comment");
+        pw.println("key3 = val3");
+        pw.println("");
+
+
         Properties props = new Properties();
-        props.load(url);
+        props.load(new StringReader(sw.toString()));
         props.save(System.err);
         System.err.println("=====");
 
-        props.put("storage.name", "foo bar");
+        props.put("key2", props.get("key2"));
+        props.put("key3", "foo");
         props.save(System.err);
         System.err.println("=====");
     }