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