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 }