| /* |
| * 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.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| import java.util.stream.StreamSupport; |
| |
| /** |
| * <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> |
| * |
| * @since 2.0 |
| */ |
| public final class ImmutableNode implements Iterable<ImmutableNode> { |
| /** |
| * <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 { |
| /** |
| * 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(final Collection<? extends ImmutableNode> children) { |
| final List<ImmutableNode> result = new ArrayList<>(children.size()); |
| children.forEach(c -> { |
| if (c != null) { |
| result.add(c); |
| } |
| }); |
| return result; |
| } |
| |
| /** 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(final int childCount) { |
| this(); |
| initChildrenCollection(childCount); |
| } |
| |
| /** |
| * 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(final int childCount, final Map<String, Object> dirAttrs) { |
| this(null, dirAttrs); |
| 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(final List<ImmutableNode> dirChildren, final Map<String, Object> dirAttrs) { |
| directChildren = dirChildren; |
| directAttributes = dirAttrs; |
| } |
| |
| /** |
| * 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(final String name, final 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(final Map<String, ?> attrs) { |
| if (attrs != null) { |
| ensureAttributesExist(); |
| attributes.putAll(attrs); |
| } |
| 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(final 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(final Collection<? extends ImmutableNode> children) { |
| if (children != null) { |
| ensureChildrenExist(); |
| this.children.addAll(filterNull(children)); |
| } |
| 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() { |
| final ImmutableNode newNode = new ImmutableNode(this); |
| children = null; |
| attributes = null; |
| return newNode; |
| } |
| |
| /** |
| * 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; |
| } |
| if (attributes != null) { |
| return Collections.unmodifiableMap(attributes); |
| } |
| return Collections.emptyMap(); |
| } |
| |
| /** |
| * 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; |
| } |
| if (children != null) { |
| return Collections.unmodifiableList(children); |
| } |
| return Collections.emptyList(); |
| } |
| |
| /** |
| * Ensures that the map for the attributes exists. It is created on demand. |
| */ |
| private void ensureAttributesExist() { |
| if (attributes == null) { |
| attributes = new HashMap<>(); |
| } |
| } |
| |
| /** |
| * Ensures that the collection for the child nodes exists. It is created on demand. |
| */ |
| private void ensureChildrenExist() { |
| if (children == null) { |
| children = new LinkedList<>(); |
| } |
| } |
| |
| /** |
| * Creates the collection for child nodes based on the expected number of children. |
| * |
| * @param childCount the expected number of new children |
| */ |
| private void initChildrenCollection(final int childCount) { |
| if (childCount > 0) { |
| children = new ArrayList<>(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(final 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(final Object v) { |
| value = v; |
| return this; |
| } |
| } |
| |
| /** |
| * 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(final ImmutableNode child) { |
| if (child == null) { |
| throw new IllegalArgumentException("Child node must not be null!"); |
| } |
| } |
| |
| /** 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(final Builder b) { |
| children = b.createChildren(); |
| attributes = b.createAttributes(); |
| nodeName = b.name; |
| value = b.value; |
| } |
| |
| /** |
| * 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(final ImmutableNode child) { |
| checkChildNode(child); |
| final Builder builder = new Builder(children.size() + 1, attributes); |
| builder.addChildren(children).addChild(child); |
| return createWithBasicProperties(builder); |
| } |
| |
| /** |
| * 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(final 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(final Map<String, Object> newAttrs) { |
| return createWithBasicProperties(new Builder(children, null).addAttributes(newAttrs)); |
| } |
| |
| /** |
| * Gets 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; |
| } |
| |
| /** |
| * Gets 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 list with the children of this node. |
| * |
| * @param name the node name to find |
| * |
| * @return a list with the child nodes |
| */ |
| public List<ImmutableNode> getChildren(final String name) { |
| if (name == null) { |
| return new ArrayList<>(); |
| } |
| return children.stream().filter(in -> name.equals(in.getNodeName())).collect(Collectors.toList()); |
| } |
| |
| /** |
| * Gets the name of this node. |
| * |
| * @return the name of this node |
| */ |
| public String getNodeName() { |
| return nodeName; |
| } |
| |
| /** |
| * Gets the value of this node. |
| * |
| * @return the value of this node |
| */ |
| public Object getValue() { |
| return value; |
| } |
| |
| /** |
| * @return An iterator of {@link #children child nodes.} |
| * @since 2.8.0 |
| */ |
| @Override |
| public Iterator<ImmutableNode> iterator() { |
| return children.iterator(); |
| } |
| |
| /** |
| * 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(final String name) { |
| final Map<String, Object> newAttrs = new HashMap<>(attributes); |
| if (newAttrs.remove(name) != null) { |
| return createWithNewAttributes(newAttrs); |
| } |
| return this; |
| } |
| |
| /** |
| * 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(final ImmutableNode child) { |
| // use same size of children in case the child does not exist |
| final Builder builder = new Builder(children.size(), attributes); |
| boolean foundChild = false; |
| for (final 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(final ImmutableNode oldChild, final ImmutableNode newChild) { |
| checkChildNode(newChild); |
| final Builder builder = new Builder(children.size(), attributes); |
| boolean foundChild = false; |
| for (final 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(final Collection<ImmutableNode> newChildren) { |
| final 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(final String name, final Object value) { |
| final Map<String, Object> newAttrs = new HashMap<>(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(final Map<String, ?> newAttributes) { |
| if (newAttributes == null || newAttributes.isEmpty()) { |
| return this; |
| } |
| |
| final Map<String, Object> newAttrs = new HashMap<>(attributes); |
| newAttrs.putAll(newAttributes); |
| return createWithNewAttributes(newAttrs); |
| } |
| |
| /** |
| * 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(final 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(final Object newValue) { |
| return new Builder(children, attributes).name(nodeName).value(newValue).create(); |
| } |
| |
| /** |
| * Returns a sequential {@code Stream} with this node as its source. |
| * |
| * @return a sequential {@code Stream} over the elements in this node. |
| * @since 2.9.0 |
| */ |
| public Stream<ImmutableNode> stream() { |
| return StreamSupport.stream(spliterator(), false); |
| } |
| |
| @Override |
| public String toString() { |
| return super.toString() + "(" + nodeName + ")"; |
| } |
| } |