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}