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 2014/01/25 17:32:43 UTC

svn commit: r1561335 - in /commons/proper/configuration/trunk/src: main/java/org/apache/commons/configuration/tree/ImmutableNode.java test/java/org/apache/commons/configuration/tree/TestImmutableNode.java

Author: oheger
Date: Sat Jan 25 16:32:42 2014
New Revision: 1561335

URL: http://svn.apache.org/r1561335
Log:
Added initial implementation of ImmutableNode.

This class provides read-only access to properties needed for a node in a
hierarchical configuration.

Added:
    commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/tree/ImmutableNode.java
    commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/tree/TestImmutableNode.java

Added: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/tree/ImmutableNode.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/tree/ImmutableNode.java?rev=1561335&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/tree/ImmutableNode.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/tree/ImmutableNode.java Sat Jan 25 16:32:42 2014
@@ -0,0 +1,376 @@
+/*
+ * 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.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * An immutable default implementation for configuration nodes.
+ * </p>
+ * <p>
+ * This class is used for an in-memory representation of hierarchical
+ * configuration data. It stores typical information like a node name, a value,
+ * child nodes, or attributes.
+ * </p>
+ * <p>
+ * After their creation, instances cannot be manipulated. There are methods for
+ * updating properties, but these methods return new {@code ImmutableNode}
+ * instances. Instances are created using the nested {@code Builder} class.
+ * </p>
+ *
+ * @version $Id$
+ */
+public class ImmutableNode
+{
+    /** The name of this node. */
+    private final String nodeName;
+
+    /** The value of this node. */
+    private final Object value;
+
+    /** A collection with the child nodes of this node. */
+    private final List<ImmutableNode> children;
+
+    /** A map with the attributes of this node. */
+    private final Map<String, Object> attributes;
+
+    /**
+     * Creates a new instance of {@code ImmutableNode} from the given
+     * {@code Builder} object.
+     *
+     * @param b the {@code Builder}
+     */
+    private ImmutableNode(Builder b)
+    {
+        children = b.createChildren();
+        attributes = b.createAttributes();
+        nodeName = b.name;
+        value = b.value;
+    }
+
+    /**
+     * Returns the name of this node.
+     *
+     * @return the name of this node
+     */
+    public String getNodeName()
+    {
+        return nodeName;
+    }
+
+    /**
+     * Returns the value of this node.
+     *
+     * @return the value of this node
+     */
+    public Object getValue()
+    {
+        return value;
+    }
+
+    /**
+     * Returns a list with the children of this node. This list cannot be
+     * modified.
+     *
+     * @return a list with the child nodes
+     */
+    public List<ImmutableNode> getChildren()
+    {
+        return children;
+    }
+
+    /**
+     * Returns a map with the attributes of this node. This map cannot be
+     * modified.
+     *
+     * @return a map with this node's attributes
+     */
+    public Map<String, Object> getAttributes()
+    {
+        return attributes;
+    }
+
+    /**
+     * <p>
+     * A <em>builder</em> class for creating instances of {@code ImmutableNode}.
+     * </p>
+     * <p>
+     * This class can be used to set all properties of an immutable node
+     * instance. Eventually call the {@code create()} method to obtain the
+     * resulting instance.
+     * </p>
+     * <p>
+     * Implementation note: This class is not thread-safe. It is intended to be
+     * used to define a single node instance only.
+     * </p>
+     */
+    public static final class Builder
+    {
+        /** The direct list of children of the new node. */
+        private final List<ImmutableNode> directChildren;
+
+        /** The direct map of attributes of the new node. */
+        private final Map<String, Object> directAttributes;
+
+        /**
+         * A list for the children of the new node. This list is populated by
+         * the {@code addChild()} method.
+         */
+        private List<ImmutableNode> children;
+
+        /**
+         * A map for storing the attributes of the new node. This map is
+         * populated by {@code addAttribute()}.
+         */
+        private Map<String, Object> attributes;
+
+        /** The name of the node. */
+        private String name;
+
+        /** The value of the node. */
+        private Object value;
+
+        /**
+         * Creates a new instance of {@code Builder} which does not contain any
+         * property definitions yet.
+         */
+        public Builder()
+        {
+            this(null, null);
+        }
+
+        /**
+         * Creates a new instance of {@code Builder} and sets the number of
+         * expected child nodes. Using this constructor helps the class to
+         * create a properly sized list for the child nodes to be added.
+         *
+         * @param childCount the number of child nodes
+         */
+        public Builder(int childCount)
+        {
+            this();
+            children = new ArrayList<ImmutableNode>(childCount);
+        }
+
+        /**
+         * Creates a new instance of {@code Builder} and initializes the
+         * children and attributes of the new node. This constructor is used
+         * internally by the {@code ImmutableNode} class for creating instances
+         * derived from another node. The passed in collections are passed
+         * directly to the newly created instance; thus they already need to be
+         * immutable. (Background is that the creation of intermediate objects
+         * is to be avoided.)
+         *
+         * @param dirChildren the children of the new node
+         * @param dirAttrs the attributes of the new node
+         */
+        private Builder(List<ImmutableNode> dirChildren,
+                Map<String, Object> dirAttrs)
+        {
+            directChildren = dirChildren;
+            directAttributes = dirAttrs;
+        }
+
+        /**
+         * Sets the name of the node to be created.
+         *
+         * @param n the node name
+         * @return a reference to this object for method chaining
+         */
+        public Builder name(String n)
+        {
+            name = n;
+            return this;
+        }
+
+        /**
+         * Sets the value of the node to be created.
+         *
+         * @param v the value
+         * @return a reference to this object for method chaining
+         */
+        public Builder value(Object v)
+        {
+            value = v;
+            return this;
+        }
+
+        /**
+         * Adds a child node to this builder. The passed in node becomes a child
+         * of the newly created node.
+         *
+         * @param c the child node
+         * @return a reference to this object for method chaining
+         */
+        public Builder addChild(ImmutableNode c)
+        {
+            ensureChildrenExist();
+            children.add(c);
+            return this;
+        }
+
+        /**
+         * Adds multiple child nodes to this builder. This method works like
+         * {@link #addChild(ImmutableNode)}, but it allows setting a number of
+         * child nodes at once.
+         *
+         * @param children a collection with the child nodes to be added
+         * @return a reference to this object for method chaining
+         */
+        public Builder addChildren(Collection<ImmutableNode> children)
+        {
+            if (children != null)
+            {
+                ensureChildrenExist();
+                this.children.addAll(children);
+            }
+            return this;
+        }
+
+        /**
+         * Adds an attribute to this builder. The passed in attribute key and
+         * value are stored in an internal map. If there is already an attribute
+         * with this name, it is overridden.
+         *
+         * @param name the attribute name
+         * @param value the attribute value
+         * @return a reference to this object for method chaining
+         */
+        public Builder addAttribute(String name, Object value)
+        {
+            ensureAttributesExist();
+            attributes.put(name, value);
+            return this;
+        }
+
+        /**
+         * Adds all attributes of the given map to this builder. This method
+         * works like {@link #addAttribute(String, Object)}, but it allows
+         * setting multiple attributes at once.
+         *
+         * @param attrs the map with attributes to be added (may be <b>null</b>
+         * @return a reference to this object for method chaining
+         */
+        public Builder addAttributes(Map<String, ?> attrs)
+        {
+            if (attrs != null)
+            {
+                ensureAttributesExist();
+                attributes.putAll(attrs);
+            }
+            return this;
+        }
+
+        /**
+         * Creates a new {@code ImmutableNode} instance based on the properties
+         * set for this builder.
+         *
+         * @return the newly created {@code ImmutableNode}
+         */
+        public ImmutableNode create()
+        {
+            ImmutableNode newNode = new ImmutableNode(this);
+            children = null;
+            attributes = null;
+            return newNode;
+        }
+
+        /**
+         * Creates a list with the children of the newly created node. The list
+         * returned here is always immutable. It depends on the way this builder
+         * was populated.
+         *
+         * @return the list with the children of the new node
+         */
+        List<ImmutableNode> createChildren()
+        {
+            if (directChildren != null)
+            {
+                return directChildren;
+            }
+            else
+            {
+                if (children != null)
+                {
+                    return Collections.unmodifiableList(children);
+                }
+                else
+                {
+                    return Collections.emptyList();
+                }
+            }
+        }
+
+        /**
+         * Creates a map with the attributes of the newly created node. This is
+         * an immutable map. If direct attributes were set, they are returned.
+         * Otherwise an unmodifiable map from the attributes passed to this
+         * builder is constructed.
+         *
+         * @return a map with the attributes for the new node
+         */
+        private Map<String, Object> createAttributes()
+        {
+            if (directAttributes != null)
+            {
+                return directAttributes;
+            }
+            else
+            {
+                if (attributes != null)
+                {
+                    return Collections.unmodifiableMap(attributes);
+                }
+                else
+                {
+                    return Collections.emptyMap();
+                }
+            }
+        }
+
+        /**
+         * Ensures that the collection for the child nodes exists. It is created
+         * on demand.
+         */
+        private void ensureChildrenExist()
+        {
+            if (children == null)
+            {
+                children = new LinkedList<ImmutableNode>();
+            }
+        }
+
+        /**
+         * Ensures that the map for the attributes exists. It is created on
+         * demand.
+         */
+        private void ensureAttributesExist()
+        {
+            if (attributes == null)
+            {
+                attributes = new HashMap<String, Object>();
+            }
+        }
+    }
+}

Added: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/tree/TestImmutableNode.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/tree/TestImmutableNode.java?rev=1561335&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/tree/TestImmutableNode.java (added)
+++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/tree/TestImmutableNode.java Sat Jan 25 16:32:42 2014
@@ -0,0 +1,255 @@
+/*
+ * 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.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Test;
+
+/**
+ * Test class for {@code ImmutableNode}
+ *
+ * @version $Id$
+ */
+public class TestImmutableNode
+{
+    /** Constant for a test node name. */
+    public static final String NAME = "testNode";
+
+    /** Constant for a test node value. */
+    public static final Integer VALUE = 42;
+
+    /**
+     * Sets up a builder with default settings.
+     *
+     * @return the default builder
+     */
+    private static ImmutableNode.Builder setUpBuilder()
+    {
+        ImmutableNode.Builder builder = new ImmutableNode.Builder();
+        builder.name(NAME).value(VALUE);
+        return builder;
+    }
+
+    /**
+     * Tests whether a node with basic properties can be created.
+     */
+    @Test
+    public void testSimpleProperties()
+    {
+        ImmutableNode node = setUpBuilder().create();
+        assertEquals("Wrong node name", NAME, node.getNodeName());
+        assertTrue("Got children", node.getChildren().isEmpty());
+        assertTrue("Got attributes", node.getAttributes().isEmpty());
+    }
+
+    /**
+     * Tests that a node's children cannot be manipulated.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testChildrenImmutable()
+    {
+        ImmutableNode node = setUpBuilder().create();
+        node.getChildren().add(null);
+    }
+
+    /**
+     * Tests that a node's attributes cannot be directly manipulated.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAttributesImmutable()
+    {
+        ImmutableNode node = setUpBuilder().create();
+        node.getAttributes().put("test", VALUE);
+    }
+
+    /**
+     * Checks whether a node has the expected children.
+     *
+     * @param node the node to be checked
+     * @param expChildren the collection with the expected children
+     */
+    private static void checkChildNodes(ImmutableNode node,
+            Collection<ImmutableNode> expChildren)
+    {
+        assertEquals("Wrong number of child nodes", expChildren.size(), node
+                .getChildren().size());
+        assertTrue("Wrong children", node.getChildren()
+                .containsAll(expChildren));
+    }
+
+    /**
+     * Tests whether child nodes can be added.
+     */
+    @Test
+    public void testNodeWithChildren()
+    {
+        Set<ImmutableNode> childNodes = new HashSet<ImmutableNode>();
+        final int childCount = 8;
+        ImmutableNode.Builder builder = new ImmutableNode.Builder(childCount);
+        for (int i = 0; i < childCount; i++)
+        {
+            ImmutableNode.Builder childBuilder = new ImmutableNode.Builder();
+            ImmutableNode child = childBuilder.name(NAME + i).value(i).create();
+            builder.addChild(child);
+            childNodes.add(child);
+        }
+        ImmutableNode node = builder.name(NAME).create();
+        checkChildNodes(node, childNodes);
+    }
+
+    /**
+     * Tests whether multiple child nodes can be added to a builder.
+     */
+    @Test
+    public void testNodeWithAddMultipleChildren()
+    {
+        final int childCount = 4;
+        List<ImmutableNode> childNodes =
+                new ArrayList<ImmutableNode>(childCount);
+        for (int i = 0; i < childCount; i++)
+        {
+            ImmutableNode.Builder childBuilder = new ImmutableNode.Builder();
+            ImmutableNode child = childBuilder.name(NAME + i).value(i).create();
+            childNodes.add(child);
+        }
+        ImmutableNode.Builder builder = setUpBuilder();
+        ImmutableNode node = builder.addChildren(childNodes).create();
+        checkChildNodes(node, childNodes);
+    }
+
+    /**
+     * Tests that the list of children cannot be changed by a later manipulation
+     * of the builder.
+     */
+    @Test
+    public void testNodeWithChildrenManipulateLater()
+    {
+        ImmutableNode.Builder builder = setUpBuilder();
+        ImmutableNode child =
+                new ImmutableNode.Builder().name("Child").create();
+        ImmutableNode node = builder.addChild(child).create();
+        builder.addChild(new ImmutableNode.Builder().name("AnotherChild")
+                .create());
+        checkChildNodes(node, Collections.singleton(child));
+    }
+
+    /**
+     * Tests whether addChildren() can deal with null input.
+     */
+    @Test
+    public void testAddChildrenNull()
+    {
+        ImmutableNode.Builder builder = setUpBuilder();
+        builder.addChildren(null);
+        ImmutableNode node = builder.create();
+        assertTrue("Got children", node.getChildren().isEmpty());
+    }
+
+    /**
+     * Tests whether a node with attributes can be created.
+     */
+    @Test
+    public void testNodeWithAttributes()
+    {
+        ImmutableNode.Builder builder = setUpBuilder();
+        final int attrCount = 4;
+        Map<String, Object> attrs = new HashMap<String, Object>();
+        for (int i = 0; i < attrCount; i++)
+        {
+            String attrName = NAME + i;
+            attrs.put(attrName, i);
+            builder.addAttribute(attrName, i);
+        }
+        ImmutableNode node = builder.create();
+        checkAttributes(node, attrs);
+    }
+
+    /**
+     * Checks whether a node has the expected attributes.
+     *
+     * @param node the node to be checked
+     * @param expAttrs the expected attributes
+     */
+    private static void checkAttributes(ImmutableNode node,
+            Map<String, Object> expAttrs)
+    {
+        assertEquals("Wrong number of attributes", expAttrs.size(), node
+                .getAttributes().size());
+        for (Map.Entry<String, Object> e : expAttrs.entrySet())
+        {
+            assertEquals("Wrong value for " + e.getKey(), e.getValue(), node
+                    .getAttributes().get(e.getKey()));
+        }
+    }
+
+    /**
+     * Tests that the map of attributes cannot be changed by a later
+     * manipulation of the builder.
+     */
+    @Test
+    public void testNodeWithAttributesManipulateLater()
+    {
+        ImmutableNode.Builder builder = setUpBuilder();
+        builder.addAttribute("attr", "a1");
+        ImmutableNode node = builder.create();
+        builder.addAttribute("attr2", "a2");
+        assertEquals("Wrong number of attributes", 1, node.getAttributes()
+                .size());
+        assertEquals("Wrong attribute", "a1", node.getAttributes().get("attr"));
+    }
+
+    /**
+     * Tests whether multiple attributes can be added in a single operation.
+     */
+    @Test
+    public void testNodeWithMultipleAttributes()
+    {
+        ImmutableNode.Builder builder = setUpBuilder();
+        final int attrCount = 4;
+        Map<String, Object> attrs = new HashMap<String, Object>();
+        for (int i = 0; i < attrCount; i++)
+        {
+            String attrName = NAME + i;
+            attrs.put(attrName, i);
+        }
+        ImmutableNode node = builder.addAttributes(attrs).create();
+        checkAttributes(node, attrs);
+    }
+
+    /**
+     * Tests whether addAttributes() handles null input.
+     */
+    @Test
+    public void testAddAttributesNull()
+    {
+        ImmutableNode.Builder builder = setUpBuilder();
+        builder.addAttributes(null);
+        ImmutableNode node = builder.create();
+        assertTrue("Got attributes", node.getAttributes().isEmpty());
+    }
+}