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