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