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 }