| /* |
| * 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; |
| } |
| } |
| } |