Author: oheger
Date: Sat Feb  1 20:28:58 2014
New Revision: 1563461

URL: http://svn.apache.org/r1563461
Log:
Initial implementation of InMemoryNodeModel.

Currently, only the functionality related to the NodeHandler interface has been
implemented.

Added:
    
commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java
    
commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModel.java
Modified:
    
commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeHandler.java

Added: 
commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java?rev=1563461&view=auto
==============================================================================
--- 
commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java
 (added)
+++ 
commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java
 Sat Feb  1 20:28:58 2014
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.tree;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * <p>
+ * A specialized node model implementation which operates on
+ * {@link ImmutableNode} structures.
+ * </p>
+ *
+ * @version $Id$
+ * @since 2.0
+ */
+public class InMemoryNodeModel implements NodeHandler<ImmutableNode>
+{
+    /** Stores information about the current nodes structure. */
+    private final AtomicReference<TreeData> structure;
+
+    /**
+     * Creates a new instance of {@code InMemoryNodeModel} which is initialized
+     * with an empty root node.
+     */
+    public InMemoryNodeModel()
+    {
+        this(null);
+    }
+
+    /**
+     * Creates a new instance of {@code InMemoryNodeModel} and initializes it
+     * from the given root node. If the passed in node is <b>null</b>, a new,
+     * empty root node is created.
+     *
+     * @param root the new root node for this model
+     */
+    public InMemoryNodeModel(ImmutableNode root)
+    {
+        structure =
+                new AtomicReference<TreeData>(
+                        createTreeData(initialRootNode(root)));
+    }
+
+    /**
+     * Returns the root node of this model.
+     *
+     * @return the root node
+     */
+    public ImmutableNode getRootNode()
+    {
+        return structure.get().getRoot();
+    }
+
+    /**
+     * Returns a {@code NodeHandler} for dealing with the nodes managed by this
+     * model.
+     *
+     * @return the {@code NodeHandler}
+     */
+    public NodeHandler<ImmutableNode> getNodeHandler()
+    {
+        return this;
+    }
+
+    public String nodeName(ImmutableNode node)
+    {
+        return node.getNodeName();
+    }
+
+    public Object getValue(ImmutableNode node)
+    {
+        return node.getValue();
+    }
+
+    /**
+     * {@inheritDoc} This implementation uses internal mapping information to
+     * determine the parent node of the given node. If the passed in node is 
the
+     * root node of this model, result is <b>null</b>. If the node is not part
+     * of this model, an exception is thrown. Otherwise, the parent node is
+     * returned.
+     *
+     * @throws IllegalArgumentException if the passed in node does not belong 
to
+     *         this model
+     */
+    public ImmutableNode getParent(ImmutableNode node)
+    {
+        return structure.get().getParent(node);
+    }
+
+    public List<ImmutableNode> getChildren(ImmutableNode node)
+    {
+        return node.getChildren();
+    }
+
+    /**
+     * {@inheritDoc} This implementation returns an immutable list with all
+     * child nodes that have the specified name.
+     */
+    public List<ImmutableNode> getChildren(ImmutableNode node, String name)
+    {
+        List<ImmutableNode> result =
+                new ArrayList<ImmutableNode>(node.getChildren().size());
+        for (ImmutableNode c : node.getChildren())
+        {
+            if (StringUtils.equals(name, c.getNodeName()))
+            {
+                result.add(c);
+            }
+        }
+        return Collections.unmodifiableList(result);
+    }
+
+    public ImmutableNode getChild(ImmutableNode node, int index)
+    {
+        return node.getChildren().get(index);
+    }
+
+    public int indexOfChild(ImmutableNode parent, ImmutableNode child)
+    {
+        return parent.getChildren().indexOf(child);
+    }
+
+    public int getChildrenCount(ImmutableNode node, String name)
+    {
+        if (name == null)
+        {
+            return node.getChildren().size();
+        }
+        else
+        {
+            return getChildren(node, name).size();
+        }
+    }
+
+    public Set<String> getAttributes(ImmutableNode node)
+    {
+        return node.getAttributes().keySet();
+    }
+
+    public boolean hasAttributes(ImmutableNode node)
+    {
+        return !node.getAttributes().isEmpty();
+    }
+
+    public Object getAttributeValue(ImmutableNode node, String name)
+    {
+        return node.getAttributes().get(name);
+    }
+
+    /**
+     * {@inheritDoc} This implementation assumes that a node is defined if it
+     * has a value or has children or has attributes.
+     */
+    public boolean isDefined(ImmutableNode node)
+    {
+        return node.getValue() != null || !node.getChildren().isEmpty()
+                || !node.getAttributes().isEmpty();
+    }
+
+    /**
+     * Determines the initial root node of this model. If a root node has been
+     * provided, it is used. Otherwise, an empty dummy root node is created.
+     *
+     * @param providedRoot the passed in root node
+     * @return the root node to be used
+     */
+    private static ImmutableNode initialRootNode(ImmutableNode providedRoot)
+    {
+        return (providedRoot != null) ? providedRoot
+                : new ImmutableNode.Builder().create();
+    }
+
+    /**
+     * Creates a {@code TreeData} object for the specified root node.
+     *
+     * @param root the root node of the current tree
+     * @return the {@code TreeData} describing the current tree
+     */
+    private static TreeData createTreeData(ImmutableNode root)
+    {
+        return new TreeData(root, createParentMapping(root));
+    }
+
+    /**
+     * Creates the mapping to parent nodes for the nodes structured represented
+     * by the passed in root node. Each node is assigned its parent node. Here
+     * an iterative algorithm is used rather than a recursive one to avoid 
stack
+     * overflow for huge structures.
+     *
+     * @param root the root node of the structure
+     * @return the parent node mapping
+     */
+    private static Map<ImmutableNode, ImmutableNode> createParentMapping(
+            ImmutableNode root)
+    {
+        Map<ImmutableNode, ImmutableNode> parents =
+                new HashMap<ImmutableNode, ImmutableNode>();
+        List<ImmutableNode> pendingNodes = new LinkedList<ImmutableNode>();
+        pendingNodes.add(root);
+
+        while (!pendingNodes.isEmpty())
+        {
+            ImmutableNode node = pendingNodes.remove(0);
+            for (ImmutableNode c : node.getChildren())
+            {
+                pendingNodes.add(c);
+                parents.put(c, node);
+            }
+        }
+        return parents;
+    }
+
+    /**
+     * An internally used helper class for storing information about the 
managed
+     * node structure. An instance of this class represents the current tree. 
It
+     * stores the current root node and additional information which is not 
part
+     * of the {@code ImmutableNode} class.
+     */
+    private static class TreeData
+    {
+        /** The root node of the tree. */
+        private final ImmutableNode root;
+
+        /** A map that associates the parent node to each node. */
+        private final Map<ImmutableNode, ImmutableNode> parentMapping;
+
+        /**
+         * Creates a new instance of {@code TreeData} and initializes it with
+         * all data to be stored.
+         *
+         * @param root the root node of the current tree
+         * @param parentMapping the mapping to parent nodes
+         */
+        public TreeData(ImmutableNode root,
+                Map<ImmutableNode, ImmutableNode> parentMapping)
+        {
+            this.root = root;
+            this.parentMapping = parentMapping;
+        }
+
+        /**
+         * Returns the root node.
+         *
+         * @return the current root node
+         */
+        public ImmutableNode getRoot()
+        {
+            return root;
+        }
+
+        /**
+         * Returns the parent node of the specified node. Result is <b>null</b>
+         * for the root node. If the passed in node cannot be resolved, an
+         * exception is thrown.
+         *
+         * @param node the node in question
+         * @return the parent node for this node
+         * @throws IllegalArgumentException if the node cannot be reslved
+         */
+        public ImmutableNode getParent(ImmutableNode node)
+        {
+            if (node == getRoot())
+            {
+                return null;
+            }
+
+            ImmutableNode parent = parentMapping.get(node);
+            if (parent == null)
+            {
+                throw new IllegalArgumentException("Cannot determine parent! "
+                        + node + " is not part of this model.");
+            }
+            return parent;
+        }
+    }
+}

Modified: 
commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeHandler.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeHandler.java?rev=1563461&r1=1563460&r2=1563461&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeHandler.java
 (original)
+++ 
commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeHandler.java
 Sat Feb  1 20:28:58 2014
@@ -17,6 +17,7 @@
 package org.apache.commons.configuration.tree;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * <p>
@@ -28,15 +29,15 @@ import java.util.List;
  * {@code NodeHandler} is used. The handler provides a number of methods for
  * querying the internal state of a node in a read-only way.
  * </p>
- * 
+ *
  * @version $Id$
- * @param T the type of the nodes this handler deals with
+ * @param <T> the type of the nodes this handler deals with
  */
 public interface NodeHandler<T>
 {
     /**
      * Returns the name of the specified node
-     * 
+     *
      * @param node the node
      * @return the name of this node
      */
@@ -44,7 +45,7 @@ public interface NodeHandler<T>
 
     /**
      * Returns the value of the specified node.
-     * 
+     *
      * @param node the node
      * @return the value of this node
      */
@@ -52,7 +53,7 @@ public interface NodeHandler<T>
 
     /**
      * Returns the parent of the specified node.
-     * 
+     *
      * @param node the node
      * @return the parent node
      */
@@ -60,7 +61,7 @@ public interface NodeHandler<T>
 
     /**
      * Returns an unmodifiable list with all children of the specified node.
-     * 
+     *
      * @param node the node
      * @return a list with the child nodes of this node
      */
@@ -69,7 +70,7 @@ public interface NodeHandler<T>
     /**
      * Returns an unmodifiable list of all children of the specified node with
      * the given name.
-     * 
+     *
      * @param node the node
      * @param name the name of the desired child nodes
      * @return a list with all children with the given name
@@ -78,7 +79,7 @@ public interface NodeHandler<T>
 
     /**
      * Returns the child with the given index of the specified node.
-     * 
+     *
      * @param node the node
      * @param index the index (0-based)
      * @return the child with the given index
@@ -86,18 +87,18 @@ public interface NodeHandler<T>
     T getChild(T node, int index);
 
     /**
-     * Returns the index of the given child node relative to its name. This
-     * method can be called when a unique identifier for a specific node is
-     * needed. The node name alone might not be sufficient because there may be
-     * multiple child nodes with the same name. This method returns 0 if the
-     * given node is the first child node with this name, 1 for the second 
child
-     * node and so on. If the node has no parent node or if it is an attribute,
-     * -1 is returned.
-     * 
-     * @param node a child node whose index is to be retrieved
+     * Returns the index of the given child node in the list of children of its
+     * parent. This method is the opposite operation of
+     * {@link #getChild(Object, int)}. This method returns 0 if the given node
+     * is the first child node with this name, 1 for the second child node and
+     * so on. If the node has no parent node or if it is an attribute, -1 is
+     * returned.
+     *
+     * @param parent the parent node
+     * @param child a child node whose index is to be retrieved
      * @return the index of this child node
      */
-    int indexOfChild(T node);
+    int indexOfChild(T parent, T child);
 
     /**
      * Returns the number of children of the specified node with the given 
name.
@@ -108,7 +109,7 @@ public interface NodeHandler<T>
      * a child name is passed in, only the children with this name are taken
      * into account. If the name <b>null</b> is passed, the total number of
      * children must be returned.
-     * 
+     *
      * @param node the node
      * @param name the name of the children in question (can be <b>null</b> for
      *        all children)
@@ -117,17 +118,17 @@ public interface NodeHandler<T>
     int getChildrenCount(T node, String name);
 
     /**
-     * Returns an unmodifiable list with the names of all attributes of the
+     * Returns an unmodifiable set with the names of all attributes of the
      * specified node.
-     * 
+     *
      * @param node the node
-     * @return a list with the names of all attributes of this node
+     * @return a set with the names of all attributes of this node
      */
-    List<String> getAttributes(T node);
+    Set<String> getAttributes(T node);
 
     /**
      * Returns a flag whether the passed in node has any attributes.
-     * 
+     *
      * @param node the node
      * @return a flag whether this node has any attributes
      */
@@ -137,7 +138,7 @@ public interface NodeHandler<T>
      * Returns the value of the specified attribute from the given node. If a
      * concrete {@code NodeHandler} supports attributes with multiple values,
      * result might be a collection.
-     * 
+     *
      * @param node the node
      * @param name the name of the attribute
      * @return the value of this attribute
@@ -148,7 +149,7 @@ public interface NodeHandler<T>
      * Checks whether the specified node is defined. Nodes are
      * &quot;defined&quot; if they contain any data, e.g. a value, or
      * attributes, or defined children.
-     * 
+     *
      * @param node the node to test
      * @return a flag whether the passed in node is defined
      */

Added: 
commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModel.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModel.java?rev=1563461&view=auto
==============================================================================
--- 
commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModel.java
 (added)
+++ 
commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModel.java
 Sat Feb  1 20:28:58 2014
@@ -0,0 +1,571 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test class for {@code InMemoryNodeModel}.
+ *
+ * @version $Id$
+ */
+public class TestInMemoryNodeModel
+{
+    /** A pattern for parsing node keys with optional indices. */
+    private static final Pattern PAT_KEY_WITH_INDEX = Pattern
+            .compile("(\\w+)\\((\\d+)\\)");
+
+    /** The character for splitting node path elements. */
+    private static final String PATH_SEPARATOR = "/";
+
+    /** An array with authors. */
+    private static final String[] AUTHORS = {
+            "Shakespeare", "Homer", "Simmons"
+    };
+
+    /** An array with the works of the test authors. */
+    private static final String[][] WORKS = {
+            {
+                    "Troilus and Cressida", "The Tempest",
+                    "A Midsummer Night?s Dream"
+            }, {
+                "Ilias"
+            }, {
+                    "Ilium", "Hyperion"
+            }
+    };
+
+    /** An array with the personae in the works. */
+    private static final String[][][] PERSONAE = {
+            {
+                    // Works of Shakespeare
+                    {
+                            "Troilus", "Cressidia", "Ajax", "Achilles"
+                    }, {
+                            "Prospero", "Ariel"
+                    }, {
+                            "Oberon", "Titania", "Puck"
+                    }
+            }, {
+                // Works of Homer
+                {
+                        "Achilles", "Agamemnon", "Hektor"
+                }
+            }, {
+                    // Works of Dan Simmons
+                    {
+                            "Hockenberry", "Achilles"
+                    }, {
+                            "Shrike", "Moneta", "Consul", "Weintraub"
+                    }
+            }
+    };
+
+    /** Constant for the author attribute. */
+    private static final String ATTR_AUTHOR = "author";
+
+    /** The root node of the authors tree. */
+    private static ImmutableNode rootAuthorsTree;
+
+    /** The root node of the personae tree. */
+    private static ImmutableNode rootPersonaeTree;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception
+    {
+        rootAuthorsTree = createAuthorsTree();
+        rootPersonaeTree = createPersonaeTree();
+    }
+
+    /**
+     * Creates a tree with a root node whose children are the test authors. 
Each
+     * other has his works as child nodes. Each work has its personae as
+     * children.
+     *
+     * @return the root node of the authors tree
+     */
+    private static ImmutableNode createAuthorsTree()
+    {
+        ImmutableNode.Builder rootBuilder =
+                new ImmutableNode.Builder(AUTHORS.length);
+        for (int author = 0; author < AUTHORS.length; author++)
+        {
+            ImmutableNode.Builder authorBuilder = new ImmutableNode.Builder();
+            authorBuilder.name(AUTHORS[author]);
+            for (int work = 0; work < WORKS[author].length; work++)
+            {
+                ImmutableNode.Builder workBuilder = new 
ImmutableNode.Builder();
+                workBuilder.name(WORKS[author][work]);
+                for (String person : PERSONAE[author][work])
+                {
+                    workBuilder.addChild(new ImmutableNode.Builder().name(
+                            person).create());
+                }
+                authorBuilder.addChild(workBuilder.create());
+            }
+            rootBuilder.addChild(authorBuilder.create());
+        }
+        return rootBuilder.create();
+    }
+
+    /**
+     * Creates a tree with a root node whose children are the test personae.
+     * Each node represents a person and has an attribute pointing to the 
author
+     * who invented this person. There is a single child node for the 
associated
+     * work.
+     *
+     * @return the root node of the personae tree
+     */
+    private static ImmutableNode createPersonaeTree()
+    {
+        ImmutableNode.Builder rootBuilder = new ImmutableNode.Builder();
+        for (int author = 0; author < AUTHORS.length; author++)
+        {
+            for (int work = 0; work < WORKS[author].length; work++)
+            {
+                for (String person : PERSONAE[author][work])
+                {
+                    ImmutableNode workNode =
+                            new ImmutableNode.Builder().name(
+                                    WORKS[author][work]).create();
+                    ImmutableNode personNode =
+                            new ImmutableNode.Builder(1).name(person)
+                                    .addAttribute(ATTR_AUTHOR, AUTHORS[author])
+                                    .addChild(workNode).create();
+                    rootBuilder.addChild(personNode);
+                }
+            }
+        }
+        return rootBuilder.create();
+    }
+
+    /**
+     * Evaluates the given key and finds the corresponding child node of the
+     * specified root. Keys have the form {@code path/to/node}. If there are
+     * multiple sibling nodes with the same name, a numerical index can be
+     * specified in parenthesis.
+     *
+     * @param root the root node
+     * @param key the key to the desired node
+     * @return the node with this key
+     * @throws NoSuchElementException if the key cannot be resolved
+     */
+    private static ImmutableNode nodeForKey(ImmutableNode root, String key)
+    {
+        String[] components = key.split(PATH_SEPARATOR);
+        return findNode(root, components, 0);
+    }
+
+    /**
+     * Helper method for evaluating a single component of a node key.
+     *
+     * @param parent the current parent node
+     * @param components the array with the components of the node key
+     * @param currentIdx the index of the current path component
+     * @return the found target node
+     * @throws NoSuchElementException if the desired node cannot be found
+     */
+    private static ImmutableNode findNode(ImmutableNode parent,
+            String[] components, int currentIdx)
+    {
+        if (currentIdx >= components.length)
+        {
+            return parent;
+        }
+
+        Matcher m = PAT_KEY_WITH_INDEX.matcher(components[currentIdx]);
+        String childName;
+        int childIndex;
+        if (m.matches())
+        {
+            childName = m.group(1);
+            childIndex = Integer.parseInt(m.group(2));
+        }
+        else
+        {
+            childName = components[currentIdx];
+            childIndex = 0;
+        }
+
+        int foundIdx = 0;
+        for (ImmutableNode node : parent.getChildren())
+        {
+            if (childName.equals(node.getNodeName()))
+            {
+                if (foundIdx++ == childIndex)
+                {
+                    return findNode(node, components, currentIdx + 1);
+                }
+            }
+        }
+        throw new NoSuchElementException("Cannot resolve child "
+                + components[currentIdx]);
+    }
+
+    /**
+     * Evaluates the given key and finds the corresponding child node of the
+     * root node of the specified model. This is a convenience method that 
works
+     * like the method with the same name, but obtains the root node from the
+     * given model.
+     *
+     * @param model the node model
+     * @param key the key to the desired node
+     * @return the found target node
+     * @throws NoSuchElementException if the desired node cannot be found
+     */
+    private static ImmutableNode nodeForKey(InMemoryNodeModel model, String 
key)
+    {
+        return nodeForKey(model.getRootNode(), key);
+    }
+
+    /**
+     * Tests whether an undefined default root node is created if none is
+     * specified.
+     */
+    @Test
+    public void testInitDefaultRoot()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel();
+        ImmutableNode root = model.getRootNode();
+        assertNull("Got a name", root.getNodeName());
+        assertNull("Got a value", root.getValue());
+        assertTrue("Got children", root.getChildren().isEmpty());
+        assertTrue("Got attributes", root.getAttributes().isEmpty());
+    }
+
+    /**
+     * Tests whether the correct root node is returned if a tree was passed at
+     * construction time.
+     */
+    @Test
+    public void testGetRootNodeFromConstructor()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        assertSame("Wrong root node", rootAuthorsTree, model.getRootNode());
+    }
+
+    /**
+     * Tests whether the correct parent nodes are returned. All nodes in the
+     * tree are checked.
+     */
+    @Test
+    public void testGetParentNode()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        for (int authorIdx = 0; authorIdx < AUTHORS.length; authorIdx++)
+        {
+            ImmutableNode authorNode =
+                    nodeForKey(model.getRootNode(), AUTHORS[authorIdx]);
+            assertSame("Wrong parent for " + AUTHORS[authorIdx],
+                    model.getRootNode(), model.getParent(authorNode));
+            for (int workIdx = 0; workIdx < WORKS[authorIdx].length; workIdx++)
+            {
+                String workKey =
+                        AUTHORS[authorIdx] + PATH_SEPARATOR
+                                + WORKS[authorIdx][workIdx];
+                ImmutableNode workNode =
+                        nodeForKey(model.getRootNode(), workKey);
+                assertSame("Wrong parent for " + workKey, authorNode,
+                        model.getParent(workNode));
+                for (String person : PERSONAE[authorIdx][workIdx])
+                {
+                    String personKey = workKey + PATH_SEPARATOR + person;
+                    ImmutableNode personNode =
+                            nodeForKey(model.getRootNode(), personKey);
+                    assertSame("Wrong parent for " + personKey, workNode,
+                            model.getParent(personNode));
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests whether the correct parent for the root node is returned.
+     */
+    @Test
+    public void testGetParentForRoot()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        assertNull("Got a parent", model.getParent(rootAuthorsTree));
+    }
+
+    /**
+     * Tries to query the parent node for a node which does not belong to the
+     * managed tree.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetParentInvalidNode()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        model.getParent(new ImmutableNode.Builder().name("unknown").create());
+    }
+
+    /**
+     * Tests whether the name of a node can be queried.
+     */
+    @Test
+    public void testNodeHandlerName()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        ImmutableNode author = nodeForKey(model, AUTHORS[0]);
+        assertEquals("Wrong node name", AUTHORS[0], model.nodeName(author));
+    }
+
+    /**
+     * Tests whether the value of a node can be queried.
+     */
+    @Test
+    public void testNodeHandlerValue()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        ImmutableNode work = nodeForKey(model, "Shakespeare/The Tempest");
+        int year = 1611;
+        work = work.setValue(year);
+        assertEquals("Wrong value", year, model.getValue(work));
+    }
+
+    /**
+     * Tests whether the children of a node can be queried.
+     */
+    @Test
+    public void testNodeHandlerGetChildren()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        ImmutableNode node = nodeForKey(model, AUTHORS[0]);
+        assertSame("Wrong children", node.getChildren(),
+                model.getChildren(node));
+    }
+
+    /**
+     * Tests whether all children of a specific name can be queried.
+     */
+    @Test
+    public void testNodeHandlerGetChildrenByName()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        String name = "Achilles";
+        Set<ImmutableNode> children =
+                new HashSet<ImmutableNode>(model.getChildren(rootPersonaeTree,
+                        name));
+        assertEquals("Wrong number of children", 3, children.size());
+        for (ImmutableNode c : children)
+        {
+            assertEquals("Wrong node name", name, c.getNodeName());
+        }
+    }
+
+    /**
+     * Tests whether the collection of children cannot be modified.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testNodeHandlerGetChildrenByNameImmutable()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        List<ImmutableNode> children =
+                model.getChildren(rootPersonaeTree, "Ajax");
+        children.add(null);
+    }
+
+    /**
+     * Tests whether a child at a given index can be accessed.
+     */
+    @Test
+    public void testNodeHandlerGetChildAtIndex()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        ImmutableNode node = nodeForKey(model, AUTHORS[0]);
+        assertSame("Wrong child", node.getChildren().get(1),
+                model.getChild(node, 1));
+    }
+
+    /**
+     * Tests whether the index of a given child can be queried.
+     */
+    @Test
+    public void testNodeHandlerIndexOfChild()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        String key = "Simmons/Hyperion";
+        ImmutableNode parent = nodeForKey(model, key);
+        ImmutableNode child = nodeForKey(model, key + "/Weintraub");
+        assertEquals("Wrong child index", 3, model.indexOfChild(parent, 
child));
+    }
+
+    /**
+     * Tests the indexOfChild() method for an unknown child node.
+     */
+    @Test
+    public void testNodeHandlerIndexOfUnknownChild()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        ImmutableNode parent = nodeForKey(model, "Homer/Ilias");
+        ImmutableNode child =
+                nodeForKey(model, "Shakespeare/Troilus and Cressida/Achilles");
+        assertEquals("Wrong child index", -1, model.indexOfChild(parent, 
child));
+    }
+
+    /**
+     * Tests whether the number of all children can be queried.
+     */
+    @Test
+    public void testNodeHandlerGetChildrenCountAll()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        ImmutableNode node = nodeForKey(model, AUTHORS[0]);
+        assertEquals("Wrong number of children", WORKS[0].length,
+                model.getChildrenCount(node, null));
+    }
+
+    /**
+     * Tests whether the number of all children with a given name can be
+     * queried.
+     */
+    @Test
+    public void testNodeHandlerGetChildrenCountSpecific()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        assertEquals("Wrong number of children", 3,
+                model.getChildrenCount(rootPersonaeTree, "Achilles"));
+    }
+
+    /**
+     * Tests whether a node's attributes can be queried.
+     */
+    @Test
+    public void testNodeHandlerGetAttributes()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        ImmutableNode node = nodeForKey(model, "Puck");
+        assertEquals("Wrong attributes", node.getAttributes().keySet(),
+                model.getAttributes(node));
+    }
+
+    /**
+     * Tests that the keys of attributes cannot be modified.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testNodeHandlerGetAttributesImmutable()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        ImmutableNode node = nodeForKey(model, "Puck");
+        model.getAttributes(node).add("test");
+    }
+
+    /**
+     * Tests a positive check whether a node has attributes.
+     */
+    @Test
+    public void testNodeHandlerHasAttributesTrue()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        ImmutableNode node = nodeForKey(model, "Puck");
+        assertTrue("No attributes", model.hasAttributes(node));
+    }
+
+    /**
+     * Tests a negative check whether a node has attributes.
+     */
+    @Test
+    public void testNodeHandlerHasAttributesFalse()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        assertFalse("Got attributes", model.hasAttributes(rootPersonaeTree));
+    }
+
+    /**
+     * Tests whether the value of an attribute can be queried.
+     */
+    @Test
+    public void testNodeHandlerGetAttributeValue()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        ImmutableNode node = nodeForKey(model, "Prospero");
+        assertEquals("Wrong value", "Shakespeare",
+                model.getAttributeValue(node, ATTR_AUTHOR));
+    }
+
+    /**
+     * Tests whether a node with children is defined.
+     */
+    @Test
+    public void testNodeHandlerIsDefinedChildren()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootAuthorsTree);
+        ImmutableNode node = nodeForKey(model, AUTHORS[2]);
+        assertTrue("Not defined", model.isDefined(node));
+    }
+
+    /**
+     * Tests whether a node with attributes is defined.
+     */
+    @Test
+    public void testNodeHandlerIsDefinedAttributes()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        ImmutableNode node =
+                new ImmutableNode.Builder().addAttribute(ATTR_AUTHOR,
+                        AUTHORS[0]).create();
+        assertTrue("Not defined", model.isDefined(node));
+    }
+
+    /**
+     * Tests whether a node with a value is defined.
+     */
+    @Test
+    public void testNodeHandlerIsDefinedValue()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        ImmutableNode node = new ImmutableNode.Builder().value(42).create();
+        assertTrue("Not defined", model.isDefined(node));
+    }
+
+    /**
+     * Tests whether an undefined node is correctly detected.
+     */
+    @Test
+    public void testNodeHandlerIsDefinedFalse()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        ImmutableNode node =
+                new ImmutableNode.Builder().name(AUTHORS[1]).create();
+        assertFalse("Defined", model.isDefined(node));
+    }
+
+    /**
+     * Tests whether the correct node handler is returned.
+     */
+    @Test
+    public void testGetNodeHandler()
+    {
+        InMemoryNodeModel model = new InMemoryNodeModel(rootPersonaeTree);
+        assertSame("Wrong node handler", model, model.getNodeHandler());
+    }
+}


Reply via email to