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()