View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.tree;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.stream.Collectors;
28  import java.util.stream.Stream;
29  import java.util.stream.StreamSupport;
30  
31  /**
32   * <p>
33   * An immutable default implementation for configuration nodes.
34   * </p>
35   * <p>
36   * This class is used for an in-memory representation of hierarchical configuration data. It stores typical information
37   * like a node name, a value, child nodes, or attributes.
38   * </p>
39   * <p>
40   * After their creation, instances cannot be manipulated. There are methods for updating properties, but these methods
41   * return new {@code ImmutableNode} instances. Instances are created using the nested {@code Builder} class.
42   * </p>
43   *
44   * @since 2.0
45   */
46  public final class ImmutableNode implements Iterable<ImmutableNode> {
47      /**
48       * <p>
49       * A <em>builder</em> class for creating instances of {@code ImmutableNode}.
50       * </p>
51       * <p>
52       * This class can be used to set all properties of an immutable node instance. Eventually call the {@code create()}
53       * method to obtain the resulting instance.
54       * </p>
55       * <p>
56       * Implementation note: This class is not thread-safe. It is intended to be used to define a single node instance only.
57       * </p>
58       */
59      public static final class Builder {
60          /**
61           * Filters null entries from the passed in collection with child nodes.
62           *
63           *
64           * @param children the collection to be filtered
65           * @return the collection with null entries removed
66           */
67          private static Collection<? extends ImmutableNode> filterNull(final Collection<? extends ImmutableNode> children) {
68              final List<ImmutableNode> result = new ArrayList<>(children.size());
69              children.forEach(c -> {
70                  if (c != null) {
71                      result.add(c);
72                  }
73              });
74              return result;
75          }
76  
77          /** The direct list of children of the new node. */
78          private final List<ImmutableNode> directChildren;
79  
80          /** The direct map of attributes of the new node. */
81          private final Map<String, Object> directAttributes;
82  
83          /**
84           * A list for the children of the new node. This list is populated by the {@code addChild()} method.
85           */
86          private List<ImmutableNode> children;
87  
88          /**
89           * A map for storing the attributes of the new node. This map is populated by {@code addAttribute()}.
90           */
91          private Map<String, Object> attributes;
92  
93          /** The name of the node. */
94          private String name;
95  
96          /** The value of the node. */
97          private Object value;
98  
99          /**
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 <strong>null</strong>
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          * <strong>null</strong>, it is ignored.
177          *
178          * @param c the child node (must not be <strong>null</strong>)
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 <strong>null</strong>
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 <strong>null</strong>)
342      * @return the new node with the child node added
343      * @throws IllegalArgumentException if the child node is <strong>null</strong>
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      * @return a list with the child nodes
397      */
398     public List<ImmutableNode> getChildren(final String name) {
399         if (name == null) {
400             return new ArrayList<>();
401         }
402         return children.stream().filter(in -> name.equals(in.getNodeName())).collect(Collectors.toList());
403     }
404 
405     /**
406      * Gets the name of this node.
407      *
408      * @return the name of this node
409      */
410     public String getNodeName() {
411         return nodeName;
412     }
413 
414     /**
415      * Gets the value of this node.
416      *
417      * @return the value of this node
418      */
419     public Object getValue() {
420         return value;
421     }
422 
423     /**
424      * @return An iterator of {@link #children child nodes.}
425      * @since 2.8.0
426      */
427     @Override
428     public Iterator<ImmutableNode> iterator() {
429         return children.iterator();
430     }
431 
432     /**
433      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the specified attribute
434      * removed. If there is no attribute with the given name, the same node instance is returned.
435      *
436      * @param name the name of the attribute
437      * @return the new node without this attribute
438      */
439     public ImmutableNode removeAttribute(final String name) {
440         final Map<String, Object> newAttrs = new HashMap<>(attributes);
441         if (newAttrs.remove(name) != null) {
442             return createWithNewAttributes(newAttrs);
443         }
444         return this;
445     }
446 
447     /**
448      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the given child node removed.
449      * If the child node does not belong to this node, the same node instance is returned.
450      *
451      * @param child the child node to be removed
452      * @return the new node with the child node removed
453      */
454     public ImmutableNode removeChild(final ImmutableNode child) {
455         // use same size of children in case the child does not exist
456         final Builder builder = new Builder(children.size(), attributes);
457         boolean foundChild = false;
458         for (final ImmutableNode c : children) {
459             if (c == child) {
460                 foundChild = true;
461             } else {
462                 builder.addChild(c);
463             }
464         }
465 
466         return foundChild ? createWithBasicProperties(builder) : this;
467     }
468 
469     /**
470      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the given child replaced by the
471      * new one. If the child to be replaced cannot be found, the same node instance is returned.
472      *
473      * @param oldChild the child node to be replaced
474      * @param newChild the replacing child node (must not be <strong>null</strong>)
475      * @return the new node with the child replaced
476      * @throws IllegalArgumentException if the new child node is <strong>null</strong>
477      */
478     public ImmutableNode replaceChild(final ImmutableNode oldChild, final ImmutableNode newChild) {
479         checkChildNode(newChild);
480         final Builder builder = new Builder(children.size(), attributes);
481         boolean foundChild = false;
482         for (final ImmutableNode c : children) {
483             if (c == oldChild) {
484                 builder.addChild(newChild);
485                 foundChild = true;
486             } else {
487                 builder.addChild(c);
488             }
489         }
490 
491         return foundChild ? createWithBasicProperties(builder) : this;
492     }
493 
494     /**
495      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the children replaced by the
496      * ones in the passed in collection. With this method all children can be replaced in a single step. For the collection
497      * the same rules apply as for {@link Builder#addChildren(Collection)}.
498      *
499      * @param newChildren the collection with the new children (may be <strong>null</strong>)
500      * @return the new node with replaced children
501      */
502     public ImmutableNode replaceChildren(final Collection<ImmutableNode> newChildren) {
503         final Builder builder = new Builder(null, attributes);
504         builder.addChildren(newChildren);
505         return createWithBasicProperties(builder);
506     }
507 
508     /**
509      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the specified attribute set to
510      * the given value. If an attribute with this name does not exist, it is created now. Otherwise, the new value overrides
511      * the old one.
512      *
513      * @param name the name of the attribute
514      * @param value the attribute value
515      * @return the new node with this attribute
516      */
517     public ImmutableNode setAttribute(final String name, final Object value) {
518         final Map<String, Object> newAttrs = new HashMap<>(attributes);
519         newAttrs.put(name, value);
520         return createWithNewAttributes(newAttrs);
521     }
522 
523     /**
524      * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with all attributes added defined by
525      * the given map. This method is analogous to {@link #setAttribute(String, Object)}, but all attributes in the given map
526      * are added. If the map is <strong>null</strong> or empty, this method has no effect.
527      *
528      * @param newAttributes the map with attributes to be added
529      * @return the new node with these attributes
530      */
531     public ImmutableNode setAttributes(final Map<String, ?> newAttributes) {
532         if (newAttributes == null || newAttributes.isEmpty()) {
533             return this;
534         }
535 
536         final Map<String, Object> newAttrs = new HashMap<>(attributes);
537         newAttrs.putAll(newAttributes);
538         return createWithNewAttributes(newAttrs);
539     }
540 
541     /**
542      * Creates a new {@code ImmutableNode} instance which is a copy of this object with the name changed to the passed in
543      * value.
544      *
545      * @param name the name of the newly created node
546      * @return the new node with the changed name
547      */
548     public ImmutableNode setName(final String name) {
549         return new Builder(children, attributes).name(name).value(value).create();
550     }
551 
552     /**
553      * Creates a new {@code ImmutableNode} instance which is a copy of this object with the value changed to the passed in
554      * value.
555      *
556      * @param newValue the value of the newly created node
557      * @return the new node with the changed value
558      */
559     public ImmutableNode setValue(final Object newValue) {
560         return new Builder(children, attributes).name(nodeName).value(newValue).create();
561     }
562 
563     /**
564      * Returns a sequential {@code Stream} with this node as its source.
565      *
566      * @return a sequential {@code Stream} over the elements in this node.
567      * @since 2.9.0
568      */
569     public Stream<ImmutableNode> stream() {
570         return StreamSupport.stream(spliterator(), false);
571     }
572 
573     @Override
574     public String toString() {
575         return super.toString() + "(" + nodeName + ")";
576     }
577 }