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      /**
49       * <p>
50       * A <em>builder</em> class for creating instances of {@code ImmutableNode}.
51       * </p>
52       * <p>
53       * This class can be used to set all properties of an immutable node instance. Eventually call the {@code create()}
54       * method to obtain the resulting instance.
55       * </p>
56       * <p>
57       * Implementation note: This class is not thread-safe. It is intended to be used to define a single node instance only.
58       * </p>
59       */
60      public static final class Builder {
61  
62          /**
63           * Filters null entries from the passed in collection with child nodes.
64           *
65           *
66           * @param children the collection to be filtered
67           * @return the collection with null entries removed
68           */
69          private static Collection<? extends ImmutableNode> filterNull(final Collection<? extends ImmutableNode> children) {
70              final List<ImmutableNode> result = new ArrayList<>(children.size());
71              children.forEach(c -> {
72                  if (c != null) {
73                      result.add(c);
74                  }
75              });
76              return result;
77          }
78  
79          /** The direct list of children of the new node. */
80          private final List<ImmutableNode> directChildren;
81  
82          /** The direct map of attributes of the new node. */
83          private final Map<String, Object> directAttributes;
84  
85          /**
86           * A list for the children of the new node. This list is populated by the {@code addChild()} method.
87           */
88          private List<ImmutableNode> children;
89  
90          /**
91           * A map for storing the attributes of the new node. This map is populated by {@code addAttribute()}.
92           */
93          private Map<String, Object> attributes;
94  
95          /** The name of the node. */
96          private String name;
97  
98          /** The value of the node. */
99          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 }