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