    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.configuration2.tree;
    018
    019import java.util.ArrayList;
    020import java.util.Collection;
    021import java.util.Collections;
    022import java.util.HashMap;
    023import java.util.Iterator;
    024import java.util.LinkedList;
    025import java.util.List;
    026import java.util.Map;
    027import java.util.stream.Collectors;
    028import java.util.stream.Stream;
    029import java.util.stream.StreamSupport;
    030
    031/**
    032 * <p>
    033 * An immutable default implementation for configuration nodes.
    034 * </p>
    035 * <p>
    036 * This class is used for an in-memory representation of hierarchical configuration data. It stores typical information
    037 * like a node name, a value, child nodes, or attributes.
    038 * </p>
    039 * <p>
    040 * After their creation, instances cannot be manipulated. There are methods for updating properties, but these methods
    041 * return new {@code ImmutableNode} instances. Instances are created using the nested {@code Builder} class.
    042 * </p>
    043 *
    044 * @since 2.0
    045 */
    046public final class ImmutableNode implements Iterable<ImmutableNode> {
    047
    048    /**
    049     * <p>
    050     * A <em>builder</em> class for creating instances of {@code ImmutableNode}.
    051     * </p>
    052     * <p>
    053     * This class can be used to set all properties of an immutable node instance. Eventually call the {@code create()}
    054     * method to obtain the resulting instance.
    055     * </p>
    056     * <p>
    057     * Implementation note: This class is not thread-safe. It is intended to be used to define a single node instance only.
    058     * </p>
    059     */
    060    public static final class Builder {
    061
    062        /**
    063         * Filters null entries from the passed in collection with child nodes.
    064         *
    065         *
    066         * @param children the collection to be filtered
    067         * @return the collection with null entries removed
    068         */
    069        private static Collection<? extends ImmutableNode> filterNull(final Collection<? extends ImmutableNode> children) {
    070            final List<ImmutableNode> result = new ArrayList<>(children.size());
    071            children.forEach(c -> {
    072                if (c != null) {
    073                    result.add(c);
    074                }
    075            });
    076            return result;
    077        }
    078
    079        /** The direct list of children of the new node. */
    080        private final List<ImmutableNode> directChildren;
    081
    082        /** The direct map of attributes of the new node. */
    083        private final Map<String, Object> directAttributes;
    084
    085        /**
    086         * A list for the children of the new node. This list is populated by the {@code addChild()} method.
    087         */
    088        private List<ImmutableNode> children;
    089
    090        /**
    091         * A map for storing the attributes of the new node. This map is populated by {@code addAttribute()}.
    092         */
    093        private Map<String, Object> attributes;
    094
    095        /** The name of the node. */
    096        private String name;
    097
    098        /** The value of the node. */
    099        private Object value;
    100
    101        /**
    102         * Creates a new instance of {@code Builder} which does not contain any property definitions yet.
    103         */
    104        public Builder() {
    105            this(null, null);
    106        }
    107
    108        /**
    109         * Creates a new instance of {@code Builder} and sets the number of expected child nodes. Using this constructor helps
    110         * the class to create a properly sized list for the child nodes to be added.
    111         *
    112         * @param childCount the number of child nodes
    113         */
    114        public Builder(final int childCount) {
    115            this();
    116            initChildrenCollection(childCount);
    117        }
    118
    119        /**
    120         * Creates a new instance of {@code Builder} and initializes the attributes of the new node and prepares the collection
    121         * for the children. This constructor is used internally by methods of {@code ImmutableNode} which update the node and
    122         * change the children. The new number of child nodes can be passed so that the collection for the new children can be
    123         * created with an appropriate size.
    124         *
    125         * @param childCount the expected number of new children
    126         * @param dirAttrs the attributes of the new node
    127         */
    128        private Builder(final int childCount, final Map<String, Object> dirAttrs) {
    129            this(null, dirAttrs);
    130            initChildrenCollection(childCount);
    131        }
    132
    133        /**
    134         * Creates a new instance of {@code Builder} and initializes the children and attributes of the new node. This
    135         * constructor is used internally by the {@code ImmutableNode} class for creating instances derived from another node.
    136         * The passed in collections are passed directly to the newly created instance; thus they already need to be immutable.
    137         * (Background is that the creation of intermediate objects is to be avoided.)
    138         *
    139         * @param dirChildren the children of the new node
    140         * @param dirAttrs the attributes of the new node
    141         */
    142        private Builder(final List<ImmutableNode> dirChildren, final Map<String, Object> dirAttrs) {
    143            directChildren = dirChildren;
    144            directAttributes = dirAttrs;
    145        }
    146
    147        /**
    148         * Adds an attribute to this builder. The passed in attribute key and value are stored in an internal map. If there is
    149         * already an attribute with this name, it is overridden.
    150         *
    151         * @param name the attribute name
    152         * @param value the attribute value
    153         * @return a reference to this object for method chaining
    154         */
    155        public Builder addAttribute(final String name, final Object value) {
    156            ensureAttributesExist();
    157            attributes.put(name, value);
    158            return this;
    159        }
    160
    161        /**
    162         * Adds all attributes of the given map to this builder. This method works like {@link #addAttribute(String, Object)},
    163         * but it allows setting multiple attributes at once.
    164         *
    165         * @param attrs the map with attributes to be added (may be <strong>null</strong>
    166         * @return a reference to this object for method chaining
    167         */
    168        public Builder addAttributes(final Map<String, ?> attrs) {
    169            if (attrs != null) {
    170                ensureAttributesExist();
    171                attributes.putAll(attrs);
    172            }
    173            return this;
    174        }
    175
    176        /**
    177         * Adds a child node to this builder. The passed in node becomes a child of the newly created node. If it is
    178         * <strong>null</strong>, it is ignored.
    179         *
    180         * @param c the child node (must not be <strong>null</strong>)
    181         * @return a reference to this object for method chaining
    182         */
    183        public Builder addChild(final ImmutableNode c) {
    184            if (c != null) {
    185                ensureChildrenExist();
    186                children.add(c);
    187            }
    188            return this;
    189        }
    190
    191        /**
    192         * Adds multiple child nodes to this builder. This method works like {@link #addChild(ImmutableNode)}, but it allows
    193         * setting a number of child nodes at once.
    194         *
    195         *
    196         * @param children a collection with the child nodes to be added
    197         * @return a reference to this object for method chaining
    198         */
    199        public Builder addChildren(final Collection<? extends ImmutableNode> children) {
    200            if (children != null) {
    201                ensureChildrenExist();
    202                this.children.addAll(filterNull(children));
    203            }
    204            return this;
    205        }
    206
    207        /**
    208         * Creates a new {@code ImmutableNode} instance based on the properties set for this builder.
    209         *
    210         * @return the newly created {@code ImmutableNode}
    211         */
    212        public ImmutableNode create() {
    213            final ImmutableNode newNode = new ImmutableNode(this);
    214            children = null;
    215            attributes = null;
    216            return newNode;
    217        }
    218
    219        /**
    220         * Creates a map with the attributes of the newly created node. This is an immutable map. If direct attributes were set,
    221         * they are returned. Otherwise an unmodifiable map from the attributes passed to this builder is constructed.
    222         *
    223         * @return a map with the attributes for the new node
    224         */
    225        private Map<String, Object> createAttributes() {
    226            if (directAttributes != null) {
    227                return directAttributes;
    228            }
    229            if (attributes != null) {
    230                return Collections.unmodifiableMap(attributes);
    231            }
    232            return Collections.emptyMap();
    233        }
    234
    235        /**
    236         * Creates a list with the children of the newly created node. The list returned here is always immutable. It depends on
    237         * the way this builder was populated.
    238         *
    239         * @return the list with the children of the new node
    240         */
    241        List<ImmutableNode> createChildren() {
    242            if (directChildren != null) {
    243                return directChildren;
    244            }
    245            if (children != null) {
    246                return Collections.unmodifiableList(children);
    247            }
    248            return Collections.emptyList();
    249        }
    250
    251        /**
    252         * Ensures that the map for the attributes exists. It is created on demand.
    253         */
    254        private void ensureAttributesExist() {
    255            if (attributes == null) {
    256                attributes = new HashMap<>();
    257            }
    258        }
    259
    260        /**
    261         * Ensures that the collection for the child nodes exists. It is created on demand.
    262         */
    263        private void ensureChildrenExist() {
    264            if (children == null) {
    265                children = new LinkedList<>();
    266            }
    267        }
    268
    269        /**
    270         * Creates the collection for child nodes based on the expected number of children.
    271         *
    272         * @param childCount the expected number of new children
    273         */
    274        private void initChildrenCollection(final int childCount) {
    275            if (childCount > 0) {
    276                children = new ArrayList<>(childCount);
    277            }
    278        }
    279
    280        /**
    281         * Sets the name of the node to be created.
    282         *
    283         * @param n the node name
    284         * @return a reference to this object for method chaining
    285         */
    286        public Builder name(final String n) {
    287            name = n;
    288            return this;
    289        }
    290
    291        /**
    292         * Sets the value of the node to be created.
    293         *
    294         * @param v the value
    295         * @return a reference to this object for method chaining
    296         */
    297        public Builder value(final Object v) {
    298            value = v;
    299            return this;
    300        }
    301    }
    302
    303    /**
    304     * Checks whether the given child node is not null. This check is done at multiple places to ensure that newly added
    305     * child nodes are always defined.
    306     *
    307     * @param child the child node to be checked
    308     * @throws IllegalArgumentException if the child node is <strong>null</strong>
    309     */
    310    private static void checkChildNode(final ImmutableNode child) {
    311        if (child == null) {
    312            throw new IllegalArgumentException("Child node must not be null!");
    313        }
    314    }
    315
    316    /** The name of this node. */
    317    private final String nodeName;
    318
    319    /** The value of this node. */
    320    private final Object value;
    321
    322    /** A collection with the child nodes of this node. */
    323    private final List<ImmutableNode> children;
    324
    325    /** A map with the attributes of this node. */
    326    private final Map<String, Object> attributes;
    327
    328    /**
    329     * Creates a new instance of {@code ImmutableNode} from the given {@code Builder} object.
    330     *
    331     * @param b the {@code Builder}
    332     */
    333    private ImmutableNode(final Builder b) {
    334        children = b.createChildren();
    335        attributes = b.createAttributes();
    336        nodeName = b.name;
    337        value = b.value;
    338    }
    339
    340    /**
    341     * Creates a new {@code ImmutableNode} instance which is a copy of this object, but has the given child node added.
    342     *
    343     * @param child the child node to be added (must not be <strong>null</strong>)
    344     * @return the new node with the child node added
    345     * @throws IllegalArgumentException if the child node is <strong>null</strong>
    346     */
    347    public ImmutableNode addChild(final ImmutableNode child) {
    348        checkChildNode(child);
    349        final Builder builder = new Builder(children.size() + 1, attributes);
    350        builder.addChildren(children).addChild(child);
    351        return createWithBasicProperties(builder);
    352    }
    353
    354    /**
    355     * Initializes the given builder with basic properties (node name and value) and returns the newly created node. This is
    356     * a helper method for updating a node when only children or attributes are affected.
    357     *
    358     * @param builder the already prepared builder
    359     * @return the newly created node
    360     */
    361    private ImmutableNode createWithBasicProperties(final Builder builder) {
    362        return builder.name(nodeName).value(value).create();
    363    }
    364
    365    /**
    366     * Creates a new {@code ImmutableNode} instance with the same properties as this object, but with the given new
    367     * attributes.
    368     *
    369     * @param newAttrs the new attributes
    370     * @return the new node instance
    371     */
    372    private ImmutableNode createWithNewAttributes(final Map<String, Object> newAttrs) {
    373        return createWithBasicProperties(new Builder(children, null).addAttributes(newAttrs));
    374    }
    375
    376    /**
    377     * Gets a map with the attributes of this node. This map cannot be modified.
    378     *
    379     * @return a map with this node's attributes
    380     */
    381    public Map<String, Object> getAttributes() {
    382        return attributes;
    383    }
    384
    385    /**
    386     * Gets a list with the children of this node. This list cannot be modified.
    387     *
    388     * @return a list with the child nodes
    389     */
    390    public List<ImmutableNode> getChildren() {
    391        return children;
    392    }
    393
    394    /**
    395     * Returns a list with the children of this node.
    396     *
    397     * @param name the node name to find
    398     * @return a list with the child nodes
    399     */
    400    public List<ImmutableNode> getChildren(final String name) {
    401        if (name == null) {
    402            return new ArrayList<>();
    403        }
    404        return children.stream().filter(in -> name.equals(in.getNodeName())).collect(Collectors.toList());
    405    }
    406
    407    /**
    408     * Gets the name of this node.
    409     *
    410     * @return the name of this node
    411     */
    412    public String getNodeName() {
    413        return nodeName;
    414    }
    415
    416    /**
    417     * Gets the value of this node.
    418     *
    419     * @return the value of this node
    420     */
    421    public Object getValue() {
    422        return value;
    423    }
    424
    425    /**
    426     * @return An iterator of {@link #children child nodes.}
    427     * @since 2.8.0
    428     */
    429    @Override
    430    public Iterator<ImmutableNode> iterator() {
    431        return children.iterator();
    432    }
    433
    434    /**
    435     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the specified attribute
    436     * removed. If there is no attribute with the given name, the same node instance is returned.
    437     *
    438     * @param name the name of the attribute
    439     * @return the new node without this attribute
    440     */
    441    public ImmutableNode removeAttribute(final String name) {
    442        final Map<String, Object> newAttrs = new HashMap<>(attributes);
    443        if (newAttrs.remove(name) != null) {
    444            return createWithNewAttributes(newAttrs);
    445        }
    446        return this;
    447    }
    448
    449    /**
    450     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the given child node removed.
    451     * If the child node does not belong to this node, the same node instance is returned.
    452     *
    453     * @param child the child node to be removed
    454     * @return the new node with the child node removed
    455     */
    456    public ImmutableNode removeChild(final ImmutableNode child) {
    457        // use same size of children in case the child does not exist
    458        final Builder builder = new Builder(children.size(), attributes);
    459        boolean foundChild = false;
    460        for (final ImmutableNode c : children) {
    461            if (c == child) {
    462                foundChild = true;
    463            } else {
    464                builder.addChild(c);
    465            }
    466        }
    467
    468        return foundChild ? createWithBasicProperties(builder) : this;
    469    }
    470
    471    /**
    472     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the given child replaced by the
    473     * new one. If the child to be replaced cannot be found, the same node instance is returned.
    474     *
    475     * @param oldChild the child node to be replaced
    476     * @param newChild the replacing child node (must not be <strong>null</strong>)
    477     * @return the new node with the child replaced
    478     * @throws IllegalArgumentException if the new child node is <strong>null</strong>
    479     */
    480    public ImmutableNode replaceChild(final ImmutableNode oldChild, final ImmutableNode newChild) {
    481        checkChildNode(newChild);
    482        final Builder builder = new Builder(children.size(), attributes);
    483        boolean foundChild = false;
    484        for (final ImmutableNode c : children) {
    485            if (c == oldChild) {
    486                builder.addChild(newChild);
    487                foundChild = true;
    488            } else {
    489                builder.addChild(c);
    490            }
    491        }
    492
    493        return foundChild ? createWithBasicProperties(builder) : this;
    494    }
    495
    496    /**
    497     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the children replaced by the
    498     * ones in the passed in collection. With this method all children can be replaced in a single step. For the collection
    499     * the same rules apply as for {@link Builder#addChildren(Collection)}.
    500     *
    501     * @param newChildren the collection with the new children (may be <strong>null</strong>)
    502     * @return the new node with replaced children
    503     */
    504    public ImmutableNode replaceChildren(final Collection<ImmutableNode> newChildren) {
    505        final Builder builder = new Builder(null, attributes);
    506        builder.addChildren(newChildren);
    507        return createWithBasicProperties(builder);
    508    }
    509
    510    /**
    511     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the specified attribute set to
    512     * the given value. If an attribute with this name does not exist, it is created now. Otherwise, the new value overrides
    513     * the old one.
    514     *
    515     * @param name the name of the attribute
    516     * @param value the attribute value
    517     * @return the new node with this attribute
    518     */
    519    public ImmutableNode setAttribute(final String name, final Object value) {
    520        final Map<String, Object> newAttrs = new HashMap<>(attributes);
    521        newAttrs.put(name, value);
    522        return createWithNewAttributes(newAttrs);
    523    }
    524
    525    /**
    526     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with all attributes added defined by
    527     * the given map. This method is analogous to {@link #setAttribute(String, Object)}, but all attributes in the given map
    528     * are added. If the map is <strong>null</strong> or empty, this method has no effect.
    529     *
    530     * @param newAttributes the map with attributes to be added
    531     * @return the new node with these attributes
    532     */
    533    public ImmutableNode setAttributes(final Map<String, ?> newAttributes) {
    534        if (newAttributes == null || newAttributes.isEmpty()) {
    535            return this;
    536        }
    537
    538        final Map<String, Object> newAttrs = new HashMap<>(attributes);
    539        newAttrs.putAll(newAttributes);
    540        return createWithNewAttributes(newAttrs);
    541    }
    542
    543    /**
    544     * Creates a new {@code ImmutableNode} instance which is a copy of this object with the name changed to the passed in
    545     * value.
    546     *
    547     * @param name the name of the newly created node
    548     * @return the new node with the changed name
    549     */
    550    public ImmutableNode setName(final String name) {
    551        return new Builder(children, attributes).name(name).value(value).create();
    552    }
    553
    554    /**
    555     * Creates a new {@code ImmutableNode} instance which is a copy of this object with the value changed to the passed in
    556     * value.
    557     *
    558     * @param newValue the value of the newly created node
    559     * @return the new node with the changed value
    560     */
    561    public ImmutableNode setValue(final Object newValue) {
    562        return new Builder(children, attributes).name(nodeName).value(newValue).create();
    563    }
    564
    565    /**
    566     * Returns a sequential {@code Stream} with this node as its source.
    567     *
    568     * @return a sequential {@code Stream} over the elements in this node.
    569     * @since 2.9.0
    570     */
    571    public Stream<ImmutableNode> stream() {
    572        return StreamSupport.stream(spliterator(), false);
    573    }
    574
    575    @Override
    576    public String toString() {
    577        return super.toString() + "(" + nodeName + ")";
    578    }
    579}