Author: oheger
Date: Mon Aug 20 10:57:08 2007
New Revision: 567771

URL: http://svn.apache.org/viewvc?rev=567771&view=rev
Log:
CONFIGURATION-272: Added new copy() and append() methods to 
AbstractConfiguration, which replace the corresponding methods of 
ConfigurationUtils. These methods handle (escaped) list delimiters correctly.

Modified:
    
commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/AbstractConfiguration.java
    
commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/ConfigurationUtils.java
    
commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestAbstractConfigurationBasicFeatures.java
    
commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestPropertiesConfiguration.java
    commons/proper/configuration/trunk/xdocs/changes.xml
    commons/proper/configuration/trunk/xdocs/userguide/howto_utilities.xml

Modified: 
commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/AbstractConfiguration.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/AbstractConfiguration.java?rev=567771&r1=567770&r2=567771&view=diff
==============================================================================
--- 
commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/AbstractConfiguration.java
 (original)
+++ 
commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/AbstractConfiguration.java
 Mon Aug 20 10:57:08 2007
@@ -46,7 +46,7 @@
  * implement only abstract methods from this class. A lot of functionality
  * needed by typical implementations of the <code>Configuration</conde>
  * interface is already provided by this base class. Following is a list of
- * feauters implemented here:
+ * features implemented here:
  * <ul><li>Data conversion support. The various data types required by the
  * <code>Configuration</code> interface are already handled by this base class.
  * A concrete sub class only needs to provide a generic 
<code>getProperty()</code>
@@ -73,10 +73,9 @@
  * </ul></p>
  *
  * @author <a href="mailto:[EMAIL PROTECTED]">Konstantin Shaposhnikov </a>
- * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Heger </a>
+ * @author Oliver Heger
  * @author <a href="mailto:[EMAIL PROTECTED]">Henning P. Schmiedehausen </a>
- * @version $Id: AbstractConfiguration.java,v 1.29 2004/12/02 22:05:52 ebourg
- * Exp $
+ * @version $Id$
  */
 public abstract class AbstractConfiguration extends EventSource implements 
Configuration
 {
@@ -387,15 +386,9 @@
     public void addProperty(String key, Object value)
     {
         fireEvent(EVENT_ADD_PROPERTY, key, value, true);
-
-        Iterator it = PropertyConverter.toIterator(value,
+        addPropertyValues(key, value,
                 isDelimiterParsingDisabled() ? DISABLED_DELIMITER
                         : getListDelimiter());
-        while (it.hasNext())
-        {
-            addPropertyDirect(key, it.next());
-        }
-
         fireEvent(EVENT_ADD_PROPERTY, key, value, false);
     }
 
@@ -409,6 +402,25 @@
     protected abstract void addPropertyDirect(String key, Object value);
 
     /**
+     * Adds the specified value for the given property. This method supports
+     * single values and containers (e.g. collections or arrays) as well. In 
the
+     * latter case, <code>addPropertyDirect()</code> will be called for each
+     * element.
+     *
+     * @param key the property key
+     * @param value the value object
+     * @param delimiter the list delimiter character
+     */
+    private void addPropertyValues(String key, Object value, char delimiter)
+    {
+        Iterator it = PropertyConverter.toIterator(value, delimiter);
+        while (it.hasNext())
+        {
+            addPropertyDirect(key, it.next());
+        }
+    }
+
+    /**
      * interpolate key names to handle ${key} stuff
      *
      * @param base string to interpolate
@@ -1157,4 +1169,74 @@
         return value;
     }
 
+    /**
+     * Copies the content of the specified configuration into this
+     * configuration. If the specified configuration contains a key that is 
also
+     * present in this configuration, the value of this key will be replaced by
+     * the new value. <em>Note:</em> This method won't work well when copying
+     * hierarchical configurations because it is not able to copy information
+     * about the properties' structure (i.e. the parent-child-relationships 
will
+     * get lost). So when dealing with hierarchical configuration objects their
+     * <code>[EMAIL PROTECTED] HierarchicalConfiguration#clone() 
clone()}</code> methods
+     * should be used.
+     *
+     * @param c the configuration to copy (can be <b>null</b>, then this
+     * operation will have no effect)
+     * @since 1.5
+     */
+    public void copy(Configuration c)
+    {
+        if (c != null)
+        {
+            for (Iterator it = c.getKeys(); it.hasNext();)
+            {
+                String key = (String) it.next();
+                Object value = c.getProperty(key);
+                fireEvent(EVENT_SET_PROPERTY, key, value, true);
+                setDetailEvents(false);
+                try
+                {
+                    clearProperty(key);
+                    addPropertyValues(key, value, DISABLED_DELIMITER);
+                }
+                finally
+                {
+                    setDetailEvents(true);
+                }
+                fireEvent(EVENT_SET_PROPERTY, key, value, false);
+            }
+        }
+    }
+
+    /**
+     * Appends the content of the specified configuration to this 
configuration.
+     * The values of all properties contained in the specified configuration
+     * will be appended to this configuration. So if a property is already
+     * present in this configuration, its new value will be a union of the
+     * values in both configurations. <em>Note:</em> This method won't work
+     * well when appending hierarchical configurations because it is not able 
to
+     * copy information about the properties' structure (i.e. the
+     * parent-child-relationships will get lost). So when dealing with
+     * hierarchical configuration objects their
+     * <code>[EMAIL PROTECTED] HierarchicalConfiguration#clone() 
clone()}</code> methods
+     * should be used.
+     *
+     * @param c the configuration to be appended (can be <b>null</b>, then this
+     * operation will have no effect)
+     * @since 1.5
+     */
+    public void append(Configuration c)
+    {
+        if (c != null)
+        {
+            for (Iterator it = c.getKeys(); it.hasNext();)
+            {
+                String key = (String) it.next();
+                Object value = c.getProperty(key);
+                fireEvent(EVENT_ADD_PROPERTY, key, value, true);
+                addPropertyValues(key, value, DISABLED_DELIMITER);
+                fireEvent(EVENT_ADD_PROPERTY, key, value, false);
+            }
+        }
+    }
 }

Modified: 
commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/ConfigurationUtils.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/ConfigurationUtils.java?rev=567771&r1=567770&r2=567771&view=diff
==============================================================================
--- 
commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/ConfigurationUtils.java
 (original)
+++ 
commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/ConfigurationUtils.java
 Mon Aug 20 10:57:08 2007
@@ -121,14 +121,14 @@
     }
 
     /**
-     * Copy all properties from the source configuration to the target
+     * <p>Copy all properties from the source configuration to the target
      * configuration. Properties in the target configuration are replaced with
-     * the properties with the same key in the source configuration.
-     * <em>Note:</em> This method won't work well on hierarchical 
configurations
-     * because it is not able to copy information about the properties'
-     * structure. So when dealing with hierarchical configuration objects their
-     * <code>[EMAIL PROTECTED] HierarchicalConfiguration#clone() 
clone()}</code> methods
-     * should be used.
+     * the properties with the same key in the source configuration.</p>
+     * <p><em>Note:</em> This method is not able to handle some specifics of
+     * configurations derived from <code>AbstractConfiguration</code> (e.g.
+     * list delimiters). For a full support of all of these features the
+     * <code>copy()</code> method of <code>AbstractConfiguration</code> should
+     * be used. In a future release this method might become deprecated.</p>
      *
      * @param source the source configuration
      * @param target the target configuration
@@ -145,9 +145,14 @@
     }
 
     /**
-     * Append all properties from the source configuration to the target
+     * <p>Append all properties from the source configuration to the target
      * configuration. Properties in the source configuration are appended to
-     * the properties with the same key in the target configuration.
+     * the properties with the same key in the target configuration.</p>
+     * <p><em>Note:</em> This method is not able to handle some specifics of
+     * configurations derived from <code>AbstractConfiguration</code> (e.g.
+     * list delimiters). For a full support of all of these features the
+     * <code>copy()</code> method of <code>AbstractConfiguration</code> should
+     * be used. In a future release this method might become deprecated.</p>
      *
      * @param source the source configuration
      * @param target the target configuration

Modified: 
commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestAbstractConfigurationBasicFeatures.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestAbstractConfigurationBasicFeatures.java?rev=567771&r1=567770&r2=567771&view=diff
==============================================================================
--- 
commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestAbstractConfigurationBasicFeatures.java
 (original)
+++ 
commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestAbstractConfigurationBasicFeatures.java
 Mon Aug 20 10:57:08 2007
@@ -19,10 +19,14 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
 
 import junit.framework.TestCase;
 
@@ -34,6 +38,12 @@
  */
 public class TestAbstractConfigurationBasicFeatures extends TestCase
 {
+    /** Constant for the prefix of test keys.*/
+    private static final String KEY_PREFIX = "key";
+
+    /** Constant for the number of properties in tests for copy operations.*/
+    private static final int PROP_COUNT = 12;
+
     /**
      * Tests the clear() implementation of AbstractConfiguration if the 
iterator
      * returned by getKeys() does not support the remove() operation.
@@ -124,6 +134,217 @@
     }
 
     /**
+     * Tests the copy() method.
+     */
+    public void testCopy()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        Configuration srcConfig = setUpSourceConfig();
+        config.copy(srcConfig);
+        for (int i = 0; i < PROP_COUNT; i++)
+        {
+            String key = KEY_PREFIX + i;
+            if (srcConfig.containsKey(key))
+            {
+                assertEquals("Value not replaced: " + key, srcConfig
+                        .getProperty(key), config.getProperty(key));
+            }
+            else
+            {
+                assertEquals("Value modified: " + key, "value" + i, config
+                        .getProperty(key));
+            }
+        }
+    }
+
+    /**
+     * Tests the copy() method when properties with multiple values and escaped
+     * list delimiters are involved.
+     */
+    public void testCopyWithLists()
+    {
+        Configuration srcConfig = setUpSourceConfig();
+        AbstractConfiguration config = setUpDestConfig();
+        config.copy(srcConfig);
+        checkListProperties(config);
+    }
+
+    /**
+     * Tests the events generated by a copy() operation.
+     */
+    public void testCopyEvents()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        Configuration srcConfig = setUpSourceConfig();
+        CollectingConfigurationListener l = new 
CollectingConfigurationListener();
+        config.addConfigurationListener(l);
+        config.copy(srcConfig);
+        checkCopyEvents(l, srcConfig, 
AbstractConfiguration.EVENT_SET_PROPERTY);
+    }
+
+    /**
+     * Tests copying a null configuration. This should be a noop.
+     */
+    public void testCopyNull()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        config.copy(null);
+        ConfigurationAssert.assertEquals(setUpDestConfig(), config);
+    }
+
+    /**
+     * Tests the append() method.
+     */
+    public void testAppend()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        Configuration srcConfig = setUpSourceConfig();
+        config.append(srcConfig);
+        for (int i = 0; i < PROP_COUNT; i++)
+        {
+            String key = KEY_PREFIX + i;
+            if (srcConfig.containsKey(key))
+            {
+                List values = config.getList(key);
+                assertEquals("Value not added: " + key, 2, values.size());
+                assertEquals("Wrong value 1 for " + key, "value" + i, values
+                        .get(0));
+                assertEquals("Wrong value 2 for " + key, "src" + i, values
+                        .get(1));
+            }
+            else
+            {
+                assertEquals("Value modified: " + key, "value" + i, config
+                        .getProperty(key));
+            }
+        }
+    }
+
+    /**
+     * Tests the append() method when properties with multiple values and
+     * escaped list delimiters are involved.
+     */
+    public void testAppendWithLists()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        config.append(setUpSourceConfig());
+        checkListProperties(config);
+    }
+
+    /**
+     * Tests the events generated by an append() operation.
+     */
+    public void testAppendEvents()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        Configuration srcConfig = setUpSourceConfig();
+        CollectingConfigurationListener l = new 
CollectingConfigurationListener();
+        config.addConfigurationListener(l);
+        config.append(srcConfig);
+        checkCopyEvents(l, srcConfig, 
AbstractConfiguration.EVENT_ADD_PROPERTY);
+    }
+
+    /**
+     * Tests appending a null configuration. This should be a noop.
+     */
+    public void testAppendNull()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        config.append(null);
+        ConfigurationAssert.assertEquals(setUpDestConfig(), config);
+    }
+
+    /**
+     * Creates the source configuration for testing the copy() and append()
+     * methods. This configuration contains keys with an odd index and values
+     * starting with the prefix "src". There are also some list properties.
+     *
+     * @return the source configuration for copy operations
+     */
+    private Configuration setUpSourceConfig()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        for (int i = 1; i < PROP_COUNT; i += 2)
+        {
+            config.addProperty(KEY_PREFIX + i, "src" + i);
+        }
+        config.addProperty("list1", "1,2,3");
+        config.addProperty("list2", "3\\,1415,9\\,81");
+        return config;
+    }
+
+    /**
+     * Creates the destination configuration for testing the copy() and 
append()
+     * methods. This configuration contains keys with a running index and
+     * corresponding values starting with the prefix "value".
+     *
+     * @return the destination configuration for copy operations
+     */
+    private AbstractConfiguration setUpDestConfig()
+    {
+        AbstractConfiguration config = new TestConfigurationImpl(
+                new PropertiesConfiguration());
+        for (int i = 0; i < PROP_COUNT; i++)
+        {
+            config.addProperty(KEY_PREFIX + i, "value" + i);
+        }
+        return config;
+    }
+
+    /**
+     * Tests the values of list properties after a copy operation.
+     *
+     * @param config the configuration to test
+     */
+    private void checkListProperties(Configuration config)
+    {
+        List values = config.getList("list1");
+        assertEquals("Wrong number of elements in list 1", 3, values.size());
+        values = config.getList("list2");
+        assertEquals("Wrong number of elements in list 2", 2, values.size());
+        assertEquals("Wrong value 1", "3,1415", values.get(0));
+        assertEquals("Wrong value 2", "9,81", values.get(1));
+    }
+
+    /**
+     * Tests whether the correct events are received for a copy operation.
+     *
+     * @param l the event listener
+     * @param src the configuration that was copied
+     * @param eventType the expected event type
+     */
+    private void checkCopyEvents(CollectingConfigurationListener l,
+            Configuration src, int eventType)
+    {
+        Map events = new HashMap();
+        for (Iterator it = l.events.iterator(); it.hasNext();)
+        {
+            ConfigurationEvent e = (ConfigurationEvent) it.next();
+            assertEquals("Wrong event type", eventType, e.getType());
+            assertTrue("Unknown property: " + e.getPropertyName(), src
+                    .containsKey(e.getPropertyName()));
+            assertEquals("Wrong property value for " + e.getPropertyName(), e
+                    .getPropertyValue(), src.getProperty(e.getPropertyName()));
+            if (!e.isBeforeUpdate())
+            {
+                assertTrue("After event without before event", events
+                        .containsKey(e.getPropertyName()));
+            }
+            else
+            {
+                events.put(e.getPropertyName(), e);
+            }
+        }
+
+        for (Iterator it = src.getKeys(); it.hasNext();)
+        {
+            String key = (String) it.next();
+            assertTrue("No event received for key " + key, events
+                    .containsKey(key));
+        }
+    }
+
+    /**
      * A test configuration implementation. This implementation inherits
      * directly from AbstractConfiguration. For implementing the required
      * functionality another implementation of AbstractConfiguration is used;
@@ -173,6 +394,21 @@
         protected void clearPropertyDirect(String key)
         {
             config.clearPropertyDirect(key);
+        }
+    }
+
+    /**
+     * An event listener implementation that simply collects all received
+     * configuration events.
+     */
+    static class CollectingConfigurationListener implements
+            ConfigurationListener
+    {
+        List events = new ArrayList();
+
+        public void configurationChanged(ConfigurationEvent event)
+        {
+            events.add(event);
         }
     }
 }

Modified: 
commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestPropertiesConfiguration.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestPropertiesConfiguration.java?rev=567771&r1=567770&r2=567771&view=diff
==============================================================================
--- 
commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestPropertiesConfiguration.java
 (original)
+++ 
commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestPropertiesConfiguration.java
 Mon Aug 20 10:57:08 2007
@@ -757,6 +757,66 @@
     }
 
     /**
+     * Tests copying another configuration into the test configuration. This
+     * test ensures that the layout object is informed about the newly added
+     * properties.
+     */
+    public void testCopyAndSave() throws ConfigurationException
+    {
+        Configuration copyConf = setUpCopyConfig();
+        conf.copy(copyConf);
+        checkCopiedConfig(copyConf);
+    }
+
+    /**
+     * Tests appending a configuration to the test configuration. Again it has
+     * to be ensured that the layout object is correctly updated.
+     */
+    public void testAppendAndSave() throws ConfigurationException
+    {
+        Configuration copyConf = setUpCopyConfig();
+        conf.append(copyConf);
+        checkCopiedConfig(copyConf);
+    }
+
+    /**
+     * Creates a configuration that can be used for testing copy operations.
+     *
+     * @return the configuration to be copied
+     */
+    private Configuration setUpCopyConfig()
+    {
+        final int count = 25;
+        Configuration result = new BaseConfiguration();
+        for (int i = 1; i <= count; i++)
+        {
+            result.addProperty("copyKey" + i, "copyValue" + i);
+        }
+        return result;
+    }
+
+    /**
+     * Tests whether the data of a configuration that was copied into the test
+     * configuration is correctly saved.
+     *
+     * @param copyConf the copied configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    private void checkCopiedConfig(Configuration copyConf)
+            throws ConfigurationException
+    {
+        conf.save(testSavePropertiesFile);
+        PropertiesConfiguration checkConf = new PropertiesConfiguration(
+                testSavePropertiesFile);
+        for (Iterator it = copyConf.getKeys(); it.hasNext();)
+        {
+            String key = (String) it.next();
+            assertEquals("Wrong value for property " + key, checkConf
+                    .getProperty(key), copyConf.getProperty(key));
+        }
+    }
+
+    /**
      * A dummy layout implementation for checking whether certain methods are
      * correctly called by the configuration.
      */

Modified: commons/proper/configuration/trunk/xdocs/changes.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/xdocs/changes.xml?rev=567771&r1=567770&r2=567771&view=diff
==============================================================================
--- commons/proper/configuration/trunk/xdocs/changes.xml (original)
+++ commons/proper/configuration/trunk/xdocs/changes.xml Mon Aug 20 10:57:08 
2007
@@ -23,6 +23,13 @@
 
   <body>
     <release version="1.5-SNAPSHOT" date="in SVN" description="">
+      <action dev="oheger" type="fix" issue="CONFIGURATION-272">
+        New copy() and append() methods have been added to 
AbstractConfiguration.
+        They replace the methods with the same names in ConfigurationUtils,
+        which do not handle all features of AbstractConfiguration properly 
(e.g.
+        list delimiters in property values are incorrectly treated). To avoid
+        such problems, the new methods should be used.
+      </action>
       <action dev="oheger" type="fix" issue="CONFIGURATION-291">
         The addNodes() method of hierarchical file-based configurations now
         correctly triggers an auto save.

Modified: commons/proper/configuration/trunk/xdocs/userguide/howto_utilities.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/xdocs/userguide/howto_utilities.xml?rev=567771&r1=567770&r2=567771&view=diff
==============================================================================
--- commons/proper/configuration/trunk/xdocs/userguide/howto_utilities.xml 
(original)
+++ commons/proper/configuration/trunk/xdocs/userguide/howto_utilities.xml Mon 
Aug 20 10:57:08 2007
@@ -37,13 +37,13 @@
        <p>
       Often it is required to copy the data of one <code>Configuration</code>
       object into another one. For this purpose the
-      <code><a 
href="apidocs/org/apache/commons/configuration/ConfigurationUtils.html">
-      ConfigurationUtils</a></code> class can be used. It provides two methods
-      implementing a basic copy operation:
+      <code><a 
href="apidocs/org/apache/commons/configuration/AbstractConfiguration.html">
+      AbstractConfiguration</a></code> class (which serves as the base class 
for
+      most of the configuration implementations shipped with this library)
+      provides two methods implementing a basic copy operation:
       <ul>
-        <li><code>append()</code> takes the source and the target 
configurations
-        as arguments and adds all properties found in the source to the
-        target configuration.</li>
+        <li><code>append()</code> takes the configuration to be copied
+        as argument and adds all of its properties to the current 
configuration.</li>
         <li><code>copy()</code> is very similar to <code>append()</code>. The
         difference is that properties that already exist in the target
         configuration are replaced by the properties of the source 
configuration.


Reply via email to