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