commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ohe...@apache.org
Subject svn commit: r397173 - in /jakarta/commons/proper/configuration/trunk/src: java/org/apache/commons/configuration/CombinedConfiguration.java test/org/apache/commons/configuration/TestCombinedConfiguration.java
Date Wed, 26 Apr 2006 11:05:53 GMT
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>{@link 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>{@link 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>&quot;database.tables&quot;</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 &quot;transformed&quot; 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: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org


Mime
View raw message