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.