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}