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