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