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; 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 * <p> 049 * A <em>builder</em> class for creating instances of {@code ImmutableNode}. 050 * </p> 051 * <p> 052 * This class can be used to set all properties of an immutable node instance. Eventually call the {@code create()} 053 * method to obtain the resulting instance. 054 * </p> 055 * <p> 056 * Implementation note: This class is not thread-safe. It is intended to be used to define a single node instance only. 057 * </p> 058 */ 059 public static final class Builder { 060 /** 061 * Filters null entries from the passed in collection with child nodes. 062 * 063 * 064 * @param children the collection to be filtered 065 * @return the collection with null entries removed 066 */ 067 private static Collection<? extends ImmutableNode> filterNull(final Collection<? extends ImmutableNode> children) { 068 final List<ImmutableNode> result = new ArrayList<>(children.size()); 069 children.forEach(c -> { 070 if (c != null) { 071 result.add(c); 072 } 073 }); 074 return result; 075 } 076 077 /** The direct list of children of the new node. */ 078 private final List<ImmutableNode> directChildren; 079 080 /** The direct map of attributes of the new node. */ 081 private final Map<String, Object> directAttributes; 082 083 /** 084 * A list for the children of the new node. This list is populated by the {@code addChild()} method. 085 */ 086 private List<ImmutableNode> children; 087 088 /** 089 * A map for storing the attributes of the new node. This map is populated by {@code addAttribute()}. 090 */ 091 private Map<String, Object> attributes; 092 093 /** The name of the node. */ 094 private String name; 095 096 /** The value of the node. */ 097 private Object value; 098 099 /** 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 <b>null</b> 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 * <b>null</b>, it is ignored. 177 * 178 * @param c the child node (must not be <b>null</b>) 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 <b>null</b> 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 <b>null</b>) 342 * @return the new node with the child node added 343 * @throws IllegalArgumentException if the child node is <b>null</b> 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 * 397 * @return a list with the child nodes 398 */ 399 public List<ImmutableNode> getChildren(final String name) { 400 if (name == null) { 401 return new ArrayList<>(); 402 } 403 return children.stream().filter(in -> name.equals(in.getNodeName())).collect(Collectors.toList()); 404 } 405 406 /** 407 * Gets the name of this node. 408 * 409 * @return the name of this node 410 */ 411 public String getNodeName() { 412 return nodeName; 413 } 414 415 /** 416 * Gets the value of this node. 417 * 418 * @return the value of this node 419 */ 420 public Object getValue() { 421 return value; 422 } 423 424 /** 425 * @return An iterator of {@link #children child nodes.} 426 * @since 2.8.0 427 */ 428 @Override 429 public Iterator<ImmutableNode> iterator() { 430 return children.iterator(); 431 } 432 433 /** 434 * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the specified attribute 435 * removed. If there is no attribute with the given name, the same node instance is returned. 436 * 437 * @param name the name of the attribute 438 * @return the new node without this attribute 439 */ 440 public ImmutableNode removeAttribute(final String name) { 441 final Map<String, Object> newAttrs = new HashMap<>(attributes); 442 if (newAttrs.remove(name) != null) { 443 return createWithNewAttributes(newAttrs); 444 } 445 return this; 446 } 447 448 /** 449 * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the given child node removed. 450 * If the child node does not belong to this node, the same node instance is returned. 451 * 452 * @param child the child node to be removed 453 * @return the new node with the child node removed 454 */ 455 public ImmutableNode removeChild(final ImmutableNode child) { 456 // use same size of children in case the child does not exist 457 final Builder builder = new Builder(children.size(), attributes); 458 boolean foundChild = false; 459 for (final ImmutableNode c : children) { 460 if (c == child) { 461 foundChild = true; 462 } else { 463 builder.addChild(c); 464 } 465 } 466 467 return foundChild ? createWithBasicProperties(builder) : this; 468 } 469 470 /** 471 * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the given child replaced by the 472 * new one. If the child to be replaced cannot be found, the same node instance is returned. 473 * 474 * @param oldChild the child node to be replaced 475 * @param newChild the replacing child node (must not be <b>null</b>) 476 * @return the new node with the child replaced 477 * @throws IllegalArgumentException if the new child node is <b>null</b> 478 */ 479 public ImmutableNode replaceChild(final ImmutableNode oldChild, final ImmutableNode newChild) { 480 checkChildNode(newChild); 481 final Builder builder = new Builder(children.size(), attributes); 482 boolean foundChild = false; 483 for (final ImmutableNode c : children) { 484 if (c == oldChild) { 485 builder.addChild(newChild); 486 foundChild = true; 487 } else { 488 builder.addChild(c); 489 } 490 } 491 492 return foundChild ? createWithBasicProperties(builder) : this; 493 } 494 495 /** 496 * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the children replaced by the 497 * ones in the passed in collection. With this method all children can be replaced in a single step. For the collection 498 * the same rules apply as for {@link Builder#addChildren(Collection)}. 499 * 500 * @param newChildren the collection with the new children (may be <b>null</b>) 501 * @return the new node with replaced children 502 */ 503 public ImmutableNode replaceChildren(final Collection<ImmutableNode> newChildren) { 504 final Builder builder = new Builder(null, attributes); 505 builder.addChildren(newChildren); 506 return createWithBasicProperties(builder); 507 } 508 509 /** 510 * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with the specified attribute set to 511 * the given value. If an attribute with this name does not exist, it is created now. Otherwise, the new value overrides 512 * the old one. 513 * 514 * @param name the name of the attribute 515 * @param value the attribute value 516 * @return the new node with this attribute 517 */ 518 public ImmutableNode setAttribute(final String name, final Object value) { 519 final Map<String, Object> newAttrs = new HashMap<>(attributes); 520 newAttrs.put(name, value); 521 return createWithNewAttributes(newAttrs); 522 } 523 524 /** 525 * Returns a new {@code ImmutableNode} instance which is a copy of this object, but with all attributes added defined by 526 * the given map. This method is analogous to {@link #setAttribute(String, Object)}, but all attributes in the given map 527 * are added. If the map is <b>null</b> or empty, this method has no effect. 528 * 529 * @param newAttributes the map with attributes to be added 530 * @return the new node with these attributes 531 */ 532 public ImmutableNode setAttributes(final Map<String, ?> newAttributes) { 533 if (newAttributes == null || newAttributes.isEmpty()) { 534 return this; 535 } 536 537 final Map<String, Object> newAttrs = new HashMap<>(attributes); 538 newAttrs.putAll(newAttributes); 539 return createWithNewAttributes(newAttrs); 540 } 541 542 /** 543 * Creates a new {@code ImmutableNode} instance which is a copy of this object with the name changed to the passed in 544 * value. 545 * 546 * @param name the name of the newly created node 547 * @return the new node with the changed name 548 */ 549 public ImmutableNode setName(final String name) { 550 return new Builder(children, attributes).name(name).value(value).create(); 551 } 552 553 /** 554 * Creates a new {@code ImmutableNode} instance which is a copy of this object with the value changed to the passed in 555 * value. 556 * 557 * @param newValue the value of the newly created node 558 * @return the new node with the changed value 559 */ 560 public ImmutableNode setValue(final Object newValue) { 561 return new Builder(children, attributes).name(nodeName).value(newValue).create(); 562 } 563 564 /** 565 * Returns a sequential {@code Stream} with this node as its source. 566 * 567 * @return a sequential {@code Stream} over the elements in this node. 568 * @since 2.9.0 569 */ 570 public Stream<ImmutableNode> stream() { 571 return StreamSupport.stream(spliterator(), false); 572 } 573 574 @Override 575 public String toString() { 576 return super.toString() + "(" + nodeName + ")"; 577 } 578}