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 * http://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.LinkedList; 024import java.util.List; 025import java.util.Map; 026 027/** 028 * <p> 029 * An immutable default implementation for configuration nodes. 030 * </p> 031 * <p> 032 * This class is used for an in-memory representation of hierarchical 033 * configuration data. It stores typical information like a node name, a value, 034 * child nodes, or attributes. 035 * </p> 036 * <p> 037 * After their creation, instances cannot be manipulated. There are methods for 038 * updating properties, but these methods return new {@code ImmutableNode} 039 * instances. Instances are created using the nested {@code Builder} class. 040 * </p> 041 * 042 * @version $Id: ImmutableNode.java 1842194 2018-09-27 22:24:23Z ggregory $ 043 * @since 2.0 044 */ 045public final class ImmutableNode 046{ 047 /** The name of this node. */ 048 private final String nodeName; 049 050 /** The value of this node. */ 051 private final Object value; 052 053 /** A collection with the child nodes of this node. */ 054 private final List<ImmutableNode> children; 055 056 /** A map with the attributes of this node. */ 057 private final Map<String, Object> attributes; 058 059 /** 060 * Creates a new instance of {@code ImmutableNode} from the given 061 * {@code Builder} object. 062 * 063 * @param b the {@code Builder} 064 */ 065 private ImmutableNode(final Builder b) 066 { 067 children = b.createChildren(); 068 attributes = b.createAttributes(); 069 nodeName = b.name; 070 value = b.value; 071 } 072 073 /** 074 * Returns the name of this node. 075 * 076 * @return the name of this node 077 */ 078 public String getNodeName() 079 { 080 return nodeName; 081 } 082 083 /** 084 * Returns the value of this node. 085 * 086 * @return the value of this node 087 */ 088 public Object getValue() 089 { 090 return value; 091 } 092 093 /** 094 * Returns a list with the children of this node. This list cannot be 095 * modified. 096 * 097 * @return a list with the child nodes 098 */ 099 public List<ImmutableNode> getChildren() 100 { 101 return children; 102 } 103 104 /** 105 * Returns a list with the children of this node. 106 * 107 * @param name the node name to find 108 * 109 * @return a list with the child nodes 110 */ 111 public List<ImmutableNode> getChildren(final String name) 112 { 113 final List<ImmutableNode> list = new ArrayList<>(); 114 if (name == null) 115 { 116 return list; 117 } 118 for (final ImmutableNode node : children) 119 { 120 if (name.equals(node.getNodeName())) 121 { 122 list.add(node); 123 } 124 } 125 return list; 126 } 127 128 /** 129 * Returns a map with the attributes of this node. This map cannot be 130 * modified. 131 * 132 * @return a map with this node's attributes 133 */ 134 public Map<String, Object> getAttributes() 135 { 136 return attributes; 137 } 138 139 /** 140 * Creates a new {@code ImmutableNode} instance which is a copy of this 141 * object with the name changed to the passed in value. 142 * 143 * @param name the name of the newly created node 144 * @return the new node with the changed name 145 */ 146 public ImmutableNode setName(final String name) 147 { 148 return new Builder(children, attributes).name(name).value(value) 149 .create(); 150 } 151 152 /** 153 * Creates a new {@code ImmutableNode} instance which is a copy of this 154 * object with the value changed to the passed in value. 155 * 156 * @param newValue the value of the newly created node 157 * @return the new node with the changed value 158 */ 159 public ImmutableNode setValue(final Object newValue) 160 { 161 return new Builder(children, attributes).name(nodeName).value(newValue) 162 .create(); 163 } 164 165 /** 166 * Creates a new {@code ImmutableNode} instance which is a copy of this 167 * object, but has the given child node added. 168 * 169 * @param child the child node to be added (must not be <b>null</b>) 170 * @return the new node with the child node added 171 * @throws IllegalArgumentException if the child node is <b>null</b> 172 */ 173 public ImmutableNode addChild(final ImmutableNode child) 174 { 175 checkChildNode(child); 176 final Builder builder = new Builder(children.size() + 1, attributes); 177 builder.addChildren(children).addChild(child); 178 return createWithBasicProperties(builder); 179 } 180 181 /** 182 * Returns a new {@code ImmutableNode} instance which is a copy of this 183 * object, but with the given child node removed. If the child node does not 184 * belong to this node, the same node instance is returned. 185 * 186 * @param child the child node to be removed 187 * @return the new node with the child node removed 188 */ 189 public ImmutableNode removeChild(final ImmutableNode child) 190 { 191 // use same size of children in case the child does not exist 192 final Builder builder = new Builder(children.size(), attributes); 193 boolean foundChild = false; 194 for (final ImmutableNode c : children) 195 { 196 if (c == child) 197 { 198 foundChild = true; 199 } 200 else 201 { 202 builder.addChild(c); 203 } 204 } 205 206 return foundChild ? createWithBasicProperties(builder) : this; 207 } 208 209 /** 210 * Returns a new {@code ImmutableNode} instance which is a copy of this 211 * object, but with the given child replaced by the new one. If the child to 212 * be replaced cannot be found, the same node instance is returned. 213 * 214 * @param oldChild the child node to be replaced 215 * @param newChild the replacing child node (must not be <b>null</b>) 216 * @return the new node with the child replaced 217 * @throws IllegalArgumentException if the new child node is <b>null</b> 218 */ 219 public ImmutableNode replaceChild(final ImmutableNode oldChild, 220 final ImmutableNode newChild) 221 { 222 checkChildNode(newChild); 223 final Builder builder = new Builder(children.size(), attributes); 224 boolean foundChild = false; 225 for (final ImmutableNode c : children) 226 { 227 if (c == oldChild) 228 { 229 builder.addChild(newChild); 230 foundChild = true; 231 } 232 else 233 { 234 builder.addChild(c); 235 } 236 } 237 238 return foundChild ? createWithBasicProperties(builder) : this; 239 } 240 241 /** 242 * Returns a new {@code ImmutableNode} instance which is a copy of this 243 * object, but with the children replaced by the ones in the passed in 244 * collection. With this method all children can be replaced in a single 245 * step. For the collection the same rules apply as for 246 * {@link Builder#addChildren(Collection)}. 247 * 248 * @param newChildren the collection with the new children (may be 249 * <b>null</b>) 250 * @return the new node with replaced children 251 */ 252 public ImmutableNode replaceChildren(final Collection<ImmutableNode> newChildren) 253 { 254 final Builder builder = new Builder(null, attributes); 255 builder.addChildren(newChildren); 256 return createWithBasicProperties(builder); 257 } 258 259 /** 260 * Returns a new {@code ImmutableNode} instance which is a copy of this 261 * object, but with the specified attribute set to the given value. If an 262 * attribute with this name does not exist, it is created now. Otherwise, 263 * the new value overrides the old one. 264 * 265 * @param name the name of the attribute 266 * @param value the attribute value 267 * @return the new node with this attribute 268 */ 269 public ImmutableNode setAttribute(final String name, final Object value) 270 { 271 final Map<String, Object> newAttrs = new HashMap<>(attributes); 272 newAttrs.put(name, value); 273 return createWithNewAttributes(newAttrs); 274 } 275 276 /** 277 * Returns a new {@code ImmutableNode} instance which is a copy of this 278 * object, but with all attributes added defined by the given map. This 279 * method is analogous to {@link #setAttribute(String, Object)}, but all 280 * attributes in the given map are added. If the map is <b>null</b> or 281 * empty, this method has no effect. 282 * 283 * @param newAttributes the map with attributes to be added 284 * @return the new node with these attributes 285 */ 286 public ImmutableNode setAttributes(final Map<String, ?> newAttributes) 287 { 288 if (newAttributes == null || newAttributes.isEmpty()) 289 { 290 return this; 291 } 292 293 final Map<String, Object> newAttrs = new HashMap<>(attributes); 294 newAttrs.putAll(newAttributes); 295 return createWithNewAttributes(newAttrs); 296 } 297 298 /** 299 * Returns a new {@code ImmutableNode} instance which is a copy of this 300 * object, but with the specified attribute removed. If there is no 301 * attribute with the given name, the same node instance is returned. 302 * 303 * @param name the name of the attribute 304 * @return the new node without this attribute 305 */ 306 public ImmutableNode removeAttribute(final String name) 307 { 308 final Map<String, Object> newAttrs = new HashMap<>(attributes); 309 if (newAttrs.remove(name) != null) 310 { 311 return createWithNewAttributes(newAttrs); 312 } 313 return this; 314 } 315 316 /** 317 * Initializes the given builder with basic properties (node name and value) 318 * and returns the newly created node. This is a helper method for updating 319 * a node when only children or attributes are affected. 320 * 321 * @param builder the already prepared builder 322 * @return the newly created node 323 */ 324 private ImmutableNode createWithBasicProperties(final Builder builder) 325 { 326 return builder.name(nodeName).value(value).create(); 327 } 328 329 /** 330 * Creates a new {@code ImmutableNode} instance with the same properties as 331 * this object, but with the given new attributes. 332 * 333 * @param newAttrs the new attributes 334 * @return the new node instance 335 */ 336 private ImmutableNode createWithNewAttributes(final Map<String, Object> newAttrs) 337 { 338 return createWithBasicProperties(new Builder(children, null) 339 .addAttributes(newAttrs)); 340 } 341 342 /** 343 * Checks whether the given child node is not null. This check is done at 344 * multiple places to ensure that newly added child nodes are always 345 * defined. 346 * 347 * @param child the child node to be checked 348 * @throws IllegalArgumentException if the child node is <b>null</b> 349 */ 350 private static void checkChildNode(final ImmutableNode child) 351 { 352 if (child == null) 353 { 354 throw new IllegalArgumentException("Child node must not be null!"); 355 } 356 } 357 358 /** 359 * <p> 360 * A <em>builder</em> class for creating instances of {@code ImmutableNode}. 361 * </p> 362 * <p> 363 * This class can be used to set all properties of an immutable node 364 * instance. Eventually call the {@code create()} method to obtain the 365 * resulting instance. 366 * </p> 367 * <p> 368 * Implementation note: This class is not thread-safe. It is intended to be 369 * used to define a single node instance only. 370 * </p> 371 */ 372 public static final class Builder 373 { 374 /** The direct list of children of the new node. */ 375 private final List<ImmutableNode> directChildren; 376 377 /** The direct map of attributes of the new node. */ 378 private final Map<String, Object> directAttributes; 379 380 /** 381 * A list for the children of the new node. This list is populated by 382 * the {@code addChild()} method. 383 */ 384 private List<ImmutableNode> children; 385 386 /** 387 * A map for storing the attributes of the new node. This map is 388 * populated by {@code addAttribute()}. 389 */ 390 private Map<String, Object> attributes; 391 392 /** The name of the node. */ 393 private String name; 394 395 /** The value of the node. */ 396 private Object value; 397 398 /** 399 * Creates a new instance of {@code Builder} which does not contain any 400 * property definitions yet. 401 */ 402 public Builder() 403 { 404 this(null, null); 405 } 406 407 /** 408 * Creates a new instance of {@code Builder} and sets the number of 409 * expected child nodes. Using this constructor helps the class to 410 * create a properly sized list for the child nodes to be added. 411 * 412 * @param childCount the number of child nodes 413 */ 414 public Builder(final int childCount) 415 { 416 this(); 417 initChildrenCollection(childCount); 418 } 419 420 /** 421 * Creates a new instance of {@code Builder} and initializes the 422 * children and attributes of the new node. This constructor is used 423 * internally by the {@code ImmutableNode} class for creating instances 424 * derived from another node. The passed in collections are passed 425 * directly to the newly created instance; thus they already need to be 426 * immutable. (Background is that the creation of intermediate objects 427 * is to be avoided.) 428 * 429 * @param dirChildren the children of the new node 430 * @param dirAttrs the attributes of the new node 431 */ 432 private Builder(final List<ImmutableNode> dirChildren, 433 final Map<String, Object> dirAttrs) 434 { 435 directChildren = dirChildren; 436 directAttributes = dirAttrs; 437 } 438 439 /** 440 * Creates a new instance of {@code Builder} and initializes the 441 * attributes of the new node and prepares the collection for the 442 * children. This constructor is used internally by methods of 443 * {@code ImmutableNode} which update the node and change the children. 444 * The new number of child nodes can be passed so that the collection 445 * for the new children can be created with an appropriate size. 446 * 447 * @param childCount the expected number of new children 448 * @param dirAttrs the attributes of the new node 449 */ 450 private Builder(final int childCount, final Map<String, Object> dirAttrs) 451 { 452 this(null, dirAttrs); 453 initChildrenCollection(childCount); 454 } 455 456 /** 457 * Sets the name of the node to be created. 458 * 459 * @param n the node name 460 * @return a reference to this object for method chaining 461 */ 462 public Builder name(final String n) 463 { 464 name = n; 465 return this; 466 } 467 468 /** 469 * Sets the value of the node to be created. 470 * 471 * @param v the value 472 * @return a reference to this object for method chaining 473 */ 474 public Builder value(final Object v) 475 { 476 value = v; 477 return this; 478 } 479 480 /** 481 * Adds a child node to this builder. The passed in node becomes a child 482 * of the newly created node. If it is <b>null</b>, it is ignored. 483 * 484 * @param c the child node (must not be <b>null</b>) 485 * @return a reference to this object for method chaining 486 */ 487 public Builder addChild(final ImmutableNode c) 488 { 489 if (c != null) 490 { 491 ensureChildrenExist(); 492 children.add(c); 493 } 494 return this; 495 } 496 497 /** 498 * Adds multiple child nodes to this builder. This method works like 499 * {@link #addChild(ImmutableNode)}, but it allows setting a number of 500 * child nodes at once. 501 * 502 * 503 * @param children a collection with the child nodes to be added 504 * @return a reference to this object for method chaining 505 */ 506 public Builder addChildren(final Collection<? extends ImmutableNode> children) 507 { 508 if (children != null) 509 { 510 ensureChildrenExist(); 511 this.children.addAll(filterNull(children)); 512 } 513 return this; 514 } 515 516 /** 517 * Adds an attribute to this builder. The passed in attribute key and 518 * value are stored in an internal map. If there is already an attribute 519 * with this name, it is overridden. 520 * 521 * @param name the attribute name 522 * @param value the attribute value 523 * @return a reference to this object for method chaining 524 */ 525 public Builder addAttribute(final String name, final Object value) 526 { 527 ensureAttributesExist(); 528 attributes.put(name, value); 529 return this; 530 } 531 532 /** 533 * Adds all attributes of the given map to this builder. This method 534 * works like {@link #addAttribute(String, Object)}, but it allows 535 * setting multiple attributes at once. 536 * 537 * @param attrs the map with attributes to be added (may be <b>null</b> 538 * @return a reference to this object for method chaining 539 */ 540 public Builder addAttributes(final Map<String, ?> attrs) 541 { 542 if (attrs != null) 543 { 544 ensureAttributesExist(); 545 attributes.putAll(attrs); 546 } 547 return this; 548 } 549 550 /** 551 * Creates a new {@code ImmutableNode} instance based on the properties 552 * set for this builder. 553 * 554 * @return the newly created {@code ImmutableNode} 555 */ 556 public ImmutableNode create() 557 { 558 final ImmutableNode newNode = new ImmutableNode(this); 559 children = null; 560 attributes = null; 561 return newNode; 562 } 563 564 /** 565 * Creates a list with the children of the newly created node. The list 566 * returned here is always immutable. It depends on the way this builder 567 * was populated. 568 * 569 * @return the list with the children of the new node 570 */ 571 List<ImmutableNode> createChildren() 572 { 573 if (directChildren != null) 574 { 575 return directChildren; 576 } 577 if (children != null) 578 { 579 return Collections.unmodifiableList(children); 580 } 581 return Collections.emptyList(); 582 } 583 584 /** 585 * Creates a map with the attributes of the newly created node. This is 586 * an immutable map. If direct attributes were set, they are returned. 587 * Otherwise an unmodifiable map from the attributes passed to this 588 * builder is constructed. 589 * 590 * @return a map with the attributes for the new node 591 */ 592 private Map<String, Object> createAttributes() 593 { 594 if (directAttributes != null) 595 { 596 return directAttributes; 597 } 598 if (attributes != null) 599 { 600 return Collections.unmodifiableMap(attributes); 601 } 602 return Collections.emptyMap(); 603 } 604 605 /** 606 * Ensures that the collection for the child nodes exists. It is created 607 * on demand. 608 */ 609 private void ensureChildrenExist() 610 { 611 if (children == null) 612 { 613 children = new LinkedList<>(); 614 } 615 } 616 617 /** 618 * Ensures that the map for the attributes exists. It is created on 619 * demand. 620 */ 621 private void ensureAttributesExist() 622 { 623 if (attributes == null) 624 { 625 attributes = new HashMap<>(); 626 } 627 } 628 629 /** 630 * Creates the collection for child nodes based on the expected number 631 * of children. 632 * 633 * @param childCount the expected number of new children 634 */ 635 private void initChildrenCollection(final int childCount) 636 { 637 if (childCount > 0) 638 { 639 children = new ArrayList<>(childCount); 640 } 641 } 642 643 /** 644 * Filters null entries from the passed in collection with child nodes. 645 * 646 * 647 * @param children the collection to be filtered 648 * @return the collection with null entries removed 649 */ 650 private static Collection<? extends ImmutableNode> filterNull( 651 final Collection<? extends ImmutableNode> children) 652 { 653 final List<ImmutableNode> result = 654 new ArrayList<>(children.size()); 655 for (final ImmutableNode c : children) 656 { 657 if (c != null) 658 { 659 result.add(c); 660 } 661 } 662 return result; 663 } 664 } 665 666 @Override 667 public String toString() 668 { 669 return super.toString() + "(" + nodeName + ")"; 670 } 671}