You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2010/10/08 21:17:32 UTC

svn commit: r1005974 - in /commons/proper/lang/trunk/src: main/java/org/apache/commons/lang3/text/StrSubstitutor.java test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java

Author: oheger
Date: Fri Oct  8 19:17:31 2010
New Revision: 1005974

URL: http://svn.apache.org/viewvc?rev=1005974&view=rev
Log:
[lang-482] Added support for substitution in variable names.

Modified:
    commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java
    commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java

Modified: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java?rev=1005974&r1=1005973&r2=1005974&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java (original)
+++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java Fri Oct  8 19:17:31 2010
@@ -86,6 +86,16 @@ import java.util.Properties;
  * <pre>
  *   The variable $${${name}} must be used.
  * </pre>
+ * <p>
+ * In some complex scenarios you might even want to perform substitution in the
+ * names of variables, for instance
+ * <pre>
+ * ${jre-${java.specification.version}}
+ * </pre>
+ * <code>StrSubstitutor</code> supports this recursive substitution in variable
+ * names, but it has to be enabled explicitly by setting the
+ * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables}
+ * property to <b>true</b>.
  *
  * @author Apache Software Foundation
  * @version $Id$
@@ -122,6 +132,10 @@ public class StrSubstitutor {
      * Variable resolution is delegated to an implementor of VariableResolver.
      */
     private StrLookup<?> variableResolver;
+    /**
+     * The flag whether substitution in variable names is enabled.
+     */
+    private boolean enableSubstitutionInVariables;
 
     //-----------------------------------------------------------------------
     /**
@@ -571,7 +585,8 @@ public class StrSubstitutor {
         int bufEnd = offset + length;
         int pos = offset;
         while (pos < bufEnd) {
-            int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd);
+            int startMatchLen = prefixMatcher.isMatch(chars, pos, offset,
+                    bufEnd);
             if (startMatchLen == 0) {
                 pos++;
             } else {
@@ -579,7 +594,7 @@ public class StrSubstitutor {
                 if (pos > offset && chars[pos - 1] == escape) {
                     // escaped
                     buf.deleteCharAt(pos - 1);
-                    chars = buf.buffer;  // in case buffer was altered
+                    chars = buf.buffer; // in case buffer was altered
                     lengthChange--;
                     altered = true;
                     bufEnd--;
@@ -588,45 +603,73 @@ public class StrSubstitutor {
                     int startPos = pos;
                     pos += startMatchLen;
                     int endMatchLen = 0;
+                    int nestedVarCount = 0;
                     while (pos < bufEnd) {
-                        endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd);
+                        if (isEnableSubstitutionInVariables()
+                                && (endMatchLen = prefixMatcher.isMatch(chars,
+                                        pos, offset, bufEnd)) != 0) {
+                            // found a nested variable start
+                            nestedVarCount++;
+                            pos += endMatchLen;
+                            continue;
+                        }
+
+                        endMatchLen = suffixMatcher.isMatch(chars, pos, offset,
+                                bufEnd);
                         if (endMatchLen == 0) {
                             pos++;
                         } else {
                             // found variable end marker
-                            String varName = new String(chars, startPos + startMatchLen,
-                                                        pos - startPos - startMatchLen);
-                            pos += endMatchLen;
-                            int endPos = pos;
-
-                            // on the first call initialize priorVariables
-                            if (priorVariables == null) {
-                                priorVariables = new ArrayList<String>();
-                                priorVariables.add(new String(chars, offset, length));
+                            if (nestedVarCount == 0) {
+                                String varName = new String(chars, startPos
+                                        + startMatchLen, pos - startPos
+                                        - startMatchLen);
+                                if (isEnableSubstitutionInVariables()) {
+                                    StrBuilder bufName = new StrBuilder(varName);
+                                    substitute(bufName, 0, bufName.length());
+                                    varName = bufName.toString();
+                                }
+                                pos += endMatchLen;
+                                int endPos = pos;
+
+                                // on the first call initialize priorVariables
+                                if (priorVariables == null) {
+                                    priorVariables = new ArrayList<String>();
+                                    priorVariables.add(new String(chars,
+                                            offset, length));
+                                }
+
+                                // handle cyclic substitution
+                                checkCyclicSubstitution(varName, priorVariables);
+                                priorVariables.add(varName);
+
+                                // resolve the variable
+                                String varValue = resolveVariable(varName, buf,
+                                        startPos, endPos);
+                                if (varValue != null) {
+                                    // recursive replace
+                                    int varLen = varValue.length();
+                                    buf.replace(startPos, endPos, varValue);
+                                    altered = true;
+                                    int change = substitute(buf, startPos,
+                                            varLen, priorVariables);
+                                    change = change
+                                            + (varLen - (endPos - startPos));
+                                    pos += change;
+                                    bufEnd += change;
+                                    lengthChange += change;
+                                    chars = buf.buffer; // in case buffer was
+                                                        // altered
+                                }
+
+                                // remove variable from the cyclic stack
+                                priorVariables
+                                        .remove(priorVariables.size() - 1);
+                                break;
+                            } else {
+                                nestedVarCount--;
+                                pos += endMatchLen;
                             }
-
-                            // handle cyclic substitution
-                            checkCyclicSubstitution(varName, priorVariables);
-                            priorVariables.add(varName);
-
-                            // resolve the variable
-                            String varValue = resolveVariable(varName, buf, startPos, endPos);
-                            if (varValue != null) {
-                                // recursive replace
-                                int varLen = varValue.length();
-                                buf.replace(startPos, endPos, varValue);
-                                altered = true;
-                                int change = substitute(buf, startPos, varLen, priorVariables);
-                                change = change + (varLen - (endPos - startPos));
-                                pos += change;
-                                bufEnd += change;
-                                lengthChange += change;
-                                chars = buf.buffer;  // in case buffer was altered
-                            }
-
-                            // remove variable from the cyclic stack
-                            priorVariables.remove(priorVariables.size() - 1);
-                            break;
                         }
                     }
                 }
@@ -740,7 +783,7 @@ public class StrSubstitutor {
     /**
      * Sets the variable prefix to use.
      * <p>
-     * The variable prefix is the characer or characters that identify the
+     * The variable prefix is the character or characters that identify the
      * start of a variable. This method allows a single character prefix to
      * be easily set.
      *
@@ -819,7 +862,7 @@ public class StrSubstitutor {
     /**
      * Sets the variable suffix to use.
      * <p>
-     * The variable suffix is the characer or characters that identify the
+     * The variable suffix is the character or characters that identify the
      * end of a variable. This method allows a string suffix to be easily set.
      *
      * @param suffix  the suffix for variables, not null
@@ -853,4 +896,29 @@ public class StrSubstitutor {
         this.variableResolver = variableResolver;
     }
 
+    // Substitution support in variable names
+    //-----------------------------------------------------------------------
+    /**
+     * Returns a flag whether substitution is done in variable names.
+     *
+     * @return the substitution in variable names flag
+     * @since 3.0
+     */
+    public boolean isEnableSubstitutionInVariables() {
+        return enableSubstitutionInVariables;
+    }
+
+    /**
+     * Sets a flag whether substitution is done in variable names. If set to
+     * <b>true</b>, the names of variables can contain other variables which are
+     * processed first before the original variable is evaluated, e.g.
+     * <code>${jre-${java.version}}</code>. The default value is <b>false</b>.
+     *
+     * @param enableSubstitutionInVariables the new value of the flag
+     * @since 3.0
+     */
+    public void setEnableSubstitutionInVariables(
+            boolean enableSubstitutionInVariables) {
+        this.enableSubstitutionInVariables = enableSubstitutionInVariables;
+    }
 }

Modified: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java?rev=1005974&r1=1005973&r2=1005974&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java (original)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java Fri Oct  8 19:17:31 2010
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
- * 
+ *
  *      http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,7 +27,7 @@ import org.apache.commons.lang3.mutable.
 
 /**
  * Test class for StrSubstitutor.
- * 
+ *
  * @author Oliver Heger
  * @version $Id$
  */
@@ -255,6 +255,57 @@ public class StrSubstitutorTest extends 
         assertEquals("${animal} jumps", sub.replace("The ${animal} jumps over the ${target}.", 4, 15));
     }
 
+    /**
+     * Tests whether a variable can be replaced in a variable name.
+     */
+    public void testReplaceInVariable() {
+        values.put("animal.1", "fox");
+        values.put("animal.2", "mouse");
+        values.put("species", "2");
+        StrSubstitutor sub = new StrSubstitutor(values);
+        sub.setEnableSubstitutionInVariables(true);
+        assertEquals(
+                "Wrong result (1)",
+                "The mouse jumps over the lazy dog.",
+                sub.replace("The ${animal.${species}} jumps over the ${target}."));
+        values.put("species", "1");
+        assertEquals(
+                "Wrong result (2)",
+                "The fox jumps over the lazy dog.",
+                sub.replace("The ${animal.${species}} jumps over the ${target}."));
+    }
+
+    /**
+     * Tests whether substitution in variable names is disabled per default.
+     */
+    public void testReplaceInVariableDisabled() {
+        values.put("animal.1", "fox");
+        values.put("animal.2", "mouse");
+        values.put("species", "2");
+        StrSubstitutor sub = new StrSubstitutor(values);
+        assertEquals(
+                "Wrong result",
+                "The ${animal.${species}} jumps over the lazy dog.",
+                sub.replace("The ${animal.${species}} jumps over the ${target}."));
+    }
+
+    /**
+     * Tests complex and recursive substitution in variable names.
+     */
+    public void testReplaceInVariableRecursive() {
+        values.put("animal.2", "brown fox");
+        values.put("animal.1", "white mouse");
+        values.put("color", "white");
+        values.put("species.white", "1");
+        values.put("species.brown", "2");
+        StrSubstitutor sub = new StrSubstitutor(values);
+        sub.setEnableSubstitutionInVariables(true);
+        assertEquals(
+                "Wrong result",
+                "The white mouse jumps over the lazy dog.",
+                sub.replace("The ${animal.${species.${color}}} jumps over the ${target}."));
+    }
+
     //-----------------------------------------------------------------------
     /**
      * Tests protected.
@@ -325,7 +376,7 @@ public class StrSubstitutorTest extends 
         assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
         sub.setVariablePrefix('<');
         assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.CharMatcher);
-        
+
         sub.setVariablePrefix("<<");
         assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
         try {
@@ -335,7 +386,7 @@ public class StrSubstitutorTest extends 
             // expected
         }
         assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
-        
+
         StrMatcher matcher = StrMatcher.commaMatcher();
         sub.setVariablePrefixMatcher(matcher);
         assertSame(matcher, sub.getVariablePrefixMatcher());
@@ -356,7 +407,7 @@ public class StrSubstitutorTest extends 
         assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
         sub.setVariableSuffix('<');
         assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.CharMatcher);
-        
+
         sub.setVariableSuffix("<<");
         assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
         try {
@@ -366,7 +417,7 @@ public class StrSubstitutorTest extends 
             // expected
         }
         assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
-        
+
         StrMatcher matcher = StrMatcher.commaMatcher();
         sub.setVariableSuffixMatcher(matcher);
         assertSame(matcher, sub.getVariableSuffixMatcher());
@@ -412,7 +463,7 @@ public class StrSubstitutorTest extends 
             + "working with ${os.name}, your home "
             + "directory is ${user.home}."));
     }
-    
+
     /**
      * Test the replace of a properties object
      */
@@ -430,38 +481,38 @@ public class StrSubstitutorTest extends 
     private void doTestReplace(String expectedResult, String replaceTemplate, boolean substring) {
         String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1);
         StrSubstitutor sub = new StrSubstitutor(values);
-        
+
         // replace using String
         assertEquals(expectedResult, sub.replace(replaceTemplate));
         if (substring) {
             assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2));
         }
-        
+
         // replace using char[]
         char[] chars = replaceTemplate.toCharArray();
         assertEquals(expectedResult, sub.replace(chars));
         if (substring) {
             assertEquals(expectedShortResult, sub.replace(chars, 1, chars.length - 2));
         }
-        
+
         // replace using StringBuffer
         StringBuffer buf = new StringBuffer(replaceTemplate);
         assertEquals(expectedResult, sub.replace(buf));
         if (substring) {
             assertEquals(expectedShortResult, sub.replace(buf, 1, buf.length() - 2));
         }
-        
+
         // replace using StrBuilder
         StrBuilder bld = new StrBuilder(replaceTemplate);
         assertEquals(expectedResult, sub.replace(bld));
         if (substring) {
             assertEquals(expectedShortResult, sub.replace(bld, 1, bld.length() - 2));
         }
-        
+
         // replace using object
         MutableObject<String> obj = new MutableObject<String>(replaceTemplate);  // toString returns template
         assertEquals(expectedResult, sub.replace(obj));
-        
+
         // replace in StringBuffer
         buf = new StringBuffer(replaceTemplate);
         assertEquals(true, sub.replaceIn(buf));
@@ -471,7 +522,7 @@ public class StrSubstitutorTest extends 
             assertEquals(true, sub.replaceIn(buf, 1, buf.length() - 2));
             assertEquals(expectedResult, buf.toString());  // expect full result as remainder is untouched
         }
-        
+
         // replace in StrBuilder
         bld = new StrBuilder(replaceTemplate);
         assertEquals(true, sub.replaceIn(bld));
@@ -485,7 +536,7 @@ public class StrSubstitutorTest extends 
 
     private void doTestNoReplace(String replaceTemplate) {
         StrSubstitutor sub = new StrSubstitutor(values);
-        
+
         if (replaceTemplate == null) {
             assertEquals(null, sub.replace((String) null));
             assertEquals(null, sub.replace((String) null, 0, 100));