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