Author: oheger
Date: Wed Apr 26 04:05:50 2006
New Revision: 397173
URL: http://svn.apache.org/viewcvs?rev=397173&view=rev
Log:
Added CombinedConfiguration class with unit tests
Added:
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/CombinedConfiguration.java
(with props)
jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestCombinedConfiguration.java
(with props)
Added:
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/CombinedConfiguration.java
URL:
http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/CombinedConfiguration.java?rev=397173&view=auto
==============================================================================
---
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/CombinedConfiguration.java
(added)
+++
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/CombinedConfiguration.java
Wed Apr 26 04:05:50 2006
@@ -0,0 +1,532 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationKey;
+import org.apache.commons.configuration.tree.DefaultExpressionEngine;
+import org.apache.commons.configuration.tree.NodeCombiner;
+import org.apache.commons.configuration.tree.UnionCombiner;
+import org.apache.commons.configuration.tree.ViewNode;
+
+/**
+ * <p>
+ * A hierarchical composite configuration class.
+ * </p>
+ * <p>
+ * This class maintains a list of configuration objects, which can be added
+ * using the divers <code>addConfiguration()</code> methods. After that the
+ * configurations can be accessed either by name (if one was provided when the
+ * configuration was added) or by index. For the whole set of managed
+ * configurations a logical node structure is constructed. For this purpose a
+ * <code>[EMAIL PROTECTED] org.apache.commons.configuration.tree.NodeCombiner
NodeCombiner}</code>
+ * object can be set. This makes it possible to specify different algorithms
for
+ * the combination process.
+ * </p>
+ * <p>
+ * The big advantage of this class is that it creates a truely hierarchical
+ * structure of all the properties stored in the contained configurations -
even
+ * if some of them are no hierarchical configurations per se. So all enhanced
+ * features provided by a hierarchical configuration (e.g. choosing an
+ * expression engine) are applicable.
+ * </p>
+ * <p>
+ * The class works by registering itself as an event listener add all added
+ * configurations. So it gets notified whenever one of these configurations is
+ * changed and can invalidate its internal node structure. The next time a
+ * property is accessed the node structure will be re-constructed using the
+ * current state of the managed configurations. Node that, depending on the
used
+ * <code>NodeCombiner</code>, this may be a complex operation.
+ * </p>
+ * <p>
+ * It is not strictly forbidden to manipulate a
+ * <code>CombinedConfiguration</code> directly, but the results may be
+ * unpredictable. For instance some node combiners use special view nodes for
+ * linking parts of the original configurations' data together. If new
+ * properties are added to such a special node, they do not belong to any of
the
+ * managed configurations and thus hang in the air. It is also possible that
+ * direct updates on a <code>CombinedConfiguration</code> are incompatible
+ * with the used node combiner (e.g. if the
+ * <code>[EMAIL PROTECTED]
org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}</code>
+ * is used and properties are removed the resulting node structure may be
+ * incorrect because some properties that were hidden by the removed properties
+ * are not visible). So it is recommended to perform updates only on the
managed
+ * configurations.
+ * </p>
+ * <p>
+ * Whenever the node structure of a <code>CombinedConfiguration</code> becomes
+ * invalid (either because one of the contained configurations was modified or
+ * because the <code>invalidate()</code> method was directly called) an event
+ * is generated. So this can be detected by interested event listeners. This
+ * also makes it possible to add a combined configuration into another one.
+ * </p>
+ * <p>
+ * Implementation note: Adding and removing configurations to and from a
+ * combined configuration is not thread-safe. If a combined configuration is
+ * manipulated by multiple threads, the developer has to take care about
+ * properly synchronization.
+ * </p>
+ *
+ * @since 1.3
+ * @version $Id$
+ */
+public class CombinedConfiguration extends HierarchicalConfiguration implements
+ ConfigurationListener
+{
+ /**
+ * Constant for the invalidate event that is fired when the internal node
+ * structure becomes invalid.
+ */
+ public static final int EVENT_COMBINED_INVALIDATE = 40;
+
+ /** Constant for the expression engine for parsing the at path. */
+ private static final DefaultExpressionEngine AT_ENGINE = new
DefaultExpressionEngine();
+
+ /** Constant for the default node combiner. */
+ private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
+
+ /** Stores the combiner. */
+ private NodeCombiner nodeCombiner;
+
+ /** Stores the combined root node. */
+ private ConfigurationNode combinedRoot;
+
+ /** Stores a list with the contained configurations. */
+ private List configurations;
+
+ /** Stores a map with the named configurations. */
+ private Map namedConfigurations;
+
+ /**
+ * Creates a new instance of <code>CombinedConfiguration</code> and
+ * initializes the combiner to be used.
+ *
+ * @param comb the node combiner (can be <b>null</b>, then a union combiner
+ * is used as default)
+ */
+ public CombinedConfiguration(NodeCombiner comb)
+ {
+ setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
+ configurations = new ArrayList();
+ namedConfigurations = new HashMap();
+ }
+
+ /**
+ * Creates a new instance of <code>CombinedConfiguration</code> that uses
+ * a union combiner.
+ *
+ * @see org.apache.commons.configuration.tree.UnionCombiner
+ */
+ public CombinedConfiguration()
+ {
+ this(null);
+ }
+
+ /**
+ * Returns the node combiner that is used for creating the combined node
+ * structure.
+ *
+ * @return the node combiner
+ */
+ public NodeCombiner getNodeCombiner()
+ {
+ return nodeCombiner;
+ }
+
+ /**
+ * Sets the node combiner. This object will be used when the combined node
+ * structure is to be constructed. It must not be <b>null</b>, otherwise an
+ * <code>IllegalArgumentException</code> exception is thrown. Changing the
+ * node combiner causes an invalidation of this combined configuration, so
+ * that the new combiner immediately takes effect.
+ *
+ * @param nodeCombiner the node combiner
+ */
+ public void setNodeCombiner(NodeCombiner nodeCombiner)
+ {
+ if (nodeCombiner == null)
+ {
+ throw new IllegalArgumentException(
+ "Node combiner must not be null!");
+ }
+ this.nodeCombiner = nodeCombiner;
+ invalidate();
+ }
+
+ /**
+ * Adds a new configuration to this combined configuration. It is possible
+ * (but not mandatory) to give the new configuration a name. This name must
+ * be unique, otherwise a <code>ConfigurationRuntimeException</code> will
+ * be thrown. With the optional <code>at</code> argument you can specify
+ * where in the resulting node structure the content of the added
+ * configuration should appear. This is a string that uses dots as property
+ * delimiters (independent on the current expression engine). For instance
+ * if you pass in the string <code>"database.tables"</code>,
+ * all properties of the added configuration will occur in this branch.
+ *
+ * @param config the configuration to add (must not be <b>null</b>)
+ * @param name the name of this configuration (can be <b>null</b>)
+ * @param at the position of this configuration in the combined tree (can
be
+ * <b>null</b>)
+ */
+ public void addConfiguration(AbstractConfiguration config, String name,
+ String at)
+ {
+ if (config == null)
+ {
+ throw new IllegalArgumentException(
+ "Added configuration must not be null!");
+ }
+ if (name != null && namedConfigurations.containsKey(name))
+ {
+ throw new ConfigurationRuntimeException(
+ "A configuration with the name '"
+ + name
+ + "' already exists in this combined
configuration!");
+ }
+
+ ConfigData cd = new ConfigData(config, name, at);
+ configurations.add(cd);
+ if (name != null)
+ {
+ namedConfigurations.put(name, config);
+ }
+
+ config.addConfigurationListener(this);
+ invalidate();
+ }
+
+ /**
+ * Adds a new configuration to this combined configuration with an optional
+ * name. The new configuration's properties will be added under the root of
+ * the combined node structure.
+ *
+ * @param config the configuration to add (must not be <b>null</b>)
+ * @param name the name of this configuration (can be <b>null</b>)
+ */
+ public void addConfiguration(AbstractConfiguration config, String name)
+ {
+ addConfiguration(config, name, null);
+ }
+
+ /**
+ * Adds a new configuration to this combined configuration. The new
+ * configuration is not given a name. Its properties will be added under
the
+ * root of the combined node structure.
+ *
+ * @param config the configuration to add (must not be <b>null</b>)
+ */
+ public void addConfiguration(AbstractConfiguration config)
+ {
+ addConfiguration(config, null, null);
+ }
+
+ /**
+ * Returns the number of configurations that are contained in this combined
+ * configuration.
+ *
+ * @return the number of contained configurations
+ */
+ public int getNumberOfConfigurations()
+ {
+ return configurations.size();
+ }
+
+ /**
+ * Returns the configuration at the specified index. The contained
+ * configurations are numbered in the order they were added to this
combined
+ * configuration. The index of the first configuration is 0.
+ *
+ * @param index the index
+ * @return the configuration at this index
+ */
+ public Configuration getConfiguration(int index)
+ {
+ ConfigData cd = (ConfigData) configurations.get(index);
+ return cd.getConfiguration();
+ }
+
+ /**
+ * Returns the configuration with the given name. This can be <b>null</b>
+ * if no such configuration exists.
+ *
+ * @param name the name of the configuration
+ * @return the configuration with this name
+ */
+ public Configuration getConfiguration(String name)
+ {
+ return (Configuration) namedConfigurations.get(name);
+ }
+
+ /**
+ * Removes the specified configuration from this combined configuration.
+ *
+ * @param config the configuration to be removed
+ * @return a flag whether this configuration was found and could be removed
+ */
+ public boolean removeConfiguration(Configuration config)
+ {
+ for (int index = 0; index < getNumberOfConfigurations(); index++)
+ {
+ if (((ConfigData) configurations.get(index)).getConfiguration() ==
config)
+ {
+ removeConfigurationAt(index);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Removes the configuration at the specified index.
+ *
+ * @param index the index
+ * @return the removed configuration
+ */
+ public Configuration removeConfigurationAt(int index)
+ {
+ ConfigData cd = (ConfigData) configurations.remove(index);
+ if (cd.getName() != null)
+ {
+ namedConfigurations.remove(cd.getName());
+ }
+ cd.getConfiguration().removeConfigurationListener(this);
+ invalidate();
+ return cd.getConfiguration();
+ }
+
+ /**
+ * Removes the configuration with the specified name.
+ *
+ * @param name the name of the configuration to be removed
+ * @return the removed configuration (<b>null</b> if this configuration
+ * was not found)
+ */
+ public Configuration removeConfiguration(String name)
+ {
+ Configuration conf = getConfiguration(name);
+ if (conf != null)
+ {
+ removeConfiguration(conf);
+ }
+ return conf;
+ }
+
+ /**
+ * Returns a set with the names of all configurations contained in this
+ * combined configuration. Of course here are only these configurations
+ * listed, for which a name was specified when they were added.
+ *
+ * @return a set with the names of the contained configurations (never
+ * <b>null</b>)
+ */
+ public Set getConfigurationNames()
+ {
+ return namedConfigurations.keySet();
+ }
+
+ /**
+ * Invalidates this combined configuration. This means that the next time a
+ * property is accessed the combined node structure must be re-constructed.
+ * Invalidation of a combined configuration also means that an event of
type
+ * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other
+ * events most times appear twice (once before and once after an update),
+ * this event is only fired once (after update).
+ */
+ public void invalidate()
+ {
+ synchronized (getNodeCombiner()) // use combiner as lock
+ {
+ combinedRoot = null;
+ }
+ fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
+ }
+
+ /**
+ * Event listener call back for configuration update events. This method is
+ * called whenever one of the contained configurations was modified. It
+ * invalidates this combined configuration.
+ *
+ * @param event the update event
+ */
+ public void configurationChanged(ConfigurationEvent event)
+ {
+ invalidate();
+ }
+
+ /**
+ * Returns the configuration root node of this combined configuration. This
+ * method will construct a combined node structure using the current node
+ * combiner if necessary.
+ *
+ * @return the combined root node
+ */
+ public ConfigurationNode getRootNode()
+ {
+ synchronized (getNodeCombiner())
+ {
+ if (combinedRoot == null)
+ {
+ combinedRoot = constructCombinedNode();
+ }
+ return combinedRoot;
+ }
+ }
+
+ /**
+ * Creates the root node of this combined configuration.
+ *
+ * @return the combined root node
+ */
+ private ConfigurationNode constructCombinedNode()
+ {
+ if (getNumberOfConfigurations() < 1)
+ {
+ return new ViewNode();
+ }
+
+ else
+ {
+ Iterator it = configurations.iterator();
+ ConfigurationNode node = ((ConfigData) it.next())
+ .getTransformedRoot();
+ while (it.hasNext())
+ {
+ node = getNodeCombiner().combine(node,
+ ((ConfigData) it.next()).getTransformedRoot());
+ }
+ return node;
+ }
+ }
+
+ /**
+ * An internal helper class for storing information about contained
+ * configurations.
+ */
+ static class ConfigData
+ {
+ /** Stores a reference to the configuration. */
+ private AbstractConfiguration configuration;
+
+ /** Stores the name under which the configuration is stored. */
+ private String name;
+
+ /** Stores the at information. */
+ private Collection atPath;
+
+ /**
+ * Creates a new instance of <code>ConfigData</code> and initializes
+ * it.
+ *
+ * @param config the configuration
+ * @param n the name
+ * @param at the at position
+ */
+ public ConfigData(AbstractConfiguration config, String n, String at)
+ {
+ configuration = config;
+ name = n;
+ atPath = parseAt(at);
+ }
+
+ /**
+ * Returns the stored configuration.
+ *
+ * @return the configuration
+ */
+ public AbstractConfiguration getConfiguration()
+ {
+ return configuration;
+ }
+
+ /**
+ * Returns the configuration's name.
+ *
+ * @return the name
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Returns the transformed root node of the stored configuration. The
+ * term "transformed" means that an eventually defined at
path
+ * has been applied.
+ *
+ * @return the transformed root node
+ */
+ public ConfigurationNode getTransformedRoot()
+ {
+ ViewNode result = new ViewNode();
+ ViewNode atParent = result;
+
+ if (atPath != null)
+ {
+ // Build the complete path
+ for (Iterator it = atPath.iterator(); it.hasNext();)
+ {
+ ViewNode node = new ViewNode();
+ node.setName((String) it.next());
+ atParent.addChild(node);
+ atParent = node;
+ }
+ }
+
+ // Copy data of the root node to the new path
+ HierarchicalConfiguration hc = ConfigurationUtils
+ .convertToHierarchical(getConfiguration());
+ atParent.appendChildren(hc.getRootNode());
+ atParent.appendAttributes(hc.getRootNode());
+
+ return result;
+ }
+
+ /**
+ * Splits the at path into its components.
+ *
+ * @param at the at string
+ * @return a collection with the names of the single components
+ */
+ private Collection parseAt(String at)
+ {
+ if (at == null)
+ {
+ return null;
+ }
+
+ Collection result = new ArrayList();
+ DefaultConfigurationKey.KeyIterator it = new
DefaultConfigurationKey(
+ AT_ENGINE, at).iterator();
+ while (it.hasNext())
+ {
+ result.add(it.nextKey());
+ }
+ return result;
+ }
+ }
+}
Propchange:
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/CombinedConfiguration.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/CombinedConfiguration.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange:
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/CombinedConfiguration.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestCombinedConfiguration.java
URL:
http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestCombinedConfiguration.java?rev=397173&view=auto
==============================================================================
---
jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestCombinedConfiguration.java
(added)
+++
jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestCombinedConfiguration.java
Wed Apr 26 04:05:50 2006
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.tree.NodeCombiner;
+import org.apache.commons.configuration.tree.UnionCombiner;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+/**
+ * Test class for CombinedConfiguration.
+ *
+ * @version $Id$
+ */
+public class TestCombinedConfiguration extends TestCase
+{
+ /** Constant for the name of a sub configuration. */
+ static final String TEST_NAME = "SUBCONFIG";
+
+ /** Constant for a test key. */
+ static final String TEST_KEY = "test.value";
+
+ /** The configuration to be tested. */
+ CombinedConfiguration config;
+
+ /** The test event listener. */
+ CombinedListener listener;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ config = new CombinedConfiguration();
+ listener = new CombinedListener();
+ config.addConfigurationListener(listener);
+ }
+
+ /**
+ * Tests accessing a newly created combined configuration.
+ */
+ public void testInit()
+ {
+ assertEquals("Already configurations contained", 0, config
+ .getNumberOfConfigurations());
+ assertTrue("Set of names is not empty", config.getConfigurationNames()
+ .isEmpty());
+ assertTrue("Wrong node combiner",
+ config.getNodeCombiner() instanceof UnionCombiner);
+ assertNull("Test config was found",
config.getConfiguration(TEST_NAME));
+ }
+
+ /**
+ * Tests adding a configuration (without further information).
+ */
+ public void testAddConfiguration()
+ {
+ AbstractConfiguration c = setUpTestConfiguration();
+ config.addConfiguration(c);
+ checkAddConfig(c);
+ assertEquals("Wrong number of configs", 1, config
+ .getNumberOfConfigurations());
+ assertTrue("Name list is not empty", config.getConfigurationNames()
+ .isEmpty());
+ assertSame("Added config not found", c, config.getConfiguration(0));
+ assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
+ listener.checkEvent(1, 0);
+ }
+
+ /**
+ * Tests adding a configuration with a name.
+ */
+ public void testAddConfigurationWithName()
+ {
+ AbstractConfiguration c = setUpTestConfiguration();
+ config.addConfiguration(c, TEST_NAME);
+ checkAddConfig(c);
+ assertEquals("Wrong number of configs", 1, config
+ .getNumberOfConfigurations());
+ assertSame("Added config not found", c, config.getConfiguration(0));
+ assertSame("Added config not found by name", c, config
+ .getConfiguration(TEST_NAME));
+ Set names = config.getConfigurationNames();
+ assertEquals("Wrong number of config names", 1, names.size());
+ assertTrue("Name not found", names.contains(TEST_NAME));
+ assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
+ listener.checkEvent(1, 0);
+ }
+
+ /**
+ * Tests adding a configuration with a name when this name already exists.
+ * This should cause an exception.
+ */
+ public void testAddConfigurationWithNameTwice()
+ {
+ config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
+ try
+ {
+ config.addConfiguration(setUpTestConfiguration(), TEST_NAME,
+ "prefix");
+ fail("Could add config with same name!");
+ }
+ catch (ConfigurationRuntimeException cex)
+ {
+ // ok
+ }
+ }
+
+ /**
+ * Tests adding a configuration and specifying an at position.
+ */
+ public void testAddConfigurationAt()
+ {
+ AbstractConfiguration c = setUpTestConfiguration();
+ config.addConfiguration(c, null, "my");
+ checkAddConfig(c);
+ assertTrue("Wrong property value", config.getBoolean("my." +
TEST_KEY));
+ }
+
+ /**
+ * Tests adding a configuration with a complex at position. Here the at
path
+ * contains a dot, which must be escaped.
+ */
+ public void testAddConfigurationComplexAt()
+ {
+ AbstractConfiguration c = setUpTestConfiguration();
+ config.addConfiguration(c, null, "This..is.a.complex");
+ checkAddConfig(c);
+ assertTrue("Wrong property value", config
+ .getBoolean("This..is.a.complex." + TEST_KEY));
+ }
+
+ /**
+ * Checks if a configuration was correctly added to the combined config.
+ *
+ * @param c the config to check
+ */
+ private void checkAddConfig(AbstractConfiguration c)
+ {
+ Collection listeners = c.getConfigurationListeners();
+ assertEquals("Wrong number of configuration listeners", 1, listeners
+ .size());
+ assertTrue("Combined config is no listener",
listeners.contains(config));
+ }
+
+ /**
+ * Tests adding a null configuration. This should cause an exception to be
+ * thrown.
+ */
+ public void testAddNullConfiguration()
+ {
+ try
+ {
+ config.addConfiguration(null);
+ fail("Could add null configuration!");
+ }
+ catch (IllegalArgumentException iex)
+ {
+ // ok
+ }
+ }
+
+ /**
+ * Tests accessing properties if no configurations have been added.
+ */
+ public void testAccessPropertyEmpty()
+ {
+ assertFalse("Found a key", config.containsKey(TEST_KEY));
+ assertNull("Key has a value", config.getString("test.comment"));
+ assertTrue("Config is not empty", config.isEmpty());
+ }
+
+ /**
+ * Tests accessing properties if multiple configurations have been added.
+ */
+ public void testAccessPropertyMulti()
+ {
+ config.addConfiguration(setUpTestConfiguration());
+ config.addConfiguration(setUpTestConfiguration(), null, "prefix1");
+ config.addConfiguration(setUpTestConfiguration(), null, "prefix2");
+ assertTrue("Prop1 not found", config.getBoolean(TEST_KEY));
+ assertTrue("Prop 2 not found", config.getBoolean("prefix1." +
TEST_KEY));
+ assertTrue("Prop 3 not found", config.getBoolean("prefix2." +
TEST_KEY));
+ assertFalse("Configuration is empty", config.isEmpty());
+ listener.checkEvent(3, 0);
+ }
+
+ /**
+ * Tests removing a configuration.
+ */
+ public void testRemoveConfiguration()
+ {
+ AbstractConfiguration c = setUpTestConfiguration();
+ config.addConfiguration(c);
+ checkAddConfig(c);
+ assertTrue("Config could not be removed",
config.removeConfiguration(c));
+ checkRemoveConfig(c);
+ }
+
+ /**
+ * Tests removing a configuration by index.
+ */
+ public void testRemoveConfigurationAt()
+ {
+ AbstractConfiguration c = setUpTestConfiguration();
+ config.addConfiguration(c);
+ assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
+ checkRemoveConfig(c);
+ }
+
+ /**
+ * Tests removing a configuration by name.
+ */
+ public void testRemoveConfigurationByName()
+ {
+ AbstractConfiguration c = setUpTestConfiguration();
+ config.addConfiguration(c, TEST_NAME);
+ assertSame("Wrong config removed", c, config
+ .removeConfiguration(TEST_NAME));
+ checkRemoveConfig(c);
+ }
+
+ /**
+ * Tests removing a configuration with a name.
+ */
+ public void testRemoveNamedConfiguration()
+ {
+ AbstractConfiguration c = setUpTestConfiguration();
+ config.addConfiguration(c, TEST_NAME);
+ config.removeConfiguration(c);
+ checkRemoveConfig(c);
+ }
+
+ /**
+ * Tests removing a named configuration by index.
+ */
+ public void testRemoveNamedConfigurationAt()
+ {
+ AbstractConfiguration c = setUpTestConfiguration();
+ config.addConfiguration(c, TEST_NAME);
+ assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
+ checkRemoveConfig(c);
+ }
+
+ /**
+ * Tests removing a configuration that was not added prior.
+ */
+ public void testRemoveNonContainedConfiguration()
+ {
+ assertFalse("Could remove non contained config", config
+ .removeConfiguration(setUpTestConfiguration()));
+ listener.checkEvent(0, 0);
+ }
+
+ /**
+ * Tests removing a configuration by name, which is not contained.
+ */
+ public void testRemoveConfigurationByUnknownName()
+ {
+ assertNull("Could remove configuration by unknown name", config
+ .removeConfiguration("unknownName"));
+ listener.checkEvent(0, 0);
+ }
+
+ /**
+ * Tests whether a configuration was completely removed.
+ *
+ * @param c the removed configuration
+ */
+ private void checkRemoveConfig(AbstractConfiguration c)
+ {
+ assertTrue("Listener was not removed", c.getConfigurationListeners()
+ .isEmpty());
+ assertEquals("Wrong number of contained configs", 0, config
+ .getNumberOfConfigurations());
+ assertTrue("Name was not removed", config.getConfigurationNames()
+ .isEmpty());
+ listener.checkEvent(2, 0);
+ }
+
+ /**
+ * Tests if an update of a contained configuration leeds to an invalidation
+ * of the combined configuration.
+ */
+ public void testUpdateContainedConfiguration()
+ {
+ AbstractConfiguration c = setUpTestConfiguration();
+ config.addConfiguration(c);
+ c.addProperty("test.otherTest", "yes");
+ assertEquals("New property not found", "yes", config
+ .getString("test.otherTest"));
+ listener.checkEvent(3, 0);
+ }
+
+ /**
+ * Tests if setting a node combiner causes an invalidation.
+ */
+ public void testSetNodeCombiner()
+ {
+ NodeCombiner combiner = new UnionCombiner();
+ config.setNodeCombiner(combiner);
+ assertSame("Node combiner was not set", combiner, config
+ .getNodeCombiner());
+ listener.checkEvent(1, 0);
+ }
+
+ /**
+ * Tests setting a null node combiner. This should cause an exception.
+ */
+ public void testSetNullNodeCombiner()
+ {
+ try
+ {
+ config.setNodeCombiner(null);
+ fail("Could set null node combiner!");
+ }
+ catch (IllegalArgumentException iex)
+ {
+ // ok
+ }
+ }
+
+ /**
+ * Helper method for creating a test configuration to be added to the
+ * combined configuration.
+ *
+ * @return the test configuration
+ */
+ private AbstractConfiguration setUpTestConfiguration()
+ {
+ HierarchicalConfiguration config = new HierarchicalConfiguration();
+ config.addProperty(TEST_KEY, Boolean.TRUE);
+ config.addProperty("test.comment", "This is a test");
+ return config;
+ }
+
+ /**
+ * Test event listener class for checking if the expected invalidate events
+ * are fired.
+ */
+ static class CombinedListener implements ConfigurationListener
+ {
+ int invalidateEvents;
+
+ int otherEvents;
+
+ public void configurationChanged(ConfigurationEvent event)
+ {
+ if (event.getType() ==
CombinedConfiguration.EVENT_COMBINED_INVALIDATE)
+ {
+ invalidateEvents++;
+ }
+ else
+ {
+ otherEvents++;
+ }
+ }
+
+ /**
+ * Checks if the expected number of events was fired.
+ *
+ * @param expectedInvalidate the expected number of invalidate events
+ * @param expectedOthers the expected number of other events
+ */
+ public void checkEvent(int expectedInvalidate, int expectedOthers)
+ {
+ Assert.assertEquals("Wrong number of invalidate events",
+ expectedInvalidate, invalidateEvents);
+ Assert.assertEquals("Wrong number of other events", expectedOthers,
+ otherEvents);
+ }
+ }
+}
Propchange:
jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestCombinedConfiguration.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestCombinedConfiguration.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange:
jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestCombinedConfiguration.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]