You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2009/08/18 22:28:31 UTC

svn commit: r805568 - in /commons/proper/configuration/branches/configuration2_experimental/src: main/java/org/apache/commons/configuration2/flat/FlatRootNode.java test/java/org/apache/commons/configuration2/flat/TestFlatNodes.java

Author: oheger
Date: Tue Aug 18 20:28:31 2009
New Revision: 805568

URL: http://svn.apache.org/viewvc?rev=805568&view=rev
Log:
More efficient implementation of access to flat child nodes.

Modified:
    commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/flat/FlatRootNode.java
    commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/flat/TestFlatNodes.java

Modified: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/flat/FlatRootNode.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/flat/FlatRootNode.java?rev=805568&r1=805567&r2=805568&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/flat/FlatRootNode.java (original)
+++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/flat/FlatRootNode.java Tue Aug 18 20:28:31 2009
@@ -19,7 +19,11 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.ConfigurationRuntimeException;
@@ -34,6 +38,13 @@
  * root node is somewhat special. It does not have a name nor a value. It is the
  * only node in the whole structure that has children.
  * </p>
+ * <p>
+ * The children are stored internally in two different structures: a list for
+ * accessing all children (optionally with indices), and a map for accessing
+ * children by name. Because standard lists and maps from the Java collection
+ * framework are used, this data cannot be updated concurrently. Read-only
+ * access from multiple threads is possible however.
+ * </p>
  *
  * @author <a href="http://commons.apache.org/configuration/team-list.html">Commons
  *         Configuration team</a>
@@ -41,8 +52,14 @@
  */
 class FlatRootNode extends FlatNode
 {
+    /** Constant for an empty default child node manager. */
+    private static final ChildNodeManager DEF_MANAGER = new ChildNodeManager();
+
     /** Stores the child nodes of this root node. */
-    private List<FlatNode> children;
+    private final List<FlatNode> children;
+
+    /** A map for direct access to child nodes by name. */
+    private final Map<String, ChildNodeManager> childrenByName;
 
     /**
      * Creates a new instance of <code>FlatRootNode</code>.
@@ -50,6 +67,7 @@
     public FlatRootNode()
     {
         children = new ArrayList<FlatNode>();
+        childrenByName = new HashMap<String, ChildNodeManager>();
     }
 
     /**
@@ -72,16 +90,17 @@
      *
      * @param name the name of the child node
      * @param hasValue a flag whether the node already has a value; this flag
-     *        impacts the behavior of the <code>setValue()</code> method: if
-     *        it is <b>false</code>, the next <code>setValue()</code> call
-     *        will add a new property to the configuration; otherwise an
-     *        existing property value is overridden
+     *        impacts the behavior of the <code>setValue()</code> method: if it
+     *        is <b>false</code>, the next <code>setValue()</code> call will add
+     *        a new property to the configuration; otherwise an existing
+     *        property value is overridden
      * @return the newly created child node
      */
     public FlatNode addChild(String name, boolean hasValue)
     {
         FlatLeafNode child = new FlatLeafNode(this, name, hasValue);
         children.add(child);
+        fetchChildNodeManager(name, true).addChild(child);
         return child;
     }
 
@@ -117,16 +136,7 @@
     @Override
     public List<FlatNode> getChildren(String name)
     {
-        List<FlatNode> result = new ArrayList<FlatNode>();
-        for (FlatNode c : children)
-        {
-            if (name.equals(c.getName()))
-            {
-                result.add(c);
-            }
-        }
-
-        return result;
+        return fetchChildNodeManager(name, false).getChildren();
     }
 
     /**
@@ -146,16 +156,7 @@
 
         else
         {
-            int count = 0;
-            for (FlatNode n : children)
-            {
-                if (name.equals(n.getName()))
-                {
-                    count++;
-                }
-            }
-
-            return count;
+            return fetchChildNodeManager(name, false).count();
         }
     }
 
@@ -218,27 +219,28 @@
     @Override
     public void removeChild(Configuration config, FlatNode child)
     {
-        for (FlatNode c : children)
+        if (child != null)
         {
-            if (c == child)
+            ChildNodeManager cnm = fetchChildNodeManager(child.getName(), false);
+            int index = cnm.getValueIndex(child);
+            cnm.removeChild(child);
+            children.remove(child);
+
+            if (index != INDEX_UNDEFINED)
             {
-                int index = c.getValueIndex();
-                if (index != INDEX_UNDEFINED)
-                {
-                    changeMultiProperty(config, c, index, null, true);
-                }
-                else
-                {
-                    config.clearProperty(c.getName());
-                }
-                children.remove(c);
-                return;
+                changeMultiProperty(config, child, index, null, true);
+            }
+            else
+            {
+                config.clearProperty(child.getName());
             }
         }
-
-        // child was not found
-        throw new ConfigurationRuntimeException(
-                "Node to remove is no child of this node!");
+        else
+        {
+            // child was not found
+            throw new ConfigurationRuntimeException(
+                    "Cannot remove null child node!");
+        }
     }
 
     /**
@@ -267,31 +269,8 @@
      */
     int getChildValueIndex(FlatNode child)
     {
-        int index = -1;
-        boolean found = false;
-
-        for (FlatNode c : children)
-        {
-            if (c == child)
-            {
-                if (++index > 0)
-                {
-                    return index;
-                }
-                found = true;
-            }
-
-            else if (child.getName().equals(c.getName()))
-            {
-                if (found)
-                {
-                    return index;
-                }
-                index++;
-            }
-        }
-
-        return INDEX_UNDEFINED;
+        return fetchChildNodeManager(child.getName(), false).getValueIndex(
+                child);
     }
 
     /**
@@ -312,6 +291,38 @@
     }
 
     /**
+     * Obtains the {@code ChildNodeManager} for the child nodes with the given
+     * name. This implementation looks up the {@code ChildNodeManager} in an
+     * internal map. If the manager cannot be found, the behavior depends on the
+     * {@code create} parameter: If set to <b>true</b>, a new {@code
+     * ChildNodeManager} instance is created and added to the map. Otherwise an
+     * empty default manager is returned.
+     *
+     * @param name the name of the child node
+     * @param create the create flag
+     * @return the {@code ChildNodeManager} for these child nodes
+     */
+    private ChildNodeManager fetchChildNodeManager(String name, boolean create)
+    {
+        ChildNodeManager cnm = childrenByName.get(name);
+
+        if (cnm == null)
+        {
+            if (create)
+            {
+                cnm = new ChildNodeManager();
+                childrenByName.put(name, cnm);
+            }
+            else
+            {
+                return DEF_MANAGER;
+            }
+        }
+
+        return cnm;
+    }
+
+    /**
      * Helper method for manipulating a property with multiple values. A value
      * at a given index can either be changed or removed. If the index is
      * invalid, no change is performed.
@@ -344,4 +355,151 @@
             }
         }
     }
+
+    /**
+     * A helper class for managing all child nodes of a specific name. This
+     * class provides basic CRUD operations for child nodes. A {@code
+     * FlatRootNode} holds a map that associates the names of child nodes with
+     * instances of this class. An instance can then be used for manipulating
+     * the child nodes with this name.
+     */
+    private static class ChildNodeManager
+    {
+        /** Holds the single child node with this name. */
+        private FlatNode child;
+
+        /** Holds all child nodes with this name. */
+        private List<FlatNode> childNodes;
+
+        /**
+         * Adds the given child node to this manager.
+         *
+         * @param node the node to add
+         */
+        public void addChild(FlatNode node)
+        {
+            if (childNodes != null)
+            {
+                childNodes.add(node);
+            }
+            else if (child != null)
+            {
+                childNodes = new LinkedList<FlatNode>();
+                childNodes.add(child);
+                childNodes.add(node);
+                child = null;
+            }
+            else
+            {
+                child = node;
+            }
+        }
+
+        /**
+         * Removes the given child node. If the return value is <b>false</b>,
+         * there are no more children with this name, and this instance can be
+         * removed.
+         *
+         * @param node the node to remove
+         * @return a flag whether there are remaining nodes with this name
+         * @throws ConfigurationRuntimeException if the child is unknown
+         */
+        public boolean removeChild(FlatNode node)
+        {
+            boolean found = false;
+
+            if (node == child)
+            {
+                child = null;
+                return false;
+            }
+
+            if (childNodes != null)
+            {
+                for (Iterator<FlatNode> it = childNodes.iterator(); it
+                        .hasNext();)
+                {
+                    FlatNode n = it.next();
+                    if (n == node)
+                    {
+                        it.remove();
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (childNodes.size() == 1)
+                {
+                    child = childNodes.get(0);
+                    childNodes = null;
+                }
+            }
+
+            if (!found)
+            {
+                throw new ConfigurationRuntimeException(
+                        "Node to remove is no child of this node!");
+            }
+            return true;
+        }
+
+        /**
+         * Returns the number of child nodes with this name.
+         *
+         * @return the number of child nodes with this name
+         */
+        public int count()
+        {
+            if (childNodes != null)
+            {
+                return childNodes.size();
+            }
+            else
+            {
+                return (child != null) ? 1 : 0;
+            }
+        }
+
+        /**
+         * Returns a list with all child nodes managed by this object.
+         *
+         * @return a list with all child nodes
+         */
+        public List<FlatNode> getChildren()
+        {
+            if (childNodes != null)
+            {
+                return Collections.unmodifiableList(childNodes);
+            }
+            if (child != null)
+            {
+                return Collections.singletonList(child);
+            }
+            return Collections.emptyList();
+        }
+
+        /**
+         * Returns the value index for the specified child node.
+         *
+         * @param node the child node
+         * @return the value index for this node
+         */
+        public int getValueIndex(FlatNode node)
+        {
+            if (childNodes != null)
+            {
+                int index = 0;
+                for (FlatNode n : childNodes)
+                {
+                    if (n == node)
+                    {
+                        return index;
+                    }
+                    index++;
+                }
+            }
+
+            return INDEX_UNDEFINED;
+        }
+    }
 }

Modified: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/flat/TestFlatNodes.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/flat/TestFlatNodes.java?rev=805568&r1=805567&r2=805568&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/flat/TestFlatNodes.java (original)
+++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/flat/TestFlatNodes.java Tue Aug 18 20:28:31 2009
@@ -254,9 +254,9 @@
     }
 
     /**
-     * Tests the modification of a property with multiple values if the configuration
-     * does not return a collection. This should normally not happen. In this
-     * case no modification should be performed.
+     * Tests the modification of a property with multiple values if the
+     * configuration does not return a collection. This should normally not
+     * happen. In this case no modification should be performed.
      */
     public void testSetValueCollectionInvalidValue()
     {
@@ -301,7 +301,9 @@
     public void testRemoveChildCollection()
     {
         BaseConfiguration config = new BaseConfiguration();
-        config.addProperty(NAME, new Object[] { 1, 2, 3 });
+        config.addProperty(NAME, new Object[] {
+                1, 2, 3
+        });
         FlatNode n2 = parent.addChild(NAME, true);
         parent.addChild(NAME, true);
         parent.removeChild(config, n2);
@@ -377,6 +379,59 @@
     }
 
     /**
+     * Tries to remove a null child node. This should cause an exception.
+     */
+    public void testRemoveChildNull()
+    {
+        try
+        {
+            parent.removeChild(new BaseConfiguration(), null);
+            fail("Could remove null child!");
+        }
+        catch (ConfigurationRuntimeException crex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests corner cases when adding and removing child nodes.
+     */
+    public void testAddAndRemoveChild()
+    {
+        final int count = 5;
+        BaseConfiguration config = new BaseConfiguration();
+        List<FlatNode> nodes = new ArrayList<FlatNode>(count);
+        nodes.add(node);
+        for (int i = 0; i < count; i++)
+        {
+            config.addProperty(NAME, i);
+            if (i > 0)
+            {
+                nodes.add(parent.addChild(NAME, true));
+            }
+        }
+        for (int i = 0; i < count; i++)
+        {
+            assertEquals("Wrong value", Integer.valueOf(i), nodes.get(i)
+                    .getValue(config));
+        }
+        for (int j = count - 1; j > 0; j--)
+        {
+            parent.removeChild(config, nodes.get(j));
+            List<FlatNode> remainingChildren = parent.getChildren(NAME);
+            assertEquals("Wrong children", nodes.subList(0, j),
+                    remainingChildren);
+        }
+        assertEquals("Wrong remaining value", Integer.valueOf(0), config
+                .getProperty(NAME));
+        parent.removeChild(config, nodes.get(0));
+        assertFalse("Property still found", config.containsKey(NAME));
+        assertEquals("Wrong number of children", 0, parent
+                .getChildrenCount(NAME));
+    }
+
+    /**
      * Tests adding a child node to a leaf. This should cause an exception.
      */
     public void testAddChildLeaf()