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