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.configuration.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 028import org.apache.commons.configuration.ConfigurationRuntimeException; 029 030/** 031 * <p> 032 * A default implementation of the {@code ConfigurationNode} interface. 033 * </p> 034 * 035 * @since 1.3 036 * @author <a 037 * href="http://commons.apache.org/configuration/team-list.html">Commons 038 * Configuration team</a> 039 * @version $Id: DefaultConfigurationNode.java 1301991 2012-03-17 20:18:02Z sebb $ 040 */ 041public class DefaultConfigurationNode implements ConfigurationNode, Cloneable 042{ 043 /** Stores the children of this node. */ 044 private SubNodes children; 045 046 /** Stores the attributes of this node. */ 047 private SubNodes attributes; 048 049 /** Stores a reference to this node's parent. */ 050 private ConfigurationNode parent; 051 052 /** Stores the value of this node. */ 053 private Object value; 054 055 /** Stores the reference. */ 056 private Object reference; 057 058 /** Stores the name of this node. */ 059 private String name; 060 061 /** Stores a flag if this is an attribute. */ 062 private boolean attribute; 063 064 /** 065 * Creates a new uninitialized instance of {@code DefaultConfigurationNode}. 066 */ 067 public DefaultConfigurationNode() 068 { 069 this(null); 070 } 071 072 /** 073 * Creates a new instance of {@code DefaultConfigurationNode} and 074 * initializes it with the node name. 075 * 076 * @param name the name of this node 077 */ 078 public DefaultConfigurationNode(String name) 079 { 080 this(name, null); 081 } 082 083 /** 084 * Creates a new instance of {@code DefaultConfigurationNode} and 085 * initializes it with the name and a value. 086 * 087 * @param name the node's name 088 * @param value the node's value 089 */ 090 public DefaultConfigurationNode(String name, Object value) 091 { 092 setName(name); 093 setValue(value); 094 initSubNodes(); 095 } 096 097 /** 098 * Returns the name of this node. 099 * 100 * @return the name of this node 101 */ 102 public String getName() 103 { 104 return name; 105 } 106 107 /** 108 * Sets the name of this node. 109 * 110 * @param name the new name 111 */ 112 public void setName(String name) 113 { 114 checkState(); 115 this.name = name; 116 } 117 118 /** 119 * Returns the value of this node. 120 * 121 * @return the value of this node 122 */ 123 public Object getValue() 124 { 125 return value; 126 } 127 128 /** 129 * Sets the value of this node. 130 * 131 * @param val the value of this node 132 */ 133 public void setValue(Object val) 134 { 135 value = val; 136 } 137 138 /** 139 * Returns the reference. 140 * 141 * @return the reference 142 */ 143 public Object getReference() 144 { 145 return reference; 146 } 147 148 /** 149 * Sets the reference. 150 * 151 * @param reference the reference object 152 */ 153 public void setReference(Object reference) 154 { 155 this.reference = reference; 156 } 157 158 /** 159 * Returns a reference to this node's parent. 160 * 161 * @return the parent node or <b>null </b> if this is the root 162 */ 163 public ConfigurationNode getParentNode() 164 { 165 return parent; 166 } 167 168 /** 169 * Sets the parent of this node. 170 * 171 * @param parent the parent of this node 172 */ 173 public void setParentNode(ConfigurationNode parent) 174 { 175 this.parent = parent; 176 } 177 178 /** 179 * Adds a new child to this node. 180 * 181 * @param child the new child 182 */ 183 public void addChild(ConfigurationNode child) 184 { 185 children.addNode(child); 186 child.setAttribute(false); 187 child.setParentNode(this); 188 } 189 190 /** 191 * Returns a list with all children of this node. 192 * 193 * @return a list with all child nodes 194 */ 195 public List<ConfigurationNode> getChildren() 196 { 197 return children.getSubNodes(); 198 } 199 200 /** 201 * Returns the number of all children of this node. 202 * 203 * @return the number of all children 204 */ 205 public int getChildrenCount() 206 { 207 return children.getSubNodes().size(); 208 } 209 210 /** 211 * Returns a list of all children with the given name. 212 * 213 * @param name the name; can be <b>null </b>, then all children are returned 214 * @return a list of all children with the given name 215 */ 216 public List<ConfigurationNode> getChildren(String name) 217 { 218 return children.getSubNodes(name); 219 } 220 221 /** 222 * Returns the number of children with the given name. 223 * 224 * @param name the name; can be <b>null </b>, then the number of all 225 * children is returned 226 * @return the number of child nodes with this name 227 */ 228 public int getChildrenCount(String name) 229 { 230 return children.getSubNodes(name).size(); 231 } 232 233 /** 234 * Returns the child node with the given index. 235 * 236 * @param index the index (0-based) 237 * @return the child with this index 238 */ 239 public ConfigurationNode getChild(int index) 240 { 241 return children.getNode(index); 242 } 243 244 /** 245 * Removes the specified child node from this node. 246 * 247 * @param child the node to be removed 248 * @return a flag if a node was removed 249 */ 250 public boolean removeChild(ConfigurationNode child) 251 { 252 return children.removeNode(child); 253 } 254 255 /** 256 * Removes all children with the given name. 257 * 258 * @param childName the name of the children to be removed 259 * @return a flag if at least one child node was removed 260 */ 261 public boolean removeChild(String childName) 262 { 263 return children.removeNodes(childName); 264 } 265 266 /** 267 * Removes all child nodes of this node. 268 */ 269 public void removeChildren() 270 { 271 children.clear(); 272 } 273 274 /** 275 * Checks if this node is an attribute node. 276 * 277 * @return a flag if this is an attribute node 278 */ 279 public boolean isAttribute() 280 { 281 return attribute; 282 } 283 284 /** 285 * Sets the attribute flag. Note: this method can only be called if the node 286 * is not already part of a node hierarchy. 287 * 288 * @param f the attribute flag 289 */ 290 public void setAttribute(boolean f) 291 { 292 checkState(); 293 attribute = f; 294 } 295 296 /** 297 * Adds the specified attribute to this node. 298 * 299 * @param attr the attribute to be added 300 */ 301 public void addAttribute(ConfigurationNode attr) 302 { 303 attributes.addNode(attr); 304 attr.setAttribute(true); 305 attr.setParentNode(this); 306 } 307 308 /** 309 * Returns a list with the attributes of this node. This list contains 310 * {@code DefaultConfigurationNode} objects, too. 311 * 312 * @return the attribute list, never <b>null </b> 313 */ 314 public List<ConfigurationNode> getAttributes() 315 { 316 return attributes.getSubNodes(); 317 } 318 319 /** 320 * Returns the number of attributes contained in this node. 321 * 322 * @return the number of attributes 323 */ 324 public int getAttributeCount() 325 { 326 return attributes.getSubNodes().size(); 327 } 328 329 /** 330 * Returns a list with all attributes of this node with the given name. 331 * 332 * @param name the attribute's name 333 * @return all attributes with this name 334 */ 335 public List<ConfigurationNode> getAttributes(String name) 336 { 337 return attributes.getSubNodes(name); 338 } 339 340 /** 341 * Returns the number of attributes of this node with the given name. 342 * 343 * @param name the name 344 * @return the number of attributes with this name 345 */ 346 public int getAttributeCount(String name) 347 { 348 return getAttributes(name).size(); 349 } 350 351 /** 352 * Removes the specified attribute. 353 * 354 * @param node the attribute node to be removed 355 * @return a flag if the attribute could be removed 356 */ 357 public boolean removeAttribute(ConfigurationNode node) 358 { 359 return attributes.removeNode(node); 360 } 361 362 /** 363 * Removes all attributes with the specified name. 364 * 365 * @param name the name 366 * @return a flag if at least one attribute was removed 367 */ 368 public boolean removeAttribute(String name) 369 { 370 return attributes.removeNodes(name); 371 } 372 373 /** 374 * Returns the attribute with the given index. 375 * 376 * @param index the index (0-based) 377 * @return the attribute with this index 378 */ 379 public ConfigurationNode getAttribute(int index) 380 { 381 return attributes.getNode(index); 382 } 383 384 /** 385 * Removes all attributes of this node. 386 */ 387 public void removeAttributes() 388 { 389 attributes.clear(); 390 } 391 392 /** 393 * Returns a flag if this node is defined. This means that the node contains 394 * some data. 395 * 396 * @return a flag whether this node is defined 397 */ 398 public boolean isDefined() 399 { 400 return getValue() != null || getChildrenCount() > 0 401 || getAttributeCount() > 0; 402 } 403 404 /** 405 * Visits this node and all its sub nodes. 406 * 407 * @param visitor the visitor 408 */ 409 public void visit(ConfigurationNodeVisitor visitor) 410 { 411 if (visitor == null) 412 { 413 throw new IllegalArgumentException("Visitor must not be null!"); 414 } 415 416 if (!visitor.terminate()) 417 { 418 visitor.visitBeforeChildren(this); 419 children.visit(visitor); 420 attributes.visit(visitor); 421 visitor.visitAfterChildren(this); 422 } 423 } 424 425 /** 426 * Creates a copy of this object. This is not a deep copy, the children are 427 * not cloned. 428 * 429 * @return a copy of this object 430 */ 431 @Override 432 public Object clone() 433 { 434 try 435 { 436 DefaultConfigurationNode copy = (DefaultConfigurationNode) super 437 .clone(); 438 copy.initSubNodes(); 439 return copy; 440 } 441 catch (CloneNotSupportedException cex) 442 { 443 // should not happen 444 throw new ConfigurationRuntimeException("Cannot clone " + getClass()); 445 } 446 } 447 448 /** 449 * Checks if a modification of this node is allowed. Some properties of a 450 * node must not be changed when the node has a parent. This method checks 451 * this and throws a runtime exception if necessary. 452 */ 453 protected void checkState() 454 { 455 if (getParentNode() != null) 456 { 457 throw new IllegalStateException( 458 "Node cannot be modified when added to a parent!"); 459 } 460 } 461 462 /** 463 * Creates a {@code SubNodes} instance that is used for storing 464 * either this node's children or attributes. 465 * 466 * @param attributes <b>true</b> if the returned instance is used for 467 * storing attributes, <b>false</b> for storing child nodes 468 * @return the {@code SubNodes} object to use 469 */ 470 protected SubNodes createSubNodes(boolean attributes) 471 { 472 return new SubNodes(); 473 } 474 475 /** 476 * Deals with the reference when a node is removed. This method is called 477 * for each removed child node or attribute. It can be overloaded in sub 478 * classes, for which the reference has a concrete meaning and remove 479 * operations need some update actions. This default implementation is 480 * empty. 481 */ 482 protected void removeReference() 483 { 484 } 485 486 /** 487 * Helper method for initializing the sub nodes objects. 488 */ 489 private void initSubNodes() 490 { 491 children = createSubNodes(false); 492 attributes = createSubNodes(true); 493 } 494 495 /** 496 * An internally used helper class for managing a collection of sub nodes. 497 */ 498 protected static class SubNodes 499 { 500 /** Stores a list for the sub nodes. */ 501 private List<ConfigurationNode> nodes; 502 503 /** Stores a map for accessing subnodes by name. */ 504 private Map<String, List<ConfigurationNode>> namedNodes; 505 506 /** 507 * Adds a new sub node. 508 * 509 * @param node the node to add 510 */ 511 public void addNode(ConfigurationNode node) 512 { 513 if (node == null || node.getName() == null) 514 { 515 throw new IllegalArgumentException( 516 "Node to add must have a defined name!"); 517 } 518 node.setParentNode(null); // reset, will later be set 519 520 if (nodes == null) 521 { 522 nodes = new ArrayList<ConfigurationNode>(); 523 namedNodes = new HashMap<String, List<ConfigurationNode>>(); 524 } 525 526 nodes.add(node); 527 List<ConfigurationNode> lst = namedNodes.get(node.getName()); 528 if (lst == null) 529 { 530 lst = new LinkedList<ConfigurationNode>(); 531 namedNodes.put(node.getName(), lst); 532 } 533 lst.add(node); 534 } 535 536 /** 537 * Removes a sub node. 538 * 539 * @param node the node to remove 540 * @return a flag if the node could be removed 541 */ 542 public boolean removeNode(ConfigurationNode node) 543 { 544 if (nodes != null && node != null && nodes.contains(node)) 545 { 546 detachNode(node); 547 nodes.remove(node); 548 549 List<ConfigurationNode> lst = namedNodes.get(node.getName()); 550 if (lst != null) 551 { 552 lst.remove(node); 553 if (lst.isEmpty()) 554 { 555 namedNodes.remove(node.getName()); 556 } 557 } 558 return true; 559 } 560 561 else 562 { 563 return false; 564 } 565 } 566 567 /** 568 * Removes all sub nodes with the given name. 569 * 570 * @param name the name 571 * @return a flag if at least on sub node was removed 572 */ 573 public boolean removeNodes(String name) 574 { 575 if (nodes != null && name != null) 576 { 577 List<ConfigurationNode> lst = namedNodes.remove(name); 578 if (lst != null) 579 { 580 detachNodes(lst); 581 nodes.removeAll(lst); 582 return true; 583 } 584 } 585 return false; 586 } 587 588 /** 589 * Removes all sub nodes. 590 */ 591 public void clear() 592 { 593 if (nodes != null) 594 { 595 detachNodes(nodes); 596 nodes = null; 597 namedNodes = null; 598 } 599 } 600 601 /** 602 * Returns the node with the given index. If this index cannot be found, 603 * an {@code IndexOutOfBoundException} exception will be thrown. 604 * 605 * @param index the index (0-based) 606 * @return the sub node at the specified index 607 */ 608 public ConfigurationNode getNode(int index) 609 { 610 if (nodes == null) 611 { 612 throw new IndexOutOfBoundsException("No sub nodes available!"); 613 } 614 return nodes.get(index); 615 } 616 617 /** 618 * Returns a list with all stored sub nodes. The return value is never 619 * <b>null</b>. 620 * 621 * @return a list with the sub nodes 622 */ 623 public List<ConfigurationNode> getSubNodes() 624 { 625 if (nodes == null) 626 { 627 return Collections.emptyList(); 628 } 629 else 630 { 631 return Collections.unmodifiableList(nodes); 632 } 633 } 634 635 /** 636 * Returns a list of the sub nodes with the given name. The return value 637 * is never <b>null</b>. 638 * 639 * @param name the name; if <b>null</b> is passed, all sub nodes will 640 * be returned 641 * @return all sub nodes with this name 642 */ 643 public List<ConfigurationNode> getSubNodes(String name) 644 { 645 if (name == null) 646 { 647 return getSubNodes(); 648 } 649 650 List<ConfigurationNode> result; 651 if (nodes == null) 652 { 653 result = null; 654 } 655 else 656 { 657 result = namedNodes.get(name); 658 } 659 660 if (result == null) 661 { 662 return Collections.emptyList(); 663 } 664 else 665 { 666 return Collections.unmodifiableList(result); 667 } 668 } 669 670 /** 671 * Let the passed in visitor visit all sub nodes. 672 * 673 * @param visitor the visitor 674 */ 675 public void visit(ConfigurationNodeVisitor visitor) 676 { 677 if (nodes != null) 678 { 679 for (Iterator<ConfigurationNode> it = nodes.iterator(); it.hasNext() 680 && !visitor.terminate();) 681 { 682 it.next().visit(visitor); 683 } 684 } 685 } 686 687 /** 688 * This method is called whenever a sub node is removed from this 689 * object. It ensures that the removed node's parent is reset and its 690 * {@code removeReference()} method gets called. 691 * 692 * @param subNode the node to be removed 693 */ 694 protected void detachNode(ConfigurationNode subNode) 695 { 696 subNode.setParentNode(null); 697 if (subNode instanceof DefaultConfigurationNode) 698 { 699 ((DefaultConfigurationNode) subNode).removeReference(); 700 } 701 } 702 703 /** 704 * Detaches a list of sub nodes. This method calls 705 * {@code detachNode()} for each node contained in the list. 706 * 707 * @param subNodes the list with nodes to be detached 708 */ 709 protected void detachNodes(Collection<? extends ConfigurationNode> subNodes) 710 { 711 for (ConfigurationNode nd : subNodes) 712 { 713 detachNode(nd); 714 } 715 } 716 } 717}