I don't have much experience sending patches, so please let me know if I could have 
sent this in a better way.

This patch does two things: 
1) fixes configuration so it builds w/ maven
2) adds support for multiple levels of interpolation

1)a) maven.xml had a bad variable reference that caused test.properties to be copied 
to the wrong place which caused TestClassPropertiesConfiguration to fail
1)b) project.xml had a bad unit test excludes that didn't exclude an abstract TestCase 
class

2)a) BaseConfiguration.java now supports multiple levels of interpolation.  for 
example:
base.prop = /base
first.prop = ${base.prop}/first
second.prop = ${first.prop}/second

second.prop now interpolates "/base/first/second" whereas before it interpolated to 
"${base.prop}/first/second".

Checks are also in place to catch the case of looping references, e.g.
prop.a=${prop.b}
prop.b=${prop.a}

2)b) TestBaseConfiguration.java has tests to check for proper behavior
2)c) BasePropertiesConfiguration.java now has class javadocs that note support for 
interpolation

+jeff
Index: maven.xml
===================================================================
RCS file: /home/cvspublic/jakarta-commons-sandbox/configuration/maven.xml,v
retrieving revision 1.2
diff -u -r1.2 maven.xml
--- maven.xml   14 Dec 2002 11:43:32 -0000      1.2
+++ maven.xml   24 Jan 2003 01:59:03 -0000
@@ -9,7 +9,7 @@
 <project default="java:jar">
 
   <postGoal name="test:compile">
-    <copy todir="${maven.test.dest}/org/apache/commons/configuration">
+    <copy todir="${maven.build.dest}/org/apache/commons/configuration">
       <fileset dir="${maven.conf.dir}" includes="*.properties"/>
     </copy>
   </postGoal>
Index: project.xml
===================================================================
RCS file: /home/cvspublic/jakarta-commons-sandbox/configuration/project.xml,v
retrieving revision 1.20
diff -u -r1.20 project.xml
--- project.xml 14 Jan 2003 03:53:12 -0000      1.20
+++ project.xml 24 Jan 2003 01:59:03 -0000
@@ -144,8 +144,10 @@
     <unitTest>
       <includes>
         <include>**/*Test*.java</include>
-        <exclude>**/TestBasePropertiesConfiguration.java</exclude>
       </includes>
+      <excludes>
+        <exclude>**/TestBasePropertiesConfiguration.java</exclude>
+      </excludes>
       <resources>
         <resource>
           <directory>conf</directory>
Index: src/java/org/apache/commons/configuration/BaseConfiguration.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-commons-sandbox/configuration/src/java/org/apache/commons/configuration/BaseConfiguration.java,v

retrieving revision 1.9
diff -u -r1.9 BaseConfiguration.java
--- src/java/org/apache/commons/configuration/BaseConfiguration.java    22 Jan 2003 
03:00:21 -0000      1.9
+++ src/java/org/apache/commons/configuration/BaseConfiguration.java    24 Jan 2003 
+01:59:03 -0000
@@ -256,16 +256,41 @@
         store.put(key, obj);
     }
 
+       /**
+        * interpolate key names to handle ${key} stuff
+        */
+       protected String interpolate(String base )
+       {
+               return( interpolateHelper( base, null ) );
+       }
+
     /**
-     * interpolate key names to handle ${key} stuff
+     * 
+     * Recursive handler for multple levels of interpolation.  
+     * 
+     * When called the first time, priorVariables should be null.
+     * 
+     * priorVariables serves two purposes: to allow checking for loops, and
+     * creating a meaningful exception message should a loop occur.  It's 0'th
+     * element will be set to the value of base from the first call.  All
+     * subsequent interpolated variables are added afterward.
+     * 
      */
-    protected String interpolate(String base)
+    protected String interpolateHelper(String base, List priorVariables )
     {
         if (base == null)
         {
             return null;
         }
 
+        // on the first call initialize priorVariables 
+        // and add base as the first element
+        if ( priorVariables == null)
+        {
+               priorVariables = new ArrayList();
+               priorVariables.add( base );
+        }
+        
         int begin = -1;
         int end = -1;
         int prec = 0 - END_TOKEN.length();
@@ -278,9 +303,43 @@
         {
             result.append(base.substring(prec + END_TOKEN.length(), begin));
             variable = base.substring(begin + START_TOKEN.length(), end);
+            
+            // if we've got a loop, create a useful exception message and throw
+            if (priorVariables.contains(variable))
+            {
+               String initialBase = priorVariables.remove( 0 ).toString();
+               priorVariables.add( variable );
+               StringBuffer priorVariableSb = new StringBuffer();
+               
+               // create a nice trace of interpolated variables like so:
+               // var1->var2->var3
+               for( Iterator it = priorVariables.iterator(); it.hasNext(); )
+               {
+                    priorVariableSb.append( it.next() );
+                    if ( it.hasNext() )
+                    {
+                       priorVariableSb.append( "->" );
+                    }
+               }
+            
+               throw new IllegalStateException( "infinite loop in property 
+interpolation of " +
+                                                  initialBase + ": " + 
+priorVariableSb.toString() );
+            }
+            // otherwise, add this variable to the interpolation list.
+            else 
+            {
+               priorVariables.add( variable ); 
+            }
+            
             if (store.get(variable) != null)
             {
-                result.append(store.get(variable));
+                result.append(interpolateHelper( store.get(variable).toString(), 
+priorVariables));
+                
+                // pop the interpolated variable off the stack
+                // this maintains priorVariables correctness for 
+                // properties with multiple interpolations, e.g.
+                // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
+                priorVariables.remove( priorVariables.size() - 1 );
             }
             else if (defaults != null && defaults.getString(variable) != null)
             {
Index: src/java/org/apache/commons/configuration/BasePropertiesConfiguration.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-commons-sandbox/configuration/src/java/org/apache/commons/configuration/BasePropertiesConfiguration.java,v

retrieving revision 1.4
diff -u -r1.4 BasePropertiesConfiguration.java
--- src/java/org/apache/commons/configuration/BasePropertiesConfiguration.java  13 Jan 
2003 19:29:38 -0000      1.4
+++ src/java/org/apache/commons/configuration/BasePropertiesConfiguration.java  24 Jan 
+2003 01:59:03 -0000
@@ -138,6 +138,11 @@
  *
  *      # commas may be escaped in tokens
  *      commas.excaped = Hi\, what'up?
+ * 
+ *      # properties can reference other properties
+ *      base.prop = /base
+ *      first.prop = ${base.prop}/first
+ *      second.prop = ${first.prop}/second             
  * </pre>
  *
  * @author <a href="mailto:[EMAIL PROTECTED]";>Stefano Mazzocchi</a>
Index: src/test/org/apache/commons/configuration/TestBaseConfiguration.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-commons-sandbox/configuration/src/test/org/apache/commons/configuration/TestBaseConfiguration.java,v

retrieving revision 1.4
diff -u -r1.4 TestBaseConfiguration.java
--- src/test/org/apache/commons/configuration/TestBaseConfiguration.java        13 Jan 
2003 23:08:13 -0000      1.4
+++ src/test/org/apache/commons/configuration/TestBaseConfiguration.java        24 Jan 
+2003 01:59:03 -0000
@@ -267,4 +267,34 @@
                      "/home/applicationRoot/1",
                      arrayInt[0]);
     }
+
+    public void testMultipleInterpolation() throws Exception
+    {
+        eprop.setProperty( "test.base-level", "/base-level" );
+        eprop.setProperty( "test.first-level", "${test.base-level}/first-level" );
+        eprop.setProperty( "test.second-level", "${test.first-level}/second-level" );
+        eprop.setProperty( "test.third-level", "${test.second-level}/third-level" );
+        
+        String expectedValue = "/base-level/first-level/second-level/third-level";
+        
+        assertEquals( eprop.getString( "test.third-level" ),  expectedValue );
+    }
+
+    public void testInterpolationLoop() throws Exception
+    {
+        eprop.setProperty( "test.a", "${test.b}" );
+        eprop.setProperty( "test.b", "${test.a}" );
+        
+        try {
+            eprop.getString( "test.a" );
+        }
+        catch( IllegalStateException e ) {
+            e.printStackTrace();
+            return;
+        }
+        
+        fail( "IllegalStateException should have been thrown for looped property 
+references" );
+    }
+
+
 }
--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to