You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by oh...@apache.org on 2006/01/21 17:54:42 UTC

svn commit: r371084 - in /jakarta/commons/proper/configuration/trunk/src: java/org/apache/commons/configuration/SubnodeConfiguration.java test/org/apache/commons/configuration/TestSubnodeConfiguration.java

Author: oheger
Date: Sat Jan 21 08:54:24 2006
New Revision: 371084

URL: http://svn.apache.org/viewcvs?rev=371084&view=rev
Log:
Added initial implementation of SubnodeConfiguration

Added:
    jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java   (with props)
    jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java   (with props)

Added: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java?rev=371084&view=auto
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java (added)
+++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java Sat Jan 21 08:54:24 2006
@@ -0,0 +1,201 @@
+/*
+ * 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 org.apache.commons.configuration.tree.ExpressionEngine;
+
+/**
+ * <p>
+ * A specialized hierarchical configuration class that wraps a single node of
+ * its parent configuration.
+ * </p>
+ * <p>
+ * Configurations of this type are initialized with a parent configuration and a
+ * configuration node of this configuration. This node becomes the root node of
+ * the subnode configuration. All property accessor methods are evaluated
+ * relative to this root node. A good use case for a
+ * <code>SubnodeConfiguration</code> is when multiple properties from a
+ * specific sub tree of the whole configuration need to be accessed. Then a
+ * <code>SubnodeConfiguration</code> can be created with the parent node of
+ * the affected sub tree as root node. This allows for simpler property keys and
+ * is also more efficient.
+ * </p>
+ * <p>
+ * A subnode configuration and its parent configuration operate on the same
+ * hierarchy of configuration nodes. So if modifications are performed at the
+ * subnode configuration, these changes are immideately visible in the parent
+ * configuration. Analogously will updates of the parent configuration affect
+ * the subnode configuration if the sub tree spanned by the subnode
+ * configuration's root node is involved.
+ * </p>
+ * <p>
+ * A subnode configuration and its parent configuration work very close
+ * together. For instance, some flags used by configuration objects (e.g. the
+ * <code>throwExceptionOnMissing</code> flag or the settings for handling list
+ * delimiters) are shared between both. This means if one of these properties is
+ * modified for the subnode configuration, it is also changed for the parent and
+ * vice versa.
+ * </p>
+ * <p>
+ * From its purpose this class is quite similar to
+ * <code>{@link SubsetConfiguration}</code>. The difference is that a subset
+ * configuration of a hierarchical configuration may combine multiple
+ * configuration nodes from different sub trees of the configuration, while all
+ * nodes in a subnode configuration belong to the same sub tree. If an
+ * application can live with this limitation, it is recommended to use this
+ * class instead of <code>SubsetConfiguration</code> because creating a subset
+ * configuration is more expensive than creating a subnode configuration.
+ * </p>
+ *
+ * @since 1.3
+ * @author Oliver Heger
+ * @version $Id$
+ */
+public class SubnodeConfiguration extends HierarchicalConfiguration
+{
+    /** Stores the parent configuration. */
+    private HierarchicalConfiguration parent;
+
+    /**
+     * Creates a new instance of <code>SubnodeConfiguration</code> and
+     * initializes it with the parent configuration and the new root node.
+     *
+     * @param parent the parent configuration
+     * @param root the root node of this subnode configuration
+     */
+    public SubnodeConfiguration(HierarchicalConfiguration parent, Node root)
+    {
+        if (parent == null)
+        {
+            throw new IllegalArgumentException(
+                    "Parent configuration must not be null!");
+        }
+        if (root == null)
+        {
+            throw new IllegalArgumentException("Root node must not be null!");
+        }
+
+        setRoot(root);
+        this.parent = parent;
+    }
+
+    /**
+     * Returns the parent configuration of this subnode configuration.
+     *
+     * @return the parent configuration
+     */
+    public HierarchicalConfiguration getParent()
+    {
+        return parent;
+    }
+
+    /**
+     * Returns the expression engine. This method returns the parent's
+     * expression engine.
+     *
+     * @return the expression engine
+     */
+    public ExpressionEngine getExpressionEngine()
+    {
+        return getParent().getExpressionEngine();
+    }
+
+    /**
+     * Sets the expression engine. This method sets the expression engine at the
+     * parent.
+     *
+     * @param expressionEngine the new expression engine
+     */
+    public void setExpressionEngine(ExpressionEngine expressionEngine)
+    {
+        getParent().setExpressionEngine(expressionEngine);
+    }
+
+    /**
+     * Returns the list delimiter. This method returns the parent's delimiter.
+     *
+     * @return the list delimiter
+     */
+    public char getListDelimiter()
+    {
+        return getParent().getListDelimiter();
+    }
+
+    /**
+     * Sets the list delimiter. The delimiter will also be set for the parent.
+     *
+     * @param listDelimiter the new delimiter
+     */
+    public void setListDelimiter(char listDelimiter)
+    {
+        getParent().setListDelimiter(listDelimiter);
+    }
+
+    /**
+     * Returns a flag if list properties should be splitted when they are added.
+     * This implementation returns the corresponding flag of the parent.
+     *
+     * @return the delimiter parsing flag
+     */
+    public boolean isDelimiterParsingDisabled()
+    {
+        return getParent().isDelimiterParsingDisabled();
+    }
+
+    /**
+     * Sets the delimiter parsing disabled flag. This method will also set this
+     * flag at the parent.
+     *
+     * @param delimiterParsingDisabled the new value of the flag
+     */
+    public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled)
+    {
+        getParent().setDelimiterParsingDisabled(delimiterParsingDisabled);
+    }
+
+    /**
+     * Returns the throw exception on missing flag. This implementation returns
+     * the value of the corresponding flag of the parent.
+     *
+     * @return the throw exception on missing flag
+     */
+    public boolean isThrowExceptionOnMissing()
+    {
+        return getParent().isThrowExceptionOnMissing();
+    }
+
+    /**
+     * Sets the throw exception on missing flag. The value is also set for the
+     * parent.
+     *
+     * @param throwExceptionOnMissing the new value of the flag
+     */
+    public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing)
+    {
+        getParent().setThrowExceptionOnMissing(throwExceptionOnMissing);
+    }
+
+    /**
+     * Creates a new node. This task is delegated to the parent.
+     *
+     * @param name the node's name
+     * @return the new node
+     */
+    protected Node createNode(String name)
+    {
+        return getParent().createNode(name);
+    }
+}

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java?rev=371084&view=auto
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java (added)
+++ jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java Sat Jan 21 08:54:24 2006
@@ -0,0 +1,313 @@
+/*
+ * 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.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+
+import junit.framework.TestCase;
+
+/**
+ * Test case for SubnodeConfiguration.
+ *
+ * @author Oliver Heger
+ * @version $Id$
+ */
+public class TestSubnodeConfiguration extends TestCase
+{
+    /** An array with names of tables (test data). */
+    private static final String[] TABLE_NAMES =
+    { "documents", "users" };
+
+    /** An array with the fields of the test tables (test data). */
+    private static final String[][] TABLE_FIELDS =
+    {
+    { "docid", "docname", "author", "dateOfCreation", "version", "size" },
+    { "userid", "uname", "firstName", "lastName" } };
+
+    /** The parent configuration. */
+    HierarchicalConfiguration parent;
+
+    /** The subnode configuration to be tested. */
+    SubnodeConfiguration config;
+
+    /** Stores the root node of the subnode config. */
+    ConfigurationNode subnode;
+
+    /** Stores a counter for the created nodes. */
+    int nodeCounter;
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        parent = setUpParentConfig();
+        nodeCounter = 0;
+    }
+
+    /**
+     * Tests creation of a subnode config.
+     */
+    public void testInitSubNodeConfig()
+    {
+        setUpSubnodeConfig();
+        assertSame("Wrong root node in subnode", getSubnodeRoot(parent), config
+                .getRoot());
+        assertSame("Wrong parent config", parent, config.getParent());
+    }
+
+    /**
+     * Tests constructing a subnode configuration with a null parent. This
+     * should cause an exception.
+     */
+    public void testInitSubNodeConfigWithNullParent()
+    {
+        try
+        {
+            config = new SubnodeConfiguration(null, getSubnodeRoot(parent));
+            fail("Could set a null parent config!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests constructing a subnode configuration with a null root node. This
+     * should cause an exception.
+     */
+    public void testInitSubNodeConfigWithNullNode()
+    {
+        try
+        {
+            config = new SubnodeConfiguration(parent, null);
+            fail("Could set a null root node!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests if properties of the sub node can be accessed.
+     */
+    public void testGetProperties()
+    {
+        setUpSubnodeConfig();
+        assertEquals("Wrong table name", TABLE_NAMES[0], config
+                .getString("name"));
+        List fields = config.getList("fields.field.name");
+        assertEquals("Wrong number of fields", TABLE_FIELDS[0].length, fields
+                .size());
+        for (int i = 0; i < TABLE_FIELDS[0].length; i++)
+        {
+            assertEquals("Wrong field at position " + i, TABLE_FIELDS[0][i],
+                    fields.get(i));
+        }
+    }
+
+    /**
+     * Tests setting of properties in both the parent and the subnode
+     * configuration and whether the changes are visible to each other.
+     */
+    public void testSetProperty()
+    {
+        setUpSubnodeConfig();
+        config.setProperty(null, "testTable");
+        config.setProperty("name", TABLE_NAMES[0] + "_tested");
+        assertEquals("Root value was not set", "testTable", parent
+                .getString("tables.table(0)"));
+        assertEquals("Table name was not changed", TABLE_NAMES[0] + "_tested",
+                parent.getString("tables.table(0).name"));
+
+        parent.setProperty("tables.table(0).fields.field(1).name", "testField");
+        assertEquals("Field name was not changed", "testField", config
+                .getString("fields.field(1).name"));
+    }
+
+    /**
+     * Tests adding of properties.
+     */
+    public void testAddProperty()
+    {
+        setUpSubnodeConfig();
+        config.addProperty("[@table-type]", "test");
+        assertEquals("parent.createNode() was not called", 1, nodeCounter);
+        assertEquals("Attribute not set", "test", parent
+                .getString("tables.table(0)[@table-type]"));
+
+        parent.addProperty("tables.table(0).fields.field(-1).name", "newField");
+        List fields = config.getList("fields.field.name");
+        assertEquals("New field was not added", TABLE_FIELDS[0].length + 1,
+                fields.size());
+        assertEquals("Wrong last field", "newField", fields
+                .get(fields.size() - 1));
+    }
+
+    /**
+     * Tests listing the defined keys.
+     */
+    public void testGetKeys()
+    {
+        setUpSubnodeConfig();
+        Set keys = new HashSet();
+        CollectionUtils.addAll(keys, config.getKeys());
+        assertEquals("Incorrect number of keys", 2, keys.size());
+        assertTrue("Key 1 not contained", keys.contains("name"));
+        assertTrue("Key 2 not contained", keys.contains("fields.field.name"));
+    }
+
+    /**
+     * Tests setting the exception on missing flag. The subnode config obtains
+     * this flag from its parent.
+     */
+    public void testSetThrowExceptionOnMissing()
+    {
+        setUpSubnodeConfig();
+        parent.setThrowExceptionOnMissing(true);
+        assertTrue("Exception flag not fetchted from parent", config
+                .isThrowExceptionOnMissing());
+        try
+        {
+            config.getString("non existing key");
+            fail("Could fetch non existing key!");
+        }
+        catch (NoSuchElementException nex)
+        {
+            // ok
+        }
+
+        config.setThrowExceptionOnMissing(false);
+        assertFalse("Exception flag not set on parent", parent
+                .isThrowExceptionOnMissing());
+    }
+
+    /**
+     * Tests handling of the delimiter parsing disabled flag. This is shared
+     * with the parent, too.
+     */
+    public void testSetDelimiterParsingDisabled()
+    {
+        setUpSubnodeConfig();
+        parent.setDelimiterParsingDisabled(true);
+        assertTrue("Delimiter parsing flag was not received from parent",
+                config.isDelimiterParsingDisabled());
+        config.addProperty("newProp", "test1,test2,test3");
+        assertEquals("New property was splitted", "test1,test2,test3", parent
+                .getString("tables.table(0).newProp"));
+        config.setDelimiterParsingDisabled(false);
+        assertFalse("Delimiter parsing flag was not set on parent", parent
+                .isDelimiterParsingDisabled());
+    }
+
+    /**
+     * Tests manipulating the list delimiter. This piece of data is used by both
+     * the parent and the subnode.
+     */
+    public void testSetListDelimiter()
+    {
+        setUpSubnodeConfig();
+        parent.setListDelimiter('/');
+        assertEquals("List delimiter not obtained from parent", '/', config
+                .getListDelimiter());
+        config.addProperty("newProp", "test1,test2/test3");
+        assertEquals("List was incorrectly splitted", "test1,test2", parent
+                .getString("tables.table(0).newProp"));
+        config.setListDelimiter(',');
+        assertEquals("List delimiter not set at parent", ',', parent
+                .getListDelimiter());
+    }
+
+    /**
+     * Tests changing the expression engine.
+     */
+    public void testSetExpressionEngine()
+    {
+        setUpSubnodeConfig();
+        parent.setExpressionEngine(new XPathExpressionEngine());
+        assertEquals("Wrong field name", TABLE_FIELDS[0][1], config
+                .getString("fields/field[2]/name"));
+        Set keys = new HashSet();
+        CollectionUtils.addAll(keys, config.getKeys());
+        assertEquals("Wrong number of keys", 2, keys.size());
+        assertTrue("Key 1 not contained", keys.contains("name"));
+        assertTrue("Key 2 not contained", keys.contains("fields/field/name"));
+        config.setExpressionEngine(null);
+        assertSame("Expression engine not set on parent",
+                HierarchicalConfiguration.getDefaultExpressionEngine(), parent
+                        .getExpressionEngine());
+    }
+
+    /**
+     * Initializes the parent configuration. This method creates the typical
+     * structure of tables and fields nodes.
+     *
+     * @return the parent configuration
+     */
+    protected HierarchicalConfiguration setUpParentConfig()
+    {
+        HierarchicalConfiguration conf = new HierarchicalConfiguration()
+        {
+            // Provide a special implementation of createNode() to check
+            // if it is called by the subnode config
+            protected Node createNode(String name)
+            {
+                nodeCounter++;
+                return super.createNode(name);
+            }
+
+        };
+        for (int i = 0; i < TABLE_NAMES.length; i++)
+        {
+            conf.addProperty("tables.table(-1).name", TABLE_NAMES[i]);
+            for (int j = 0; j < TABLE_FIELDS[i].length; j++)
+            {
+                conf.addProperty("tables.table.fields.field(-1).name",
+                        TABLE_FIELDS[i][j]);
+            }
+        }
+        return conf;
+    }
+
+    /**
+     * Returns the root node for the subnode config. This method returns the
+     * first table node.
+     *
+     * @param conf the parent config
+     * @return the root node for the subnode config
+     */
+    protected HierarchicalConfiguration.Node getSubnodeRoot(
+            HierarchicalConfiguration conf)
+    {
+        ConfigurationNode root = conf.getRoot();
+        return (HierarchicalConfiguration.Node) root.getChild(0).getChild(0);
+    }
+
+    /**
+     * Performs a standard initialization of the subnode config to test.
+     */
+    protected void setUpSubnodeConfig()
+    {
+        config = new SubnodeConfiguration(parent, getSubnodeRoot(parent));
+    }
+}

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.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