ImmutableNode.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.tree;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.Iterator;
  23. import java.util.LinkedList;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.stream.Collectors;
  27. import java.util.stream.Stream;
  28. import java.util.stream.StreamSupport;

  29. /**
  30.  * <p>
  31.  * An immutable default implementation for configuration nodes.
  32.  * </p>
  33.  * <p>
  34.  * This class is used for an in-memory representation of hierarchical configuration data. It stores typical information
  35.  * like a node name, a value, child nodes, or attributes.
  36.  * </p>
  37.  * <p>
  38.  * After their creation, instances cannot be manipulated. There are methods for updating properties, but these methods
  39.  * return new {@code ImmutableNode} instances. Instances are created using the nested {@code Builder} class.
  40.  * </p>
  41.  *
  42.  * @since 2.0
  43.  */
  44. public final class ImmutableNode implements Iterable<ImmutableNode> {
  45.     /**
  46.      * <p>
  47.      * A <em>builder</em> class for creating instances of {@code ImmutableNode}.
  48.      * </p>
  49.      * <p>
  50.      * This class can be used to set all properties of an immutable node instance. Eventually call the {@code create()}
  51.      * method to obtain the resulting instance.
  52.      * </p>
  53.      * <p>
  54.      * Implementation note: This class is not thread-safe. It is intended to be used to define a single node instance only.
  55.      * </p>
  56.      */
  57.     public static final class Builder {
  58.         /**
  59.          * Filters null entries from the passed in collection with child nodes.
  60.          *
  61.          *
  62.          * @param children the collection to be filtered
  63.          * @return the collection with null entries removed
  64.          */
  65.         private static Collection<? extends ImmutableNode> filterNull(final Collection<? extends ImmutableNode> children) {
  66.             final List<ImmutableNode> result = new ArrayList<>(children.size());
  67.             children.forEach(c -> {
  68.                 if (c != null) {
  69.                     result.add(c);
  70.                 }
  71.             });
  72.             return result;
  73.         }

  74.         /** The direct list of children of the new node. */
  75.         private final List<ImmutableNode> directChildren;

  76.         /** The direct map of attributes of the new node. */
  77.         private final Map<String, Object> directAttributes;

  78.         /**
  79.          * A list for the children of the new node. This list is populated by the {@code addChild()} method.
  80.          */
  81.         private List<ImmutableNode> children;

  82.         /**
  83.          * A map for storing the attributes of the new node. This map is populated by {@code addAttribute()}.
  84.          */
  85.         private Map<String, Object> attributes;

  86.         /** The name of the node. */
  87.         private String name;

  88.         /** The value of the node. */
  89.         private Object value;

  90.         /**
  91.          * Creates a new instance of {@code Builder} which does not contain any property definitions yet.
  92.          */
  93.         public Builder() {
  94.             this(null, null);
  95.         }

  96.         /**
  97.          * Creates a new instance of {@code Builder} and sets the number of expected child nodes. Using this constructor helps
  98.          * the class to create a properly sized list for the child nodes to be added.
  99.          *
  100.          * @param childCount the number of child nodes
  101.          */
  102.         public Builder(final int childCount) {
  103.             this();
  104.             initChildrenCollection(childCount);
  105.         }

  106.         /**
  107.          * Creates a new instance of {@code Builder} and initializes the attributes of the new node and prepares the collection
  108.          * for the children. This constructor is used internally by methods of {@code ImmutableNode} which update the node and
  109.          * change the children. The new number of child nodes can be passed so that the collection for the new children can be
  110.          * created with an appropriate size.
  111.          *
  112.          * @param childCount the expected number of new children
  113.          * @param dirAttrs the attributes of the new node
  114.          */
  115.         private Builder(final int childCount, final Map<String, Object> dirAttrs) {
  116.             this(null, dirAttrs);
  117.             initChildrenCollection(childCount);
  118.         }

  119.         /**
  120.          * Creates a new instance of {@code Builder} and initializes the children and attributes of the new node. This
  121.          * constructor is used internally by the {@code ImmutableNode} class for creating instances derived from another node.
  122.          * The passed in collections are passed directly to the newly created instance; thus they already need to be immutable.
  123.          * (Background is that the creation of intermediate objects is to be avoided.)
  124.          *
  125.          * @param dirChildren the children of the new node
  126.          * @param dirAttrs the attributes of the new node
  127.          */
  128.         private Builder(final List<ImmutableNode> dirChildren, final Map<String, Object> dirAttrs) {
  129.             directChildren = dirChildren;
  130.             directAttributes = dirAttrs;
  131.         }

  132.         /**
  133.          * Adds an attribute to this builder. The passed in attribute key and value are stored in an internal map. If there is
  134.          * already an attribute with this name, it is overridden.
  135.          *
  136.          * @param name the attribute name
  137.          * @param value the attribute value
  138.          * @return a reference to this object for method chaining
  139.          */
  140.         public Builder addAttribute(final String name, final Object value) {
  141.             ensureAttributesExist();
  142.             attributes.put(name, value);
  143.             return this;
  144.         }

  145.         /**
  146.          * Adds all attributes of the given map to this builder. This method works like {@link #addAttribute(String, Object)},
  147.          * but it allows setting multiple attributes at once.
  148.          *
  149.          * @param attrs the map with attributes to be added (may be <strong>null</strong>
  150.          * @return a reference to this object for method chaining
  151.          */
  152.         public Builder addAttributes(final Map<String, ?> attrs) {
  153.             if (attrs != null) {
  154.                 ensureAttributesExist();
  155.                 attributes.putAll(attrs);
  156.             }
  157.             return this;
  158.         }

  159.         /**
  160.          * Adds a child node to this builder. The passed in node becomes a child of the newly created node. If it is
  161.          * <strong>null</strong>, it is ignored.
  162.          *
  163.          * @param c the child node (must not be <strong>null</strong>)
  164.          * @return a reference to this object for method chaining
  165.          */
  166.         public Builder addChild(final ImmutableNode c) {
  167.             if (c != null) {
  168.                 ensureChildrenExist();
  169.                 children.add(c);
  170.             }
  171.             return this;
  172.         }

  173.         /**
  174.          * Adds multiple child nodes to this builder. This method works like {@link #addChild(ImmutableNode)}, but it allows
  175.          * setting a number of child nodes at once.
  176.          *
  177.          *
  178.          * @param children a collection with the child nodes to be added
  179.          * @return a reference to this object for method chaining
  180.          */
  181.         public Builder addChildren(final Collection<? extends ImmutableNode> children) {
  182.             if (children != null) {
  183.                 ensureChildrenExist();
  184.                 this.children.addAll(filterNull(children));
  185.             }
  186.             return this;
  187.         }

  188.         /**
  189.          * Creates a new {@code ImmutableNode} instance based on the properties set for this builder.
  190.          *
  191.          * @return the newly created {@code ImmutableNode}
  192.          */
  193.         public ImmutableNode create() {
  194.             final ImmutableNode newNode = new ImmutableNode(this);
  195.             children = null;
  196.             attributes = null;
  197.             return newNode;
  198.         }

  199.         /**
  200.          * Creates a map with the attributes of the newly created node. This is an immutable map. If direct attributes were set,
  201.          * they are returned. Otherwise an unmodifiable map from the attributes passed to this builder is constructed.
  202.          *
  203.          * @return a map with the attributes for the new node
  204.          */
  205.         private Map<String, Object> createAttributes() {
  206.             if (directAttributes != null) {
  207.                 return directAttributes;
  208.             }
  209.             if (attributes != null) {
  210.                 return Collections.unmodifiableMap(attributes);
  211.             }
  212.             return Collections.emptyMap();
  213.         }

  214.         /**
  215.          * Creates a list with the children of the newly created node. The list returned here is always immutable. It depends on
  216.          * the way this builder was populated.
  217.          *
  218.          * @return the list with the children of the new node
  219.          */
  220.         List<ImmutableNode> createChildren() {
  221.             if (directChildren != null) {
  222.                 return directChildren;
  223.             }
  224.             if (children != null) {
  225.                 return Collections.unmodifiableList(children);
  226.             }
  227.             return Collections.emptyList();
  228.         }

  229.         /**
  230.          * Ensures that the map for the attributes exists. It is created on demand.
  231.          */
  232.         private void ensureAttributesExist() {
  233.             if (attributes == null) {
  234.                 attributes = new HashMap<>();
  235.             }
  236.         }

  237.         /**
  238.          * Ensures that the collection for the child nodes exists. It is created on demand.
  239.          */
  240.         private void ensureChildrenExist() {
  241.             if (children == null) {
  242.                 children = new LinkedList<>();
  243.             }
  244.         }

  245.         /**
  246.          * Creates the collection for child nodes based on the expected number of children.
  247.          *
  248.          * @param childCount the expected number of new children
  249.          */
  250.         private void initChildrenCollection(final int childCount) {
  251.             if (childCount > 0) {
  252.                 children = new ArrayList<>(childCount);
  253.             }
  254.         }

  255.         /**
  256.          * Sets the name of the node to be created.
  257.          *
  258.          * @param n the node name
  259.          * @return a reference to this object for method chaining
  260.          */
  261.         public Builder name(final String n) {
  262.             name = n;
  263.             return this;
  264.         }

  265.         /**
  266.          * Sets the value of the node to be created.
  267.          *
  268.          * @param v the value
  269.          * @return a reference to this object for method chaining
  270.          */
  271.         public Builder value(final Object v) {
  272.             value = v;
  273.             return this;
  274.         }
  275.     }

  276.     /**
  277.      * Checks whether the given child node is not null. This check is done at multiple places to ensure that newly added
  278.      * child nodes are always defined.
  279.      *
  280.      * @param child the child node to be checked
  281.      * @throws IllegalArgumentException if the child node is <strong>null</strong>
  282.      */
  283.     private static void checkChildNode(final ImmutableNode child) {
  284.         if (child == null) {
  285.             throw new IllegalArgumentException("Child node must not be null!");
  286.         }
  287.     }

  288.     /** The name of this node. */
  289.     private final String nodeName;

  290.     /** The value of this node. */
  291.     private final Object value;

  292.     /** A collection with the child nodes of this node. */
  293.     private final List<ImmutableNode> children;

  294.     /** A map with the attributes of this node. */
  295.     private final Map<String, Object> attributes;

  296.     /**
  297.      * Creates a new instance of {@code ImmutableNode} from the given {@code Builder} object.
  298.      *
  299.      * @param b the {@code Builder}
  300.      */
  301.     private ImmutableNode(final Builder b) {
  302.         children = b.createChildren();
  303.         attributes = b.createAttributes();
  304.         nodeName = b.name;
  305.         value = b.value;
  306.     }

  307.     /**
  308.      * Creates a new {@code ImmutableNode} instance which is a copy of this object, but has the given child node added.
  309.      *
  310.      * @param child the child node to be added (must not be <strong>null</strong>)
  311.      * @return the new node with the child node added
  312.      * @throws IllegalArgumentException if the child node is <strong>null</strong>
  313.      */
  314.     public ImmutableNode addChild(final ImmutableNode child) {
  315.         checkChildNode(child);
  316.         final Builder builder = new Builder(children.size() + 1, attributes);
  317.         builder.addChildren(children).addChild(child);
  318.         return createWithBasicProperties(builder);
  319.     }

  320.     /**
  321.      * Initializes the given builder with basic properties (node name and value) and returns the newly created node. This is
  322.      * a helper method for updating a node when only children or attributes are affected.
  323.      *
  324.      * @param builder the already prepared builder
  325.      * @return the newly created node
  326.      */
  327.     private ImmutableNode createWithBasicProperties(final Builder builder) {
  328.         return builder.name(nodeName).value(value).create();
  329.     }

  330.     /**
  331.      * Creates a new {@code ImmutableNode} instance with the same properties as this object, but with the given new
  332.      * attributes.
  333.      *
  334.      * @param newAttrs the new attributes
  335.      * @return the new node instance
  336.      */
  337.     private ImmutableNode createWithNewAttributes(final Map<String, Object> newAttrs) {
  338.         return createWithBasicProperties(new Builder(children, null).addAttributes(newAttrs));
  339.     }

  340.     /**
  341.      * Gets a map with the attributes of this node. This map cannot be modified.
  342.      *
  343.      * @return a map with this node's attributes
  344.      */
  345.     public Map<String, Object> getAttributes() {
  346.         return attributes;
  347.     }

  348.     /**
  349.      * Gets a list with the children of this node. This list cannot be modified.
  350.      *
  351.      * @return a list with the child nodes
  352.      */
  353.     public List<ImmutableNode> getChildren() {
  354.         return children;
  355.     }

  356.     /**
  357.      * Returns a list with the children of this node.
  358.      *
  359.      * @param name the node name to find
  360.      * @return a list with the child nodes
  361.      */
  362.     public List<ImmutableNode> getChildren(final String name) {
  363.         if (name == null) {
  364.             return new ArrayList<>();
  365.         }
  366.         return children.stream().filter(in -> name.equals(in.getNodeName())).collect(Collectors.toList());
  367.     }

  368.     /**
  369.      * Gets the name of this node.
  370.      *
  371.      * @return the name of this node
  372.      */
  373.     public String getNodeName() {
  374.         return nodeName;
  375.     }

  376.     /**
  377.      * Gets the value of this node.
  378.      *
  379.      * @return the value of this node
  380.      */
  381.     public Object getValue() {
  382.         return value;
  383.     }

  384.     /**
  385.      * @return An iterator of {@link #children child nodes.}
  386.      * @since 2.8.0
  387.      */
  388.     @Override
  389.     public Iterator<ImmutableNode> iterator() {
  390.         return children.iterator();
  391.     }

  392.     /**
  393.      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the specified attribute
  394.      * removed. If there is no attribute with the given name, the same node instance is returned.
  395.      *
  396.      * @param name the name of the attribute
  397.      * @return the new node without this attribute
  398.      */
  399.     public ImmutableNode removeAttribute(final String name) {
  400.         final Map<String, Object> newAttrs = new HashMap<>(attributes);
  401.         if (newAttrs.remove(name) != null) {
  402.             return createWithNewAttributes(newAttrs);
  403.         }
  404.         return this;
  405.     }

  406.     /**
  407.      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the given child node removed.
  408.      * If the child node does not belong to this node, the same node instance is returned.
  409.      *
  410.      * @param child the child node to be removed
  411.      * @return the new node with the child node removed
  412.      */
  413.     public ImmutableNode removeChild(final ImmutableNode child) {
  414.         // use same size of children in case the child does not exist
  415.         final Builder builder = new Builder(children.size(), attributes);
  416.         boolean foundChild = false;
  417.         for (final ImmutableNode c : children) {
  418.             if (c == child) {
  419.                 foundChild = true;
  420.             } else {
  421.                 builder.addChild(c);
  422.             }
  423.         }

  424.         return foundChild ? createWithBasicProperties(builder) : this;
  425.     }

  426.     /**
  427.      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the given child replaced by the
  428.      * new one. If the child to be replaced cannot be found, the same node instance is returned.
  429.      *
  430.      * @param oldChild the child node to be replaced
  431.      * @param newChild the replacing child node (must not be <strong>null</strong>)
  432.      * @return the new node with the child replaced
  433.      * @throws IllegalArgumentException if the new child node is <strong>null</strong>
  434.      */
  435.     public ImmutableNode replaceChild(final ImmutableNode oldChild, final ImmutableNode newChild) {
  436.         checkChildNode(newChild);
  437.         final Builder builder = new Builder(children.size(), attributes);
  438.         boolean foundChild = false;
  439.         for (final ImmutableNode c : children) {
  440.             if (c == oldChild) {
  441.                 builder.addChild(newChild);
  442.                 foundChild = true;
  443.             } else {
  444.                 builder.addChild(c);
  445.             }
  446.         }

  447.         return foundChild ? createWithBasicProperties(builder) : this;
  448.     }

  449.     /**
  450.      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the children replaced by the
  451.      * ones in the passed in collection. With this method all children can be replaced in a single step. For the collection
  452.      * the same rules apply as for {@link Builder#addChildren(Collection)}.
  453.      *
  454.      * @param newChildren the collection with the new children (may be <strong>null</strong>)
  455.      * @return the new node with replaced children
  456.      */
  457.     public ImmutableNode replaceChildren(final Collection<ImmutableNode> newChildren) {
  458.         final Builder builder = new Builder(null, attributes);
  459.         builder.addChildren(newChildren);
  460.         return createWithBasicProperties(builder);
  461.     }

  462.     /**
  463.      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the specified attribute set to
  464.      * the given value. If an attribute with this name does not exist, it is created now. Otherwise, the new value overrides
  465.      * the old one.
  466.      *
  467.      * @param name the name of the attribute
  468.      * @param value the attribute value
  469.      * @return the new node with this attribute
  470.      */
  471.     public ImmutableNode setAttribute(final String name, final Object value) {
  472.         final Map<String, Object> newAttrs = new HashMap<>(attributes);
  473.         newAttrs.put(name, value);
  474.         return createWithNewAttributes(newAttrs);
  475.     }

  476.     /**
  477.      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with all attributes added defined by
  478.      * the given map. This method is analogous to {@link #setAttribute(String, Object)}, but all attributes in the given map
  479.      * are added. If the map is <strong>null</strong> or empty, this method has no effect.
  480.      *
  481.      * @param newAttributes the map with attributes to be added
  482.      * @return the new node with these attributes
  483.      */
  484.     public ImmutableNode setAttributes(final Map<String, ?> newAttributes) {
  485.         if (newAttributes == null || newAttributes.isEmpty()) {
  486.             return this;
  487.         }

  488.         final Map<String, Object> newAttrs = new HashMap<>(attributes);
  489.         newAttrs.putAll(newAttributes);
  490.         return createWithNewAttributes(newAttrs);
  491.     }

  492.     /**
  493.      * Creates a new {@code ImmutableNode} instance which is a copy of this object with the name changed to the passed in
  494.      * value.
  495.      *
  496.      * @param name the name of the newly created node
  497.      * @return the new node with the changed name
  498.      */
  499.     public ImmutableNode setName(final String name) {
  500.         return new Builder(children, attributes).name(name).value(value).create();
  501.     }

  502.     /**
  503.      * Creates a new {@code ImmutableNode} instance which is a copy of this object with the value changed to the passed in
  504.      * value.
  505.      *
  506.      * @param newValue the value of the newly created node
  507.      * @return the new node with the changed value
  508.      */
  509.     public ImmutableNode setValue(final Object newValue) {
  510.         return new Builder(children, attributes).name(nodeName).value(newValue).create();
  511.     }

  512.     /**
  513.      * Returns a sequential {@code Stream} with this node as its source.
  514.      *
  515.      * @return a sequential {@code Stream} over the elements in this node.
  516.      * @since 2.9.0
  517.      */
  518.     public Stream<ImmutableNode> stream() {
  519.         return StreamSupport.stream(spliterator(), false);
  520.     }

  521.     @Override
  522.     public String toString() {
  523.         return super.toString() + "(" + nodeName + ")";
  524.     }
  525. }