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