001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.configuration2.tree; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.stream.Collectors; 028import java.util.stream.Stream; 029import java.util.stream.StreamSupport; 030 031/** 032 * <p> 033 * An immutable default implementation for configuration nodes. 034 * </p> 035 * <p> 036 * This class is used for an in-memory representation of hierarchical configuration data. It stores typical information 037 * like a node name, a value, child nodes, or attributes. 038 * </p> 039 * <p> 040 * After their creation, instances cannot be manipulated. There are methods for updating properties, but these methods 041 * return new {@code ImmutableNode} instances. Instances are created using the nested {@code Builder} class. 042 * </p> 043 * 044 * @since 2.0 045 */ 046public final class ImmutableNode implements Iterable<ImmutableNode> { 047 048 /** 049 * <p> 050 * A <em>builder</em> class for creating instances of {@code ImmutableNode}. 051 * </p> 052 * <p> 053 * This class can be used to set all properties of an immutable node instance. Eventually call the {@code create()} 054 * method to obtain the resulting instance. 055 * </p> 056 * <p> 057 * Implementation note: This class is not thread-safe. It is intended to be used to define a single node instance only. 058 * </p> 059 */ 060 public static final class Builder { 061 062 /** 063 * Filters null entries from the passed in collection with child nodes. 064 * 065 * 066 * @param children the collection to be filtered 067 * @return the collection with null entries removed 068 */ 069 private static Collection<? extends ImmutableNode> filterNull(final Collection<? extends ImmutableNode> children) { 070 final List<ImmutableNode> result = new ArrayList<>(children.size()); 071 children.forEach(c -> { 072 if (c != null) { 073 result.add(c); 074 } 075 }); 076 return result; 077 } 078 079 /** The direct list of children of the new node. */ 080 private final List<ImmutableNode> directChildren; 081 082 /** The direct map of attributes of the new node. */ 083 private final Map<String, Object> directAttributes; 084 085 /** 086 * A list for the children of the new node. This list is populated by the {@code addChild()} method. 087 */ 088 private List<ImmutableNode> children; 089 090 /** 091 * A map for storing the attributes of the new node. This map is populated by {@code addAttribute()}. 092 */ 093 private Map<String, Object> attributes; 094 095 /** The name of the node. */ 096 private String name; 097 098 /** The value of the node. */ 099 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}