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