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