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 018 package org.apache.commons.configuration; 019 020 import java.io.Serializable; 021 import java.util.ArrayList; 022 import java.util.Collection; 023 import java.util.Iterator; 024 import java.util.LinkedList; 025 import java.util.List; 026 import java.util.Set; 027 import java.util.Stack; 028 029 import org.apache.commons.collections.iterators.SingletonIterator; 030 import org.apache.commons.collections.set.ListOrderedSet; 031 import org.apache.commons.configuration.event.ConfigurationEvent; 032 import org.apache.commons.configuration.event.ConfigurationListener; 033 import org.apache.commons.configuration.tree.ConfigurationNode; 034 import org.apache.commons.configuration.tree.ConfigurationNodeVisitorAdapter; 035 import org.apache.commons.configuration.tree.DefaultConfigurationNode; 036 import org.apache.commons.configuration.tree.DefaultExpressionEngine; 037 import org.apache.commons.configuration.tree.ExpressionEngine; 038 import org.apache.commons.configuration.tree.NodeAddData; 039 import org.apache.commons.lang.StringUtils; 040 041 /** 042 * <p>A specialized configuration class that extends its base class by the 043 * ability of keeping more structure in the stored properties.</p><p>There 044 * are some sources of configuration data that cannot be stored very well in a 045 * <code>BaseConfiguration</code> object because then their structure is lost. 046 * This is especially true for XML documents. This class can deal with such 047 * structured configuration sources by storing the properties in a tree-like 048 * organization.</p><p>The internal used storage form allows for a more 049 * sophisticated access to single properties. As an example consider the 050 * following XML document:</p><p> 051 * 052 * <pre> 053 * <database> 054 * <tables> 055 * <table> 056 * <name>users</name> 057 * <fields> 058 * <field> 059 * <name>lid</name> 060 * <type>long</name> 061 * </field> 062 * <field> 063 * <name>usrName</name> 064 * <type>java.lang.String</type> 065 * </field> 066 * ... 067 * </fields> 068 * </table> 069 * <table> 070 * <name>documents</name> 071 * <fields> 072 * <field> 073 * <name>docid</name> 074 * <type>long</type> 075 * </field> 076 * ... 077 * </fields> 078 * </table> 079 * ... 080 * </tables> 081 * </database> 082 * </pre> 083 * 084 * </p><p>If this document is parsed and stored in a 085 * <code>HierarchicalConfiguration</code> object (which can be done by one of 086 * the sub classes), there are enhanced possibilities of accessing properties. 087 * The keys for querying information can contain indices that select a certain 088 * element if there are multiple hits.</p><p>For instance the key 089 * <code>tables.table(0).name</code> can be used to find out the name of the 090 * first table. In opposite <code>tables.table.name</code> would return a 091 * collection with the names of all available tables. Similarly the key 092 * <code>tables.table(1).fields.field.name</code> returns a collection with 093 * the names of all fields of the second table. If another index is added after 094 * the <code>field</code> element, a single field can be accessed: 095 * <code>tables.table(1).fields.field(0).name</code>.</p><p>There is a 096 * <code>getMaxIndex()</code> method that returns the maximum allowed index 097 * that can be added to a given property key. This method can be used to iterate 098 * over all values defined for a certain property.</p> 099 * <p>Since the 1.3 release of <em>Commons Configuration</em> hierarchical 100 * configurations support an <em>expression engine</em>. This expression engine 101 * is responsible for evaluating the passed in configuration keys and map them 102 * to the stored properties. The examples above are valid for the default 103 * expression engine, which is used when a new <code>HierarchicalConfiguration</code> 104 * instance is created. With the <code>setExpressionEngine()</code> method a 105 * different expression engine can be set. For instance with 106 * <code>{@link org.apache.commons.configuration.tree.xpath.XPathExpressionEngine}</code> 107 * there is an expression engine available that supports configuration keys in 108 * XPATH syntax.</p> 109 * <p>In addition to the events common for all configuration classes hierarchical 110 * configurations support some more events that correspond to some specific 111 * methods and features: 112 * <dl><dt><em>EVENT_ADD_NODES</em></dt><dd>The <code>addNodes()</code> method 113 * was called; the event object contains the key, to which the nodes were added, 114 * and a collection with the new nodes as value.</dd> 115 * <dt><em>EVENT_CLEAR_TREE</em></dt><dd>The <code>clearTree()</code> method was 116 * called; the event object stores the key of the removed sub tree.</dd> 117 * <dt><em>EVENT_SUBNODE_CHANGED</em></dt><dd>A <code>SubnodeConfiguration</code> 118 * that was created from this configuration has been changed. The value property 119 * of the event object contains the original event object as it was sent by the 120 * subnode configuration.</dd></dl></p> 121 * <p><em>Note:</em>Configuration objects of this type can be read concurrently 122 * by multiple threads. However if one of these threads modifies the object, 123 * synchronization has to be performed manually.</p> 124 * 125 * @author Oliver Heger 126 * @version $Id: HierarchicalConfiguration.java 722238 2008-12-01 21:28:31Z oheger $ 127 */ 128 public class HierarchicalConfiguration extends AbstractConfiguration implements Serializable, Cloneable 129 { 130 /** 131 * Constant for the clear tree event. 132 * @since 1.3 133 */ 134 public static final int EVENT_CLEAR_TREE = 10; 135 136 /** 137 * Constant for the add nodes event. 138 * @since 1.3 139 */ 140 public static final int EVENT_ADD_NODES = 11; 141 142 /** 143 * Constant for the subnode configuration modified event. 144 * @since 1.5 145 */ 146 public static final int EVENT_SUBNODE_CHANGED = 12; 147 148 /** 149 * The serial version UID. 150 */ 151 private static final long serialVersionUID = 3373812230395363192L; 152 153 /** Stores the default expression engine to be used for new objects.*/ 154 private static ExpressionEngine defaultExpressionEngine; 155 156 /** Stores the root node of this configuration. This field is required for 157 * backwards compatibility only. 158 */ 159 private Node root; 160 161 /** Stores the root configuration node.*/ 162 private ConfigurationNode rootNode; 163 164 /** Stores the expression engine for this instance.*/ 165 private transient ExpressionEngine expressionEngine; 166 167 /** 168 * Creates a new instance of <code>HierarchicalConfiguration</code>. 169 */ 170 public HierarchicalConfiguration() 171 { 172 setRootNode(new Node()); 173 } 174 175 /** 176 * Creates a new instance of <code>HierarchicalConfiguration</code> and 177 * copies all data contained in the specified configuration into the new 178 * one. 179 * 180 * @param c the configuration that is to be copied (if <b>null</b>, this 181 * constructor will behave like the standard constructor) 182 * @since 1.4 183 */ 184 public HierarchicalConfiguration(HierarchicalConfiguration c) 185 { 186 this(); 187 if (c != null) 188 { 189 CloneVisitor visitor = new CloneVisitor(); 190 c.getRootNode().visit(visitor); 191 setRootNode(visitor.getClone()); 192 } 193 } 194 195 /** 196 * Returns the root node of this hierarchical configuration. This method 197 * exists for backwards compatibility only. New code should use the 198 * <code>{@link #getRootNode()}</code> method instead, which operates on 199 * the preferred data type <code>ConfigurationNode</code>. 200 * 201 * @return the root node 202 */ 203 public Node getRoot() 204 { 205 if (root == null && rootNode != null) 206 { 207 // Dynamically create a snapshot of the root node 208 return new Node(rootNode); 209 } 210 211 return root; 212 } 213 214 /** 215 * Sets the root node of this hierarchical configuration. This method 216 * exists for backwards compatibility only. New code should use the 217 * <code>{@link #setRootNode(ConfigurationNode)}</code> method instead, 218 * which operates on the preferred data type <code>ConfigurationNode</code>. 219 * 220 * @param node the root node 221 */ 222 public void setRoot(Node node) 223 { 224 if (node == null) 225 { 226 throw new IllegalArgumentException("Root node must not be null!"); 227 } 228 root = node; 229 rootNode = null; 230 } 231 232 /** 233 * Returns the root node of this hierarchical configuration. 234 * 235 * @return the root node 236 * @since 1.3 237 */ 238 public ConfigurationNode getRootNode() 239 { 240 return (rootNode != null) ? rootNode : root; 241 } 242 243 /** 244 * Sets the root node of this hierarchical configuration. 245 * 246 * @param rootNode the root node 247 * @since 1.3 248 */ 249 public void setRootNode(ConfigurationNode rootNode) 250 { 251 if (rootNode == null) 252 { 253 throw new IllegalArgumentException("Root node must not be null!"); 254 } 255 this.rootNode = rootNode; 256 257 // For backward compatibility also set the old root field. 258 root = (rootNode instanceof Node) ? (Node) rootNode : null; 259 } 260 261 /** 262 * Returns the default expression engine. 263 * 264 * @return the default expression engine 265 * @since 1.3 266 */ 267 public static synchronized ExpressionEngine getDefaultExpressionEngine() 268 { 269 if (defaultExpressionEngine == null) 270 { 271 defaultExpressionEngine = new DefaultExpressionEngine(); 272 } 273 return defaultExpressionEngine; 274 } 275 276 /** 277 * Sets the default expression engine. This expression engine will be used 278 * if no specific engine was set for an instance. It is shared between all 279 * hierarchical configuration instances. So modifying its properties will 280 * impact all instances, for which no specific engine is set. 281 * 282 * @param engine the new default expression engine 283 * @since 1.3 284 */ 285 public static synchronized void setDefaultExpressionEngine(ExpressionEngine engine) 286 { 287 if (engine == null) 288 { 289 throw new IllegalArgumentException( 290 "Default expression engine must not be null!"); 291 } 292 defaultExpressionEngine = engine; 293 } 294 295 /** 296 * Returns the expression engine used by this configuration. This method 297 * will never return <b>null</b>; if no specific expression engine was set, 298 * the default expression engine will be returned. 299 * 300 * @return the current expression engine 301 * @since 1.3 302 */ 303 public ExpressionEngine getExpressionEngine() 304 { 305 return (expressionEngine != null) ? expressionEngine 306 : getDefaultExpressionEngine(); 307 } 308 309 /** 310 * Sets the expression engine to be used by this configuration. All property 311 * keys this configuration has to deal with will be interpreted by this 312 * engine. 313 * 314 * @param expressionEngine the new expression engine; can be <b>null</b>, 315 * then the default expression engine will be used 316 * @since 1.3 317 */ 318 public void setExpressionEngine(ExpressionEngine expressionEngine) 319 { 320 this.expressionEngine = expressionEngine; 321 } 322 323 /** 324 * Fetches the specified property. This task is delegated to the associated 325 * expression engine. 326 * 327 * @param key the key to be looked up 328 * @return the found value 329 */ 330 public Object getProperty(String key) 331 { 332 List nodes = fetchNodeList(key); 333 334 if (nodes.size() == 0) 335 { 336 return null; 337 } 338 else 339 { 340 List list = new ArrayList(); 341 for (Iterator it = nodes.iterator(); it.hasNext();) 342 { 343 ConfigurationNode node = (ConfigurationNode) it.next(); 344 if (node.getValue() != null) 345 { 346 list.add(node.getValue()); 347 } 348 } 349 350 if (list.size() < 1) 351 { 352 return null; 353 } 354 else 355 { 356 return (list.size() == 1) ? list.get(0) : list; 357 } 358 } 359 } 360 361 /** 362 * Adds the property with the specified key. This task will be delegated to 363 * the associated <code>ExpressionEngine</code>, so the passed in key 364 * must match the requirements of this implementation. 365 * 366 * @param key the key of the new property 367 * @param obj the value of the new property 368 */ 369 protected void addPropertyDirect(String key, Object obj) 370 { 371 NodeAddData data = getExpressionEngine().prepareAdd(getRootNode(), key); 372 ConfigurationNode node = processNodeAddData(data); 373 node.setValue(obj); 374 } 375 376 /** 377 * Adds a collection of nodes at the specified position of the configuration 378 * tree. This method works similar to <code>addProperty()</code>, but 379 * instead of a single property a whole collection of nodes can be added - 380 * and thus complete configuration sub trees. E.g. with this method it is 381 * possible to add parts of another <code>HierarchicalConfiguration</code> 382 * object to this object. (However be aware that a 383 * <code>ConfigurationNode</code> object can only belong to a single 384 * configuration. So if nodes from one configuration are directly added to 385 * another one using this method, the structure of the source configuration 386 * will be broken. In this case you should clone the nodes to be added 387 * before calling <code>addNodes()</code>.) If the passed in key refers to 388 * an existing and unique node, the new nodes are added to this node. 389 * Otherwise a new node will be created at the specified position in the 390 * hierarchy. 391 * 392 * @param key the key where the nodes are to be added; can be <b>null </b>, 393 * then they are added to the root node 394 * @param nodes a collection with the <code>Node</code> objects to be 395 * added 396 */ 397 public void addNodes(String key, Collection nodes) 398 { 399 if (nodes == null || nodes.isEmpty()) 400 { 401 return; 402 } 403 404 fireEvent(EVENT_ADD_NODES, key, nodes, true); 405 ConfigurationNode parent; 406 List target = fetchNodeList(key); 407 if (target.size() == 1) 408 { 409 // existing unique key 410 parent = (ConfigurationNode) target.get(0); 411 } 412 else 413 { 414 // otherwise perform an add operation 415 parent = processNodeAddData(getExpressionEngine().prepareAdd( 416 getRootNode(), key)); 417 } 418 419 if (parent.isAttribute()) 420 { 421 throw new IllegalArgumentException( 422 "Cannot add nodes to an attribute node!"); 423 } 424 425 for (Iterator it = nodes.iterator(); it.hasNext();) 426 { 427 ConfigurationNode child = (ConfigurationNode) it.next(); 428 if (child.isAttribute()) 429 { 430 parent.addAttribute(child); 431 } 432 else 433 { 434 parent.addChild(child); 435 } 436 clearReferences(child); 437 } 438 fireEvent(EVENT_ADD_NODES, key, nodes, false); 439 } 440 441 /** 442 * Checks if this configuration is empty. Empty means that there are no keys 443 * with any values, though there can be some (empty) nodes. 444 * 445 * @return a flag if this configuration is empty 446 */ 447 public boolean isEmpty() 448 { 449 return !nodeDefined(getRootNode()); 450 } 451 452 /** 453 * Creates a new <code>Configuration</code> object containing all keys 454 * that start with the specified prefix. This implementation will return a 455 * <code>HierarchicalConfiguration</code> object so that the structure of 456 * the keys will be saved. The nodes selected by the prefix (it is possible 457 * that multiple nodes are selected) are mapped to the root node of the 458 * returned configuration, i.e. their children and attributes will become 459 * children and attributes of the new root node. However a value of the root 460 * node is only set if exactly one of the selected nodes contain a value (if 461 * multiple nodes have a value, there is simply no way to decide how these 462 * values are merged together). Note that the returned 463 * <code>Configuration</code> object is not connected to its source 464 * configuration: updates on the source configuration are not reflected in 465 * the subset and vice versa. 466 * 467 * @param prefix the prefix of the keys for the subset 468 * @return a new configuration object representing the selected subset 469 */ 470 public Configuration subset(String prefix) 471 { 472 Collection nodes = fetchNodeList(prefix); 473 if (nodes.isEmpty()) 474 { 475 return new HierarchicalConfiguration(); 476 } 477 478 final HierarchicalConfiguration parent = this; 479 HierarchicalConfiguration result = new HierarchicalConfiguration() 480 { 481 // Override interpolate to always interpolate on the parent 482 protected Object interpolate(Object value) 483 { 484 return parent.interpolate(value); 485 } 486 }; 487 CloneVisitor visitor = new CloneVisitor(); 488 489 // Initialize the new root node 490 Object value = null; 491 int valueCount = 0; 492 for (Iterator it = nodes.iterator(); it.hasNext();) 493 { 494 ConfigurationNode nd = (ConfigurationNode) it.next(); 495 if (nd.getValue() != null) 496 { 497 value = nd.getValue(); 498 valueCount++; 499 } 500 nd.visit(visitor); 501 502 for (Iterator it2 = visitor.getClone().getChildren().iterator(); it2 503 .hasNext();) 504 { 505 result.getRootNode().addChild((ConfigurationNode) it2.next()); 506 } 507 for (Iterator it2 = visitor.getClone().getAttributes().iterator(); it2 508 .hasNext();) 509 { 510 result.getRootNode().addAttribute( 511 (ConfigurationNode) it2.next()); 512 } 513 } 514 515 // Determine the value of the new root 516 if (valueCount == 1) 517 { 518 result.getRootNode().setValue(value); 519 } 520 return (result.isEmpty()) ? new HierarchicalConfiguration() : result; 521 } 522 523 /** 524 * <p> 525 * Returns a hierarchical subnode configuration object that wraps the 526 * configuration node specified by the given key. This method provides an 527 * easy means of accessing sub trees of a hierarchical configuration. In the 528 * returned configuration the sub tree can directly be accessed, it becomes 529 * the root node of this configuration. Because of this the passed in key 530 * must select exactly one configuration node; otherwise an 531 * <code>IllegalArgumentException</code> will be thrown. 532 * </p> 533 * <p> 534 * The difference between this method and the 535 * <code>{@link #subset(String)}</code> method is that 536 * <code>subset()</code> supports arbitrary subsets of configuration nodes 537 * while <code>configurationAt()</code> only returns a single sub tree. 538 * Please refer to the documentation of the 539 * <code>SubnodeConfiguration</code> class to obtain further information 540 * about subnode configurations and when they should be used. 541 * </p> 542 * <p> 543 * With the <code>supportUpdate</code> flag the behavior of the returned 544 * <code>SubnodeConfiguration</code> regarding updates of its parent 545 * configuration can be determined. A subnode configuration operates on the 546 * same nodes as its parent, so changes at one configuration are normally 547 * directly visible for the other configuration. There are however changes 548 * of the parent configuration, which are not recognized by the subnode 549 * configuration per default. An example for this is a reload operation (for 550 * file-based configurations): Here the complete node set of the parent 551 * configuration is replaced, but the subnode configuration still references 552 * the old nodes. If such changes should be detected by the subnode 553 * configuration, the <code>supportUpdates</code> flag must be set to 554 * <b>true</b>. This causes the subnode configuration to reevaluate the key 555 * used for its creation each time it is accessed. This guarantees that the 556 * subnode configuration always stays in sync with its key, even if the 557 * parent configuration's data significantly changes. If such a change 558 * makes the key invalid - because it now no longer points to exactly one 559 * node -, the subnode configuration is not reconstructed, but keeps its 560 * old data. It is then quasi detached from its parent. 561 * </p> 562 * 563 * @param key the key that selects the sub tree 564 * @param supportUpdates a flag whether the returned subnode configuration 565 * should be able to handle updates of its parent 566 * @return a hierarchical configuration that contains this sub tree 567 * @see SubnodeConfiguration 568 * @since 1.5 569 */ 570 public SubnodeConfiguration configurationAt(String key, 571 boolean supportUpdates) 572 { 573 List nodes = fetchNodeList(key); 574 if (nodes.size() != 1) 575 { 576 throw new IllegalArgumentException( 577 "Passed in key must select exactly one node: " + key); 578 } 579 return supportUpdates ? createSubnodeConfiguration( 580 (ConfigurationNode) nodes.get(0), key) 581 : createSubnodeConfiguration((ConfigurationNode) nodes.get(0)); 582 } 583 584 /** 585 * Returns a hierarchical subnode configuration for the node specified by 586 * the given key. This is a short form for <code>configurationAt(key, 587 * <b>false</b>)</code>. 588 * 589 * @param key the key that selects the sub tree 590 * @return a hierarchical configuration that contains this sub tree 591 * @see SubnodeConfiguration 592 * @since 1.3 593 */ 594 public SubnodeConfiguration configurationAt(String key) 595 { 596 return configurationAt(key, false); 597 } 598 599 /** 600 * Returns a list of sub configurations for all configuration nodes selected 601 * by the given key. This method will evaluate the passed in key (using the 602 * current <code>ExpressionEngine</code>) and then create a subnode 603 * configuration for each returned node (like 604 * <code>{@link #configurationAt(String)}</code>}). This is especially 605 * useful when dealing with list-like structures. As an example consider the 606 * configuration that contains data about database tables and their fields. 607 * If you need access to all fields of a certain table, you can simply do 608 * 609 * <pre> 610 * List fields = config.configurationsAt("tables.table(0).fields.field"); 611 * for(Iterator it = fields.iterator(); it.hasNext();) 612 * { 613 * HierarchicalConfiguration sub = (HierarchicalConfiguration) it.next(); 614 * // now the children and attributes of the field node can be 615 * // directly accessed 616 * String fieldName = sub.getString("name"); 617 * String fieldType = sub.getString("type"); 618 * ... 619 * </pre> 620 * 621 * @param key the key for selecting the desired nodes 622 * @return a list with hierarchical configuration objects; each 623 * configuration represents one of the nodes selected by the passed in key 624 * @since 1.3 625 */ 626 public List configurationsAt(String key) 627 { 628 List nodes = fetchNodeList(key); 629 List configs = new ArrayList(nodes.size()); 630 for (Iterator it = nodes.iterator(); it.hasNext();) 631 { 632 configs.add(createSubnodeConfiguration((ConfigurationNode) it.next())); 633 } 634 return configs; 635 } 636 637 /** 638 * Creates a subnode configuration for the specified node. This method is 639 * called by <code>configurationAt()</code> and 640 * <code>configurationsAt()</code>. 641 * 642 * @param node the node, for which a subnode configuration is to be created 643 * @return the configuration for the given node 644 * @since 1.3 645 */ 646 protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node) 647 { 648 SubnodeConfiguration result = new SubnodeConfiguration(this, node); 649 registerSubnodeConfiguration(result); 650 return result; 651 } 652 653 /** 654 * Creates a new subnode configuration for the specified node and sets its 655 * construction key. A subnode configuration created this way will be aware 656 * of structural changes of its parent. 657 * 658 * @param node the node, for which a subnode configuration is to be created 659 * @param subnodeKey the key used to construct the configuration 660 * @return the configuration for the given node 661 * @since 1.5 662 */ 663 protected SubnodeConfiguration createSubnodeConfiguration( 664 ConfigurationNode node, String subnodeKey) 665 { 666 SubnodeConfiguration result = createSubnodeConfiguration(node); 667 result.setSubnodeKey(subnodeKey); 668 return result; 669 } 670 671 /** 672 * This method is always called when a subnode configuration created from 673 * this configuration has been modified. This implementation transforms the 674 * received event into an event of type <code>EVENT_SUBNODE_CHANGED</code> 675 * and notifies the registered listeners. 676 * 677 * @param event the event describing the change 678 * @since 1.5 679 */ 680 protected void subnodeConfigurationChanged(ConfigurationEvent event) 681 { 682 fireEvent(EVENT_SUBNODE_CHANGED, null, event, event.isBeforeUpdate()); 683 } 684 685 /** 686 * Registers this instance at the given subnode configuration. This 687 * implementation will register a change listener, so that modifications of 688 * the subnode configuration can be tracked. 689 * 690 * @param config the subnode configuration 691 * @since 1.5 692 */ 693 void registerSubnodeConfiguration(SubnodeConfiguration config) 694 { 695 config.addConfigurationListener(new ConfigurationListener() 696 { 697 public void configurationChanged(ConfigurationEvent event) 698 { 699 subnodeConfigurationChanged(event); 700 } 701 }); 702 } 703 704 /** 705 * Checks if the specified key is contained in this configuration. Note that 706 * for this configuration the term "contained" means that the key 707 * has an associated value. If there is a node for this key that has no 708 * value but children (either defined or undefined), this method will still 709 * return <b>false </b>. 710 * 711 * @param key the key to be chekced 712 * @return a flag if this key is contained in this configuration 713 */ 714 public boolean containsKey(String key) 715 { 716 return getProperty(key) != null; 717 } 718 719 /** 720 * Sets the value of the specified property. 721 * 722 * @param key the key of the property to set 723 * @param value the new value of this property 724 */ 725 public void setProperty(String key, Object value) 726 { 727 fireEvent(EVENT_SET_PROPERTY, key, value, true); 728 729 // Update the existing nodes for this property 730 Iterator itNodes = fetchNodeList(key).iterator(); 731 Iterator itValues; 732 if (!isDelimiterParsingDisabled()) 733 { 734 itValues = PropertyConverter.toIterator(value, getListDelimiter()); 735 } 736 else 737 { 738 itValues = new SingletonIterator(value); 739 } 740 741 while (itNodes.hasNext() && itValues.hasNext()) 742 { 743 ((ConfigurationNode) itNodes.next()).setValue(itValues.next()); 744 } 745 746 // Add additional nodes if necessary 747 while (itValues.hasNext()) 748 { 749 addPropertyDirect(key, itValues.next()); 750 } 751 752 // Remove remaining nodes 753 while (itNodes.hasNext()) 754 { 755 clearNode((ConfigurationNode) itNodes.next()); 756 } 757 758 fireEvent(EVENT_SET_PROPERTY, key, value, false); 759 } 760 761 /** 762 * Removes all values of the property with the given name and of keys that 763 * start with this name. So if there is a property with the key 764 * "foo" and a property with the key "foo.bar", a call 765 * of <code>clearTree("foo")</code> would remove both properties. 766 * 767 * @param key the key of the property to be removed 768 */ 769 public void clearTree(String key) 770 { 771 fireEvent(EVENT_CLEAR_TREE, key, null, true); 772 List nodes = fetchNodeList(key); 773 774 for (Iterator it = nodes.iterator(); it.hasNext();) 775 { 776 removeNode((ConfigurationNode) it.next()); 777 } 778 fireEvent(EVENT_CLEAR_TREE, key, nodes, false); 779 } 780 781 /** 782 * Removes the property with the given key. Properties with names that start 783 * with the given key (i.e. properties below the specified key in the 784 * hierarchy) won't be affected. 785 * 786 * @param key the key of the property to be removed 787 */ 788 public void clearProperty(String key) 789 { 790 fireEvent(EVENT_CLEAR_PROPERTY, key, null, true); 791 List nodes = fetchNodeList(key); 792 793 for (Iterator it = nodes.iterator(); it.hasNext();) 794 { 795 clearNode((ConfigurationNode) it.next()); 796 } 797 798 fireEvent(EVENT_CLEAR_PROPERTY, key, null, false); 799 } 800 801 /** 802 * Returns an iterator with all keys defined in this configuration. 803 * Note that the keys returned by this method will not contain any 804 * indices. This means that some structure will be lost.</p> 805 * 806 * @return an iterator with the defined keys in this configuration 807 */ 808 public Iterator getKeys() 809 { 810 DefinedKeysVisitor visitor = new DefinedKeysVisitor(); 811 getRootNode().visit(visitor); 812 813 return visitor.getKeyList().iterator(); 814 } 815 816 /** 817 * Returns an iterator with all keys defined in this configuration that 818 * start with the given prefix. The returned keys will not contain any 819 * indices. 820 * 821 * @param prefix the prefix of the keys to start with 822 * @return an iterator with the found keys 823 */ 824 public Iterator getKeys(String prefix) 825 { 826 DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix); 827 if (containsKey(prefix)) 828 { 829 // explicitly add the prefix 830 visitor.getKeyList().add(prefix); 831 } 832 833 List nodes = fetchNodeList(prefix); 834 835 for (Iterator itNodes = nodes.iterator(); itNodes.hasNext();) 836 { 837 ConfigurationNode node = (ConfigurationNode) itNodes.next(); 838 for (Iterator it = node.getChildren().iterator(); it.hasNext();) 839 { 840 ((ConfigurationNode) it.next()).visit(visitor); 841 } 842 for (Iterator it = node.getAttributes().iterator(); it.hasNext();) 843 { 844 ((ConfigurationNode) it.next()).visit(visitor); 845 } 846 } 847 848 return visitor.getKeyList().iterator(); 849 } 850 851 /** 852 * Returns the maximum defined index for the given key. This is useful if 853 * there are multiple values for this key. They can then be addressed 854 * separately by specifying indices from 0 to the return value of this 855 * method. 856 * 857 * @param key the key to be checked 858 * @return the maximum defined index for this key 859 */ 860 public int getMaxIndex(String key) 861 { 862 return fetchNodeList(key).size() - 1; 863 } 864 865 /** 866 * Creates a copy of this object. This new configuration object will contain 867 * copies of all nodes in the same structure. Registered event listeners 868 * won't be cloned; so they are not registered at the returned copy. 869 * 870 * @return the copy 871 * @since 1.2 872 */ 873 public Object clone() 874 { 875 try 876 { 877 HierarchicalConfiguration copy = (HierarchicalConfiguration) super 878 .clone(); 879 880 // clone the nodes, too 881 CloneVisitor v = new CloneVisitor(); 882 getRootNode().visit(v); 883 copy.setRootNode(v.getClone()); 884 885 return copy; 886 } 887 catch (CloneNotSupportedException cex) 888 { 889 // should not happen 890 throw new ConfigurationRuntimeException(cex); 891 } 892 } 893 894 /** 895 * Returns a configuration with the same content as this configuration, but 896 * with all variables replaced by their actual values. This implementation 897 * is specific for hierarchical configurations. It clones the current 898 * configuration and runs a specialized visitor on the clone, which performs 899 * interpolation on the single configuration nodes. 900 * 901 * @return a configuration with all variables interpolated 902 * @since 1.5 903 */ 904 public Configuration interpolatedConfiguration() 905 { 906 HierarchicalConfiguration c = (HierarchicalConfiguration) clone(); 907 c.getRootNode().visit(new ConfigurationNodeVisitorAdapter() 908 { 909 public void visitAfterChildren(ConfigurationNode node) 910 { 911 node.setValue(interpolate(node.getValue())); 912 } 913 }); 914 return c; 915 } 916 917 /** 918 * Helper method for fetching a list of all nodes that are addressed by the 919 * specified key. 920 * 921 * @param key the key 922 * @return a list with all affected nodes (never <b>null </b>) 923 */ 924 protected List fetchNodeList(String key) 925 { 926 return getExpressionEngine().query(getRootNode(), key); 927 } 928 929 /** 930 * Recursive helper method for fetching a property. This method processes 931 * all facets of a configuration key, traverses the tree of properties and 932 * fetches the the nodes of all matching properties. 933 * 934 * @param keyPart the configuration key iterator 935 * @param node the actual node 936 * @param nodes here the found nodes are stored 937 * @deprecated Property keys are now evaluated by the expression engine 938 * associated with the configuration; this method will no longer be called. 939 * If you want to modify the way properties are looked up, consider 940 * implementing you own <code>ExpressionEngine</code> implementation. 941 */ 942 protected void findPropertyNodes(ConfigurationKey.KeyIterator keyPart, 943 Node node, Collection nodes) 944 { 945 } 946 947 /** 948 * Checks if the specified node is defined. 949 * 950 * @param node the node to be checked 951 * @return a flag if this node is defined 952 * @deprecated Use the method <code>{@link #nodeDefined(ConfigurationNode)}</code> 953 * instead. 954 */ 955 protected boolean nodeDefined(Node node) 956 { 957 return nodeDefined((ConfigurationNode) node); 958 } 959 960 /** 961 * Checks if the specified node is defined. 962 * 963 * @param node the node to be checked 964 * @return a flag if this node is defined 965 */ 966 protected boolean nodeDefined(ConfigurationNode node) 967 { 968 DefinedVisitor visitor = new DefinedVisitor(); 969 node.visit(visitor); 970 return visitor.isDefined(); 971 } 972 973 /** 974 * Removes the specified node from this configuration. This method ensures 975 * that parent nodes that become undefined by this operation are also 976 * removed. 977 * 978 * @param node the node to be removed 979 * @deprecated Use the method <code>{@link #removeNode(ConfigurationNode)}</code> 980 * instead. 981 */ 982 protected void removeNode(Node node) 983 { 984 removeNode((ConfigurationNode) node); 985 } 986 987 /** 988 * Removes the specified node from this configuration. This method ensures 989 * that parent nodes that become undefined by this operation are also 990 * removed. 991 * 992 * @param node the node to be removed 993 */ 994 protected void removeNode(ConfigurationNode node) 995 { 996 ConfigurationNode parent = node.getParentNode(); 997 if (parent != null) 998 { 999 parent.removeChild(node); 1000 if (!nodeDefined(parent)) 1001 { 1002 removeNode(parent); 1003 } 1004 } 1005 } 1006 1007 /** 1008 * Clears the value of the specified node. If the node becomes undefined by 1009 * this operation, it is removed from the hierarchy. 1010 * 1011 * @param node the node to be cleared 1012 * @deprecated Use the method <code>{@link #clearNode(ConfigurationNode)}</code> 1013 * instead 1014 */ 1015 protected void clearNode(Node node) 1016 { 1017 clearNode((ConfigurationNode) node); 1018 } 1019 1020 /** 1021 * Clears the value of the specified node. If the node becomes undefined by 1022 * this operation, it is removed from the hierarchy. 1023 * 1024 * @param node the node to be cleared 1025 */ 1026 protected void clearNode(ConfigurationNode node) 1027 { 1028 node.setValue(null); 1029 if (!nodeDefined(node)) 1030 { 1031 removeNode(node); 1032 } 1033 } 1034 1035 /** 1036 * Returns a reference to the parent node of an add operation. Nodes for new 1037 * properties can be added as children of this node. If the path for the 1038 * specified key does not exist so far, it is created now. 1039 * 1040 * @param keyIt the iterator for the key of the new property 1041 * @param startNode the node to start the search with 1042 * @return the parent node for the add operation 1043 * @deprecated Adding new properties is now to a major part delegated to the 1044 * <code>ExpressionEngine</code> associated with this configuration instance. 1045 * This method will no longer be called. Developers who want to modify the 1046 * process of adding new properties should consider implementing their own 1047 * expression engine. 1048 */ 1049 protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt, Node startNode) 1050 { 1051 return null; 1052 } 1053 1054 /** 1055 * Finds the last existing node for an add operation. This method traverses 1056 * the configuration tree along the specified key. The last existing node on 1057 * this path is returned. 1058 * 1059 * @param keyIt the key iterator 1060 * @param node the actual node 1061 * @return the last existing node on the given path 1062 * @deprecated Adding new properties is now to a major part delegated to the 1063 * <code>ExpressionEngine</code> associated with this configuration instance. 1064 * This method will no longer be called. Developers who want to modify the 1065 * process of adding new properties should consider implementing their own 1066 * expression engine. 1067 */ 1068 protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt, Node node) 1069 { 1070 return null; 1071 } 1072 1073 /** 1074 * Creates the missing nodes for adding a new property. This method ensures 1075 * that there are corresponding nodes for all components of the specified 1076 * configuration key. 1077 * 1078 * @param keyIt the key iterator 1079 * @param root the base node of the path to be created 1080 * @return the last node of the path 1081 * @deprecated Adding new properties is now to a major part delegated to the 1082 * <code>ExpressionEngine</code> associated with this configuration instance. 1083 * This method will no longer be called. Developers who want to modify the 1084 * process of adding new properties should consider implementing their own 1085 * expression engine. 1086 */ 1087 protected Node createAddPath(ConfigurationKey.KeyIterator keyIt, Node root) 1088 { 1089 return null; 1090 } 1091 1092 /** 1093 * Creates a new <code>Node</code> object with the specified name. This 1094 * method can be overloaded in derived classes if a specific node type is 1095 * needed. This base implementation always returns a new object of the 1096 * <code>Node</code> class. 1097 * 1098 * @param name the name of the new node 1099 * @return the new node 1100 */ 1101 protected Node createNode(String name) 1102 { 1103 return new Node(name); 1104 } 1105 1106 /** 1107 * Helper method for processing a node add data object obtained from the 1108 * expression engine. This method will create all new nodes. 1109 * 1110 * @param data the data object 1111 * @return the new node 1112 * @since 1.3 1113 */ 1114 private ConfigurationNode processNodeAddData(NodeAddData data) 1115 { 1116 ConfigurationNode node = data.getParent(); 1117 1118 // Create missing nodes on the path 1119 for (Iterator it = data.getPathNodes().iterator(); it.hasNext();) 1120 { 1121 ConfigurationNode child = createNode((String) it.next()); 1122 node.addChild(child); 1123 node = child; 1124 } 1125 1126 // Add new target node 1127 ConfigurationNode child = createNode(data.getNewNodeName()); 1128 if (data.isAttribute()) 1129 { 1130 node.addAttribute(child); 1131 } 1132 else 1133 { 1134 node.addChild(child); 1135 } 1136 return child; 1137 } 1138 1139 /** 1140 * Clears all reference fields in a node structure. A configuration node can 1141 * store a so-called "reference". The meaning of this data is 1142 * determined by a concrete sub class. Typically such references are 1143 * specific for a configuration instance. If this instance is cloned or 1144 * copied, they must be cleared. This can be done using this method. 1145 * 1146 * @param node the root node of the node hierarchy, in which the references 1147 * are to be cleared 1148 * @since 1.4 1149 */ 1150 protected static void clearReferences(ConfigurationNode node) 1151 { 1152 node.visit(new ConfigurationNodeVisitorAdapter() 1153 { 1154 public void visitBeforeChildren(ConfigurationNode node) 1155 { 1156 node.setReference(null); 1157 } 1158 }); 1159 } 1160 1161 /** 1162 * A data class for storing (hierarchical) property information. A property 1163 * can have a value and an arbitrary number of child properties. From 1164 * version 1.3 on this class is only a thin wrapper over the 1165 * <code>{@link org.apache.commons.configuration.tree.DefaultConfigurationNode DefaultconfigurationNode}</code> 1166 * class that exists mainly for the purpose of backwards compatibility. 1167 */ 1168 public static class Node extends DefaultConfigurationNode implements Serializable 1169 { 1170 /** 1171 * The serial version UID. 1172 */ 1173 private static final long serialVersionUID = -6357500633536941775L; 1174 1175 /** 1176 * Creates a new instance of <code>Node</code>. 1177 */ 1178 public Node() 1179 { 1180 super(); 1181 } 1182 1183 /** 1184 * Creates a new instance of <code>Node</code> and sets the name. 1185 * 1186 * @param name the node's name 1187 */ 1188 public Node(String name) 1189 { 1190 super(name); 1191 } 1192 1193 /** 1194 * Creates a new instance of <code>Node</code> and sets the name and the value. 1195 * 1196 * @param name the node's name 1197 * @param value the value 1198 */ 1199 public Node(String name, Object value) 1200 { 1201 super(name, value); 1202 } 1203 1204 /** 1205 * Creates a new instance of <code>Node</code> based on the given 1206 * source node. All properties of the source node, including its 1207 * children and attributes, will be copied. 1208 * 1209 * @param src the node to be copied 1210 */ 1211 public Node(ConfigurationNode src) 1212 { 1213 this(src.getName(), src.getValue()); 1214 setReference(src.getReference()); 1215 for (Iterator it = src.getChildren().iterator(); it.hasNext();) 1216 { 1217 ConfigurationNode nd = (ConfigurationNode) it.next(); 1218 // Don't change the parent node 1219 ConfigurationNode parent = nd.getParentNode(); 1220 addChild(nd); 1221 nd.setParentNode(parent); 1222 } 1223 1224 for (Iterator it = src.getAttributes().iterator(); it.hasNext();) 1225 { 1226 ConfigurationNode nd = (ConfigurationNode) it.next(); 1227 // Don't change the parent node 1228 ConfigurationNode parent = nd.getParentNode(); 1229 addAttribute(nd); 1230 nd.setParentNode(parent); 1231 } 1232 } 1233 1234 /** 1235 * Returns the parent of this node. 1236 * 1237 * @return this node's parent (can be <b>null</b>) 1238 */ 1239 public Node getParent() 1240 { 1241 return (Node) getParentNode(); 1242 } 1243 1244 /** 1245 * Sets the parent of this node. 1246 * 1247 * @param node the parent node 1248 */ 1249 public void setParent(Node node) 1250 { 1251 setParentNode(node); 1252 } 1253 1254 /** 1255 * Adds the given node to the children of this node. 1256 * 1257 * @param node the child to be added 1258 */ 1259 public void addChild(Node node) 1260 { 1261 addChild((ConfigurationNode) node); 1262 } 1263 1264 /** 1265 * Returns a flag whether this node has child elements. 1266 * 1267 * @return <b>true</b> if there is a child node, <b>false</b> otherwise 1268 */ 1269 public boolean hasChildren() 1270 { 1271 return getChildrenCount() > 0 || getAttributeCount() > 0; 1272 } 1273 1274 /** 1275 * Removes the specified child from this node. 1276 * 1277 * @param child the child node to be removed 1278 * @return a flag if the child could be found 1279 */ 1280 public boolean remove(Node child) 1281 { 1282 return child.isAttribute() ? removeAttribute(child) : removeChild(child); 1283 } 1284 1285 /** 1286 * Removes all children with the given name. 1287 * 1288 * @param name the name of the children to be removed 1289 * @return a flag if children with this name existed 1290 */ 1291 public boolean remove(String name) 1292 { 1293 boolean childrenRemoved = removeChild(name); 1294 boolean attrsRemoved = removeAttribute(name); 1295 return childrenRemoved || attrsRemoved; 1296 } 1297 1298 /** 1299 * A generic method for traversing this node and all of its children. 1300 * This method sends the passed in visitor to this node and all of its 1301 * children. 1302 * 1303 * @param visitor the visitor 1304 * @param key here a configuration key with the name of the root node of 1305 * the iteration can be passed; if this key is not <b>null </b>, the 1306 * full pathes to the visited nodes are builded and passed to the 1307 * visitor's <code>visit()</code> methods 1308 */ 1309 public void visit(NodeVisitor visitor, ConfigurationKey key) 1310 { 1311 int length = 0; 1312 if (key != null) 1313 { 1314 length = key.length(); 1315 if (getName() != null) 1316 { 1317 key 1318 .append(StringUtils 1319 .replace( 1320 isAttribute() ? ConfigurationKey 1321 .constructAttributeKey(getName()) 1322 : getName(), 1323 String 1324 .valueOf(ConfigurationKey.PROPERTY_DELIMITER), 1325 ConfigurationKey.ESCAPED_DELIMITER)); 1326 } 1327 } 1328 1329 visitor.visitBeforeChildren(this, key); 1330 1331 for (Iterator it = getChildren().iterator(); it.hasNext() 1332 && !visitor.terminate();) 1333 { 1334 ((Node) it.next()).visit(visitor, key); 1335 } 1336 for (Iterator it = getAttributes().iterator(); it.hasNext() 1337 && !visitor.terminate();) 1338 { 1339 ((Node) it.next()).visit(visitor, key); 1340 } 1341 1342 if (key != null) 1343 { 1344 key.setLength(length); 1345 } 1346 visitor.visitAfterChildren(this, key); 1347 } 1348 } 1349 1350 /** 1351 * <p>Definition of a visitor class for traversing a node and all of its 1352 * children.</p><p>This class defines the interface of a visitor for 1353 * <code>Node</code> objects and provides a default implementation. The 1354 * method <code>visit()</code> of <code>Node</code> implements a generic 1355 * iteration algorithm based on the <em>Visitor</em> pattern. By providing 1356 * different implementations of visitors it is possible to collect different 1357 * data during the iteration process.</p> 1358 * 1359 */ 1360 public static class NodeVisitor 1361 { 1362 /** 1363 * Visits the specified node. This method is called during iteration for 1364 * each node before its children have been visited. 1365 * 1366 * @param node the actual node 1367 * @param key the key of this node (may be <b>null </b>) 1368 */ 1369 public void visitBeforeChildren(Node node, ConfigurationKey key) 1370 { 1371 } 1372 1373 /** 1374 * Visits the specified node after its children have been processed. 1375 * This gives a visitor the opportunity of collecting additional data 1376 * after the child nodes have been visited. 1377 * 1378 * @param node the node to be visited 1379 * @param key the key of this node (may be <b>null </b>) 1380 */ 1381 public void visitAfterChildren(Node node, ConfigurationKey key) 1382 { 1383 } 1384 1385 /** 1386 * Returns a flag that indicates if iteration should be stopped. This 1387 * method is called after each visited node. It can be useful for 1388 * visitors that search a specific node. If this node is found, the 1389 * whole process can be stopped. This base implementation always returns 1390 * <b>false </b>. 1391 * 1392 * @return a flag if iteration should be stopped 1393 */ 1394 public boolean terminate() 1395 { 1396 return false; 1397 } 1398 } 1399 1400 /** 1401 * A specialized visitor that checks if a node is defined. 1402 * "Defined" in this terms means that the node or at least one of 1403 * its sub nodes is associated with a value. 1404 * 1405 */ 1406 static class DefinedVisitor extends ConfigurationNodeVisitorAdapter 1407 { 1408 /** Stores the defined flag. */ 1409 private boolean defined; 1410 1411 /** 1412 * Checks if iteration should be stopped. This can be done if the first 1413 * defined node is found. 1414 * 1415 * @return a flag if iteration should be stopped 1416 */ 1417 public boolean terminate() 1418 { 1419 return isDefined(); 1420 } 1421 1422 /** 1423 * Visits the node. Checks if a value is defined. 1424 * 1425 * @param node the actual node 1426 */ 1427 public void visitBeforeChildren(ConfigurationNode node) 1428 { 1429 defined = node.getValue() != null; 1430 } 1431 1432 /** 1433 * Returns the defined flag. 1434 * 1435 * @return the defined flag 1436 */ 1437 public boolean isDefined() 1438 { 1439 return defined; 1440 } 1441 } 1442 1443 /** 1444 * A specialized visitor that fills a list with keys that are defined in a 1445 * node hierarchy. 1446 */ 1447 class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter 1448 { 1449 /** Stores the list to be filled. */ 1450 private Set keyList; 1451 1452 /** A stack with the keys of the already processed nodes. */ 1453 private Stack parentKeys; 1454 1455 /** 1456 * Default constructor. 1457 */ 1458 public DefinedKeysVisitor() 1459 { 1460 keyList = new ListOrderedSet(); 1461 parentKeys = new Stack(); 1462 } 1463 1464 /** 1465 * Creates a new <code>DefinedKeysVisitor</code> instance and sets the 1466 * prefix for the keys to fetch. 1467 * 1468 * @param prefix the prefix 1469 */ 1470 public DefinedKeysVisitor(String prefix) 1471 { 1472 this(); 1473 parentKeys.push(prefix); 1474 } 1475 1476 /** 1477 * Returns the list with all defined keys. 1478 * 1479 * @return the list with the defined keys 1480 */ 1481 public Set getKeyList() 1482 { 1483 return keyList; 1484 } 1485 1486 /** 1487 * Visits the node after its children has been processed. Removes this 1488 * node's key from the stack. 1489 * 1490 * @param node the node 1491 */ 1492 public void visitAfterChildren(ConfigurationNode node) 1493 { 1494 parentKeys.pop(); 1495 } 1496 1497 /** 1498 * Visits the specified node. If this node has a value, its key is added 1499 * to the internal list. 1500 * 1501 * @param node the node to be visited 1502 */ 1503 public void visitBeforeChildren(ConfigurationNode node) 1504 { 1505 String parentKey = parentKeys.isEmpty() ? null 1506 : (String) parentKeys.peek(); 1507 String key = getExpressionEngine().nodeKey(node, parentKey); 1508 parentKeys.push(key); 1509 if (node.getValue() != null) 1510 { 1511 keyList.add(key); 1512 } 1513 } 1514 } 1515 1516 /** 1517 * A specialized visitor that is able to create a deep copy of a node 1518 * hierarchy. 1519 */ 1520 static class CloneVisitor extends ConfigurationNodeVisitorAdapter 1521 { 1522 /** A stack with the actual object to be copied. */ 1523 private Stack copyStack; 1524 1525 /** Stores the result of the clone process. */ 1526 private ConfigurationNode result; 1527 1528 /** 1529 * Creates a new instance of <code>CloneVisitor</code>. 1530 */ 1531 public CloneVisitor() 1532 { 1533 copyStack = new Stack(); 1534 } 1535 1536 /** 1537 * Visits the specified node after its children have been processed. 1538 * 1539 * @param node the node 1540 */ 1541 public void visitAfterChildren(ConfigurationNode node) 1542 { 1543 ConfigurationNode copy = (ConfigurationNode) copyStack.pop(); 1544 if (copyStack.isEmpty()) 1545 { 1546 result = copy; 1547 } 1548 } 1549 1550 /** 1551 * Visits and copies the specified node. 1552 * 1553 * @param node the node 1554 */ 1555 public void visitBeforeChildren(ConfigurationNode node) 1556 { 1557 ConfigurationNode copy = (ConfigurationNode) node.clone(); 1558 copy.setParentNode(null); 1559 1560 if (!copyStack.isEmpty()) 1561 { 1562 if (node.isAttribute()) 1563 { 1564 ((ConfigurationNode) copyStack.peek()).addAttribute(copy); 1565 } 1566 else 1567 { 1568 ((ConfigurationNode) copyStack.peek()).addChild(copy); 1569 } 1570 } 1571 1572 copyStack.push(copy); 1573 } 1574 1575 /** 1576 * Returns the result of the clone process. This is the root node of the 1577 * cloned node hierarchy. 1578 * 1579 * @return the cloned root node 1580 */ 1581 public ConfigurationNode getClone() 1582 { 1583 return result; 1584 } 1585 } 1586 1587 /** 1588 * A specialized visitor base class that can be used for storing the tree of 1589 * configuration nodes. The basic idea is that each node can be associated 1590 * with a reference object. This reference object has a concrete meaning in 1591 * a derived class, e.g. an entry in a JNDI context or an XML element. When 1592 * the configuration tree is set up, the <code>load()</code> method is 1593 * responsible for setting the reference objects. When the configuration 1594 * tree is later modified, new nodes do not have a defined reference object. 1595 * This visitor class processes all nodes and finds the ones without a 1596 * defined reference object. For those nodes the <code>insert()</code> 1597 * method is called, which must be defined in concrete sub classes. This 1598 * method can perform all steps to integrate the new node into the original 1599 * structure. 1600 * 1601 */ 1602 protected abstract static class BuilderVisitor extends NodeVisitor 1603 { 1604 /** 1605 * Visits the specified node before its children have been traversed. 1606 * 1607 * @param node the node to visit 1608 * @param key the current key 1609 */ 1610 public void visitBeforeChildren(Node node, ConfigurationKey key) 1611 { 1612 Collection subNodes = new LinkedList(node.getChildren()); 1613 subNodes.addAll(node.getAttributes()); 1614 Iterator children = subNodes.iterator(); 1615 Node sibling1 = null; 1616 Node nd = null; 1617 1618 while (children.hasNext()) 1619 { 1620 // find the next new node 1621 do 1622 { 1623 sibling1 = nd; 1624 nd = (Node) children.next(); 1625 } while (nd.getReference() != null && children.hasNext()); 1626 1627 if (nd.getReference() == null) 1628 { 1629 // find all following new nodes 1630 List newNodes = new LinkedList(); 1631 newNodes.add(nd); 1632 while (children.hasNext()) 1633 { 1634 nd = (Node) children.next(); 1635 if (nd.getReference() == null) 1636 { 1637 newNodes.add(nd); 1638 } 1639 else 1640 { 1641 break; 1642 } 1643 } 1644 1645 // Insert all new nodes 1646 Node sibling2 = (nd.getReference() == null) ? null : nd; 1647 for (Iterator it = newNodes.iterator(); it.hasNext();) 1648 { 1649 Node insertNode = (Node) it.next(); 1650 if (insertNode.getReference() == null) 1651 { 1652 Object ref = insert(insertNode, node, sibling1, sibling2); 1653 if (ref != null) 1654 { 1655 insertNode.setReference(ref); 1656 } 1657 sibling1 = insertNode; 1658 } 1659 } 1660 } 1661 } 1662 } 1663 1664 /** 1665 * Inserts a new node into the structure constructed by this builder. 1666 * This method is called for each node that has been added to the 1667 * configuration tree after the configuration has been loaded from its 1668 * source. These new nodes have to be inserted into the original 1669 * structure. The passed in nodes define the position of the node to be 1670 * inserted: its parent and the siblings between to insert. The return 1671 * value is interpreted as the new reference of the affected 1672 * <code>Node</code> object; if it is not <b>null </b>, it is passed 1673 * to the node's <code>setReference()</code> method. 1674 * 1675 * @param newNode the node to be inserted 1676 * @param parent the parent node 1677 * @param sibling1 the sibling after which the node is to be inserted; 1678 * can be <b>null </b> if the new node is going to be the first child 1679 * node 1680 * @param sibling2 the sibling before which the node is to be inserted; 1681 * can be <b>null </b> if the new node is going to be the last child 1682 * node 1683 * @return the reference object for the node to be inserted 1684 */ 1685 protected abstract Object insert(Node newNode, Node parent, Node sibling1, Node sibling2); 1686 } 1687 }