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