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
* "defined" 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());
+ }
+}