    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 <b>null</b>
    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         * <b>null</b>, it is ignored.
    177         *
    178         * @param c the child node (must not be <b>null</b>)
    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 <b>null</b>
    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 <b>null</b>)
    342     * @return the new node with the child node added
    343     * @throws IllegalArgumentException if the child node is <b>null</b>
    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     *
    397     * @return a list with the child nodes
    398     */
    399    public List<ImmutableNode> getChildren(final String name) {
    400        if (name == null) {
    401            return new ArrayList<>();
    402        }
    403        return children.stream().filter(in -> name.equals(in.getNodeName())).collect(Collectors.toList());
    404    }
    405
    406    /**
    407     * Gets the name of this node.
    408     *
    409     * @return the name of this node
    410     */
    411    public String getNodeName() {
    412        return nodeName;
    413    }
    414
    415    /**
    416     * Gets the value of this node.
    417     *
    418     * @return the value of this node
    419     */
    420    public Object getValue() {
    421        return value;
    422    }
    423
    424    /**
    425     * @return An iterator of {@link #children child nodes.}
    426     * @since 2.8.0
    427     */
    428    @Override
    429    public Iterator<ImmutableNode> iterator() {
    430        return children.iterator();
    431    }
    432
    433    /**
    434     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the specified attribute
    435     * removed. If there is no attribute with the given name, the same node instance is returned.
    436     *
    437     * @param name the name of the attribute
    438     * @return the new node without this attribute
    439     */
    440    public ImmutableNode removeAttribute(final String name) {
    441        final Map<String, Object> newAttrs = new HashMap<>(attributes);
    442        if (newAttrs.remove(name) != null) {
    443            return createWithNewAttributes(newAttrs);
    444        }
    445        return this;
    446    }
    447
    448    /**
    449     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the given child node removed.
    450     * If the child node does not belong to this node, the same node instance is returned.
    451     *
    452     * @param child the child node to be removed
    453     * @return the new node with the child node removed
    454     */
    455    public ImmutableNode removeChild(final ImmutableNode child) {
    456        // use same size of children in case the child does not exist
    457        final Builder builder = new Builder(children.size(), attributes);
    458        boolean foundChild = false;
    459        for (final ImmutableNode c : children) {
    460            if (c == child) {
    461                foundChild = true;
    462            } else {
    463                builder.addChild(c);
    464            }
    465        }
    466
    467        return foundChild ? createWithBasicProperties(builder) : this;
    468    }
    469
    470    /**
    471     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the given child replaced by the
    472     * new one. If the child to be replaced cannot be found, the same node instance is returned.
    473     *
    474     * @param oldChild the child node to be replaced
    475     * @param newChild the replacing child node (must not be <b>null</b>)
    476     * @return the new node with the child replaced
    477     * @throws IllegalArgumentException if the new child node is <b>null</b>
    478     */
    479    public ImmutableNode replaceChild(final ImmutableNode oldChild, final ImmutableNode newChild) {
    480        checkChildNode(newChild);
    481        final Builder builder = new Builder(children.size(), attributes);
    482        boolean foundChild = false;
    483        for (final ImmutableNode c : children) {
    484            if (c == oldChild) {
    485                builder.addChild(newChild);
    486                foundChild = true;
    487            } else {
    488                builder.addChild(c);
    489            }
    490        }
    491
    492        return foundChild ? createWithBasicProperties(builder) : this;
    493    }
    494
    495    /**
    496     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the children replaced by the
    497     * ones in the passed in collection. With this method all children can be replaced in a single step. For the collection
    498     * the same rules apply as for {@link Builder#addChildren(Collection)}.
    499     *
    500     * @param newChildren the collection with the new children (may be <b>null</b>)
    501     * @return the new node with replaced children
    502     */
    503    public ImmutableNode replaceChildren(final Collection<ImmutableNode> newChildren) {
    504        final Builder builder = new Builder(null, attributes);
    505        builder.addChildren(newChildren);
    506        return createWithBasicProperties(builder);
    507    }
    508
    509    /**
    510     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the specified attribute set to
    511     * the given value. If an attribute with this name does not exist, it is created now. Otherwise, the new value overrides
    512     * the old one.
    513     *
    514     * @param name the name of the attribute
    515     * @param value the attribute value
    516     * @return the new node with this attribute
    517     */
    518    public ImmutableNode setAttribute(final String name, final Object value) {
    519        final Map<String, Object> newAttrs = new HashMap<>(attributes);
    520        newAttrs.put(name, value);
    521        return createWithNewAttributes(newAttrs);
    522    }
    523
    524    /**
    525     * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with all attributes added defined by
    526     * the given map. This method is analogous to {@link #setAttribute(String, Object)}, but all attributes in the given map
    527     * are added. If the map is <b>null</b> or empty, this method has no effect.
    528     *
    529     * @param newAttributes the map with attributes to be added
    530     * @return the new node with these attributes
    531     */
    532    public ImmutableNode setAttributes(final Map<String, ?> newAttributes) {
    533        if (newAttributes == null || newAttributes.isEmpty()) {
    534            return this;
    535        }
    536
    537        final Map<String, Object> newAttrs = new HashMap<>(attributes);
    538        newAttrs.putAll(newAttributes);
    539        return createWithNewAttributes(newAttrs);
    540    }
    541
    542    /**
    543     * Creates a new {@code ImmutableNode} instance which is a copy of this object with the name changed to the passed in
    544     * value.
    545     *
    546     * @param name the name of the newly created node
    547     * @return the new node with the changed name
    548     */
    549    public ImmutableNode setName(final String name) {
    550        return new Builder(children, attributes).name(name).value(value).create();
    551    }
    552
    553    /**
    554     * Creates a new {@code ImmutableNode} instance which is a copy of this object with the value changed to the passed in
    555     * value.
    556     *
    557     * @param newValue the value of the newly created node
    558     * @return the new node with the changed value
    559     */
    560    public ImmutableNode setValue(final Object newValue) {
    561        return new Builder(children, attributes).name(nodeName).value(newValue).create();
    562    }
    563
    564    /**
    565     * Returns a sequential {@code Stream} with this node as its source.
    566     *
    567     * @return a sequential {@code Stream} over the elements in this node.
    568     * @since 2.9.0
    569     */
    570    public Stream<ImmutableNode> stream() {
    571        return StreamSupport.stream(spliterator(), false);
    572    }
    573
    574    @Override
    575    public String toString() {
    576        return super.toString() + "(" + nodeName + ")";
    577    }
    578}