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