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