blob: 1d0a6bd82e71b57f19b7e38ab5cfdd050c2d71e4 [file] [log] [blame]
/*
* 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.configuration2.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$
* @since 2.0
*/
public final 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;
}
/**
* Creates a new {@code ImmutableNode} instance which is a copy of this
* object with the name changed to the passed in value.
*
* @param name the name of the newly created node
* @return the new node with the changed name
*/
public ImmutableNode setName(String name)
{
return new Builder(children, attributes).name(name).value(value)
.create();
}
/**
* Creates a new {@code ImmutableNode} instance which is a copy of this
* object with the value changed to the passed in value.
*
* @param newValue the value of the newly created node
* @return the new node with the changed value
*/
public ImmutableNode setValue(Object newValue)
{
return new Builder(children, attributes).name(nodeName).value(newValue)
.create();
}
/**
* Creates a new {@code ImmutableNode} instance which is a copy of this
* object, but has the given child node added.
*
* @param child the child node to be added (must not be <b>null</b>)
* @return the new node with the child node added
* @throws IllegalArgumentException if the child node is <b>null</b>
*/
public ImmutableNode addChild(ImmutableNode child)
{
checkChildNode(child);
Builder builder = new Builder(children.size() + 1, attributes);
builder.addChildren(children).addChild(child);
return createWithBasicProperties(builder);
}
/**
* Returns a new {@code ImmutableNode} instance which is a copy of this
* object, but with the given child node removed. If the child node does not
* belong to this node, the same node instance is returned.
*
* @param child the child node to be removed
* @return the new node with the child node removed
*/
public ImmutableNode removeChild(ImmutableNode child)
{
// use same size of children in case the child does not exist
Builder builder = new Builder(children.size(), attributes);
boolean foundChild = false;
for (ImmutableNode c : children)
{
if (c == child)
{
foundChild = true;
}
else
{
builder.addChild(c);
}
}
return foundChild ? createWithBasicProperties(builder) : this;
}
/**
* Returns a new {@code ImmutableNode} instance which is a copy of this
* object, but with the given child replaced by the new one. If the child to
* be replaced cannot be found, the same node instance is returned.
*
* @param oldChild the child node to be replaced
* @param newChild the replacing child node (must not be <b>null</b>)
* @return the new node with the child replaced
* @throws IllegalArgumentException if the new child node is <b>null</b>
*/
public ImmutableNode replaceChild(ImmutableNode oldChild,
ImmutableNode newChild)
{
checkChildNode(newChild);
Builder builder = new Builder(children.size(), attributes);
boolean foundChild = false;
for (ImmutableNode c : children)
{
if (c == oldChild)
{
builder.addChild(newChild);
foundChild = true;
}
else
{
builder.addChild(c);
}
}
return foundChild ? createWithBasicProperties(builder) : this;
}
/**
* Returns a new {@code ImmutableNode} instance which is a copy of this
* object, but with the children replaced by the ones in the passed in
* collection. With this method all children can be replaced in a single
* step. For the collection the same rules apply as for
* {@link Builder#addChildren(Collection)}.
*
* @param newChildren the collection with the new children (may be
* <b>null</b>)
* @return the new node with replaced children
*/
public ImmutableNode replaceChildren(Collection<ImmutableNode> newChildren)
{
Builder builder = new Builder(null, attributes);
builder.addChildren(newChildren);
return createWithBasicProperties(builder);
}
/**
* Returns a new {@code ImmutableNode} instance which is a copy of this
* object, but with the specified attribute set to the given value. If an
* attribute with this name does not exist, it is created now. Otherwise,
* the new value overrides the old one.
*
* @param name the name of the attribute
* @param value the attribute value
* @return the new node with this attribute
*/
public ImmutableNode setAttribute(String name, Object value)
{
Map<String, Object> newAttrs = new HashMap<String, Object>(attributes);
newAttrs.put(name, value);
return createWithNewAttributes(newAttrs);
}
/**
* Returns a new {@code ImmutableNode} instance which is a copy of this
* object, but with all attributes added defined by the given map. This
* method is analogous to {@link #setAttribute(String, Object)}, but all
* attributes in the given map are added. If the map is <b>null</b> or
* empty, this method has no effect.
*
* @param newAttributes the map with attributes to be added
* @return the new node with these attributes
*/
public ImmutableNode setAttributes(Map<String, ?> newAttributes)
{
if (newAttributes == null || newAttributes.isEmpty())
{
return this;
}
Map<String, Object> newAttrs = new HashMap<String, Object>(attributes);
newAttrs.putAll(newAttributes);
return createWithNewAttributes(newAttrs);
}
/**
* Returns a new {@code ImmutableNode} instance which is a copy of this
* object, but with the specified attribute removed. If there is no
* attribute with the given name, the same node instance is returned.
*
* @param name the name of the attribute
* @return the new node without this attribute
*/
public ImmutableNode removeAttribute(String name)
{
Map<String, Object> newAttrs = new HashMap<String, Object>(attributes);
if (newAttrs.remove(name) != null)
{
return createWithNewAttributes(newAttrs);
}
return this;
}
/**
* Initializes the given builder with basic properties (node name and value)
* and returns the newly created node. This is a helper method for updating
* a node when only children or attributes are affected.
*
* @param builder the already prepared builder
* @return the newly created node
*/
private ImmutableNode createWithBasicProperties(Builder builder)
{
return builder.name(nodeName).value(value).create();
}
/**
* Creates a new {@code ImmutableNode} instance with the same properties as
* this object, but with the given new attributes.
*
* @param newAttrs the new attributes
* @return the new node instance
*/
private ImmutableNode createWithNewAttributes(Map<String, Object> newAttrs)
{
return createWithBasicProperties(new Builder(children, null)
.addAttributes(newAttrs));
}
/**
* Checks whether the given child node is not null. This check is done at
* multiple places to ensure that newly added child nodes are always
* defined.
*
* @param child the child node to be checked
* @throws IllegalArgumentException if the child node is <b>null</b>
*/
private static void checkChildNode(ImmutableNode child)
{
if (child == null)
{
throw new IllegalArgumentException("Child node must not be null!");
}
}
/**
* <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();
initChildrenCollection(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;
}
/**
* Creates a new instance of {@code Builder} and initializes the
* attributes of the new node and prepares the collection for the
* children. This constructor is used internally by methods of
* {@code ImmutableNode} which update the node and change the children.
* The new number of child nodes can be passed so that the collection
* for the new children can be created with an appropriate size.
*
* @param childCount the expected number of new children
* @param dirAttrs the attributes of the new node
*/
private Builder(int childCount, Map<String, Object> dirAttrs)
{
this(null, dirAttrs);
initChildrenCollection(childCount);
}
/**
* 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. If it is <b>null</b>, it is ignored.
*
* @param c the child node (must not be <b>null</b>)
* @return a reference to this object for method chaining
*/
public Builder addChild(ImmutableNode c)
{
if (c != null)
{
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<? extends ImmutableNode> children)
{
if (children != null)
{
ensureChildrenExist();
this.children.addAll(filterNull(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>();
}
}
/**
* Creates the collection for child nodes based on the expected number
* of children.
*
* @param childCount the expected number of new children
*/
private void initChildrenCollection(int childCount)
{
if (childCount > 0)
{
children = new ArrayList<ImmutableNode>(childCount);
}
}
/**
* Filters null entries from the passed in collection with child nodes.
*
*
* @param children the collection to be filtered
* @return the collection with null entries removed
*/
private static Collection<? extends ImmutableNode> filterNull(
Collection<? extends ImmutableNode> children)
{
List<ImmutableNode> result =
new ArrayList<ImmutableNode>(children.size());
for (ImmutableNode c : children)
{
if (c != null)
{
result.add(c);
}
}
return result;
}
}
}