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 018package org.apache.commons.configuration; 019 020import java.io.Serializable; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.Iterator; 025import java.util.LinkedHashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Set; 029import java.util.Stack; 030 031import org.apache.commons.configuration.event.ConfigurationEvent; 032import org.apache.commons.configuration.event.ConfigurationListener; 033import org.apache.commons.configuration.tree.ConfigurationNode; 034import org.apache.commons.configuration.tree.ConfigurationNodeVisitorAdapter; 035import org.apache.commons.configuration.tree.DefaultConfigurationNode; 036import org.apache.commons.configuration.tree.DefaultExpressionEngine; 037import org.apache.commons.configuration.tree.ExpressionEngine; 038import org.apache.commons.configuration.tree.NodeAddData; 039import org.apache.commons.configuration.tree.ViewNode; 040import 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} 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} 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} can be used to find out the name of the 091 * first table. In opposite {@code tables.table.name} would return a 092 * collection with the names of all available tables. Similarly the key 093 * {@code tables.table(1).fields.field.name} returns a collection with 094 * the names of all fields of the second table. If another index is added after 095 * the {@code field} element, a single field can be accessed: 096 * {@code tables.table(1).fields.field(0).name}.</p><p>There is a 097 * {@code getMaxIndex()} 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} 105 * instance is created. With the {@code setExpressionEngine()} method a 106 * different expression engine can be set. For instance with 107 * {@link org.apache.commons.configuration.tree.xpath.XPathExpressionEngine} 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()} 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()} 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} 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 1330666 2012-04-26 06:12:30Z oheger $ 128 */ 129public 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}. 170 */ 171 public HierarchicalConfiguration() 172 { 173 setRootNode(new Node()); 174 } 175 176 /** 177 * Creates a new instance of {@code HierarchicalConfiguration} 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 * {@link #getRootNode()} method instead, which operates on 211 * the preferred data type {@code ConfigurationNode}. 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 * {@link #setRootNode(ConfigurationNode)} method instead, 230 * which operates on the preferred data type {@code ConfigurationNode}. 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<ConfigurationNode> nodes = fetchNodeList(key); 345 346 if (nodes.size() == 0) 347 { 348 return null; 349 } 350 else 351 { 352 List<Object> list = new ArrayList<Object>(); 353 for (ConfigurationNode node : nodes) 354 { 355 if (node.getValue() != null) 356 { 357 list.add(node.getValue()); 358 } 359 } 360 361 if (list.size() < 1) 362 { 363 return null; 364 } 365 else 366 { 367 return (list.size() == 1) ? list.get(0) : list; 368 } 369 } 370 } 371 372 /** 373 * Adds the property with the specified key. This task will be delegated to 374 * the associated {@code ExpressionEngine}, so the passed in key 375 * must match the requirements of this implementation. 376 * 377 * @param key the key of the new property 378 * @param obj the value of the new property 379 */ 380 @Override 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()}, 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} 394 * object to this object. (However be aware that a 395 * {@code ConfigurationNode} 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()}.) 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} objects to be 407 * added 408 */ 409 public void addNodes(String key, Collection<? extends ConfigurationNode> 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<ConfigurationNode> target = fetchNodeList(key); 419 if (target.size() == 1) 420 { 421 // existing unique key 422 parent = 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 (ConfigurationNode child : nodes) 438 { 439 if (child.isAttribute()) 440 { 441 parent.addAttribute(child); 442 } 443 else 444 { 445 parent.addChild(child); 446 } 447 clearReferences(child); 448 } 449 fireEvent(EVENT_ADD_NODES, key, nodes, false); 450 } 451 452 /** 453 * Checks if this configuration is empty. Empty means that there are no keys 454 * with any values, though there can be some (empty) nodes. 455 * 456 * @return a flag if this configuration is empty 457 */ 458 public boolean isEmpty() 459 { 460 return !nodeDefined(getRootNode()); 461 } 462 463 /** 464 * Creates a new {@code Configuration} object containing all keys 465 * that start with the specified prefix. This implementation will return a 466 * {@code HierarchicalConfiguration} object so that the structure of 467 * the keys will be saved. The nodes selected by the prefix (it is possible 468 * that multiple nodes are selected) are mapped to the root node of the 469 * returned configuration, i.e. their children and attributes will become 470 * children and attributes of the new root node. However a value of the root 471 * node is only set if exactly one of the selected nodes contain a value (if 472 * multiple nodes have a value, there is simply no way to decide how these 473 * values are merged together). Note that the returned 474 * {@code Configuration} object is not connected to its source 475 * configuration: updates on the source configuration are not reflected in 476 * the subset and vice versa. 477 * 478 * @param prefix the prefix of the keys for the subset 479 * @return a new configuration object representing the selected subset 480 */ 481 @Override 482 public Configuration subset(String prefix) 483 { 484 Collection<ConfigurationNode> 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 @Override 495 protected Object interpolate(Object value) 496 { 497 return parent.interpolate(value); 498 } 499 }; 500 CloneVisitor visitor = new CloneVisitor(); 501 502 // Initialize the new root node 503 Object value = null; 504 int valueCount = 0; 505 for (ConfigurationNode nd : nodes) 506 { 507 if (nd.getValue() != null) 508 { 509 value = nd.getValue(); 510 valueCount++; 511 } 512 nd.visit(visitor); 513 514 for (ConfigurationNode c : visitor.getClone().getChildren()) 515 { 516 result.getRootNode().addChild(c); 517 } 518 for (ConfigurationNode attr : visitor.getClone().getAttributes()) 519 { 520 result.getRootNode().addAttribute(attr); 521 } 522 } 523 524 // Determine the value of the new root 525 if (valueCount == 1) 526 { 527 result.getRootNode().setValue(value); 528 } 529 return (result.isEmpty()) ? new HierarchicalConfiguration() : result; 530 } 531 532 /** 533 * <p> 534 * Returns a hierarchical subnode configuration object that wraps the 535 * configuration node specified by the given key. This method provides an 536 * easy means of accessing sub trees of a hierarchical configuration. In the 537 * returned configuration the sub tree can directly be accessed, it becomes 538 * the root node of this configuration. Because of this the passed in key 539 * must select exactly one configuration node; otherwise an 540 * {@code IllegalArgumentException} will be thrown. 541 * </p> 542 * <p> 543 * The difference between this method and the 544 * {@link #subset(String)} method is that 545 * {@code subset()} supports arbitrary subsets of configuration nodes 546 * while {@code configurationAt()} only returns a single sub tree. 547 * Please refer to the documentation of the 548 * {@code SubnodeConfiguration} class to obtain further information 549 * about subnode configurations and when they should be used. 550 * </p> 551 * <p> 552 * With the {@code supportUpdate} flag the behavior of the returned 553 * {@code SubnodeConfiguration} regarding updates of its parent 554 * configuration can be determined. A subnode configuration operates on the 555 * same nodes as its parent, so changes at one configuration are normally 556 * directly visible for the other configuration. There are however changes 557 * of the parent configuration, which are not recognized by the subnode 558 * configuration per default. An example for this is a reload operation (for 559 * file-based configurations): Here the complete node set of the parent 560 * configuration is replaced, but the subnode configuration still references 561 * the old nodes. If such changes should be detected by the subnode 562 * configuration, the {@code supportUpdates} flag must be set to 563 * <b>true</b>. This causes the subnode configuration to reevaluate the key 564 * used for its creation each time it is accessed. This guarantees that the 565 * subnode configuration always stays in sync with its key, even if the 566 * parent configuration's data significantly changes. If such a change 567 * makes the key invalid - because it now no longer points to exactly one 568 * node -, the subnode configuration is not reconstructed, but keeps its 569 * old data. It is then quasi detached from its parent. 570 * </p> 571 * 572 * @param key the key that selects the sub tree 573 * @param supportUpdates a flag whether the returned subnode configuration 574 * should be able to handle updates of its parent 575 * @return a hierarchical configuration that contains this sub tree 576 * @see SubnodeConfiguration 577 * @since 1.5 578 */ 579 public SubnodeConfiguration configurationAt(String key, 580 boolean supportUpdates) 581 { 582 List<ConfigurationNode> nodes = fetchNodeList(key); 583 if (nodes.size() != 1) 584 { 585 throw new IllegalArgumentException( 586 "Passed in key must select exactly one node: " + key); 587 } 588 return supportUpdates ? createSubnodeConfiguration( 589 nodes.get(0), key) 590 : createSubnodeConfiguration(nodes.get(0)); 591 } 592 593 /** 594 * Returns a hierarchical subnode configuration for the node specified by 595 * the given key. This is a short form for {@code configurationAt(key, 596 * <b>false</b>)}. 597 * 598 * @param key the key that selects the sub tree 599 * @return a hierarchical configuration that contains this sub tree 600 * @see SubnodeConfiguration 601 * @since 1.3 602 */ 603 public SubnodeConfiguration configurationAt(String key) 604 { 605 return configurationAt(key, false); 606 } 607 608 /** 609 * Returns a list of sub configurations for all configuration nodes selected 610 * by the given key. This method will evaluate the passed in key (using the 611 * current {@code ExpressionEngine}) and then create a subnode 612 * configuration for each returned node (like 613 * {@link #configurationAt(String)}}). This is especially 614 * useful when dealing with list-like structures. As an example consider the 615 * configuration that contains data about database tables and their fields. 616 * If you need access to all fields of a certain table, you can simply do 617 * 618 * <pre> 619 * List fields = config.configurationsAt("tables.table(0).fields.field"); 620 * for(Iterator it = fields.iterator(); it.hasNext();) 621 * { 622 * HierarchicalConfiguration sub = (HierarchicalConfiguration) it.next(); 623 * // now the children and attributes of the field node can be 624 * // directly accessed 625 * String fieldName = sub.getString("name"); 626 * String fieldType = sub.getString("type"); 627 * ... 628 * </pre> 629 * 630 * @param key the key for selecting the desired nodes 631 * @return a list with hierarchical configuration objects; each 632 * configuration represents one of the nodes selected by the passed in key 633 * @since 1.3 634 */ 635 public List<HierarchicalConfiguration> configurationsAt(String key) 636 { 637 List<ConfigurationNode> nodes = fetchNodeList(key); 638 List<HierarchicalConfiguration> configs = new ArrayList<HierarchicalConfiguration>(nodes.size()); 639 for (ConfigurationNode node : nodes) 640 { 641 configs.add(createSubnodeConfiguration(node)); 642 } 643 return configs; 644 } 645 646 /** 647 * Creates a subnode configuration for the specified node. This method is 648 * called by {@code configurationAt()} and 649 * {@code configurationsAt()}. 650 * 651 * @param node the node, for which a subnode configuration is to be created 652 * @return the configuration for the given node 653 * @since 1.3 654 */ 655 protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node) 656 { 657 SubnodeConfiguration result = new SubnodeConfiguration(this, node); 658 registerSubnodeConfiguration(result); 659 return result; 660 } 661 662 /** 663 * Creates a new subnode configuration for the specified node and sets its 664 * construction key. A subnode configuration created this way will be aware 665 * of structural changes of its parent. 666 * 667 * @param node the node, for which a subnode configuration is to be created 668 * @param subnodeKey the key used to construct the configuration 669 * @return the configuration for the given node 670 * @since 1.5 671 */ 672 protected SubnodeConfiguration createSubnodeConfiguration( 673 ConfigurationNode node, String subnodeKey) 674 { 675 SubnodeConfiguration result = createSubnodeConfiguration(node); 676 result.setSubnodeKey(subnodeKey); 677 return result; 678 } 679 680 /** 681 * This method is always called when a subnode configuration created from 682 * this configuration has been modified. This implementation transforms the 683 * received event into an event of type {@code EVENT_SUBNODE_CHANGED} 684 * and notifies the registered listeners. 685 * 686 * @param event the event describing the change 687 * @since 1.5 688 */ 689 protected void subnodeConfigurationChanged(ConfigurationEvent event) 690 { 691 fireEvent(EVENT_SUBNODE_CHANGED, null, event, event.isBeforeUpdate()); 692 } 693 694 /** 695 * Registers this instance at the given subnode configuration. This 696 * implementation will register a change listener, so that modifications of 697 * the subnode configuration can be tracked. 698 * 699 * @param config the subnode configuration 700 * @since 1.5 701 */ 702 void registerSubnodeConfiguration(SubnodeConfiguration config) 703 { 704 config.addConfigurationListener(new ConfigurationListener() 705 { 706 public void configurationChanged(ConfigurationEvent event) 707 { 708 subnodeConfigurationChanged(event); 709 } 710 }); 711 } 712 713 /** 714 * Checks if the specified key is contained in this configuration. Note that 715 * for this configuration the term "contained" means that the key 716 * has an associated value. If there is a node for this key that has no 717 * value but children (either defined or undefined), this method will still 718 * return <b>false </b>. 719 * 720 * @param key the key to be chekced 721 * @return a flag if this key is contained in this configuration 722 */ 723 public boolean containsKey(String key) 724 { 725 return getProperty(key) != null; 726 } 727 728 /** 729 * Sets the value of the specified property. 730 * 731 * @param key the key of the property to set 732 * @param value the new value of this property 733 */ 734 @Override 735 public void setProperty(String key, Object value) 736 { 737 fireEvent(EVENT_SET_PROPERTY, key, value, true); 738 739 // Update the existing nodes for this property 740 Iterator<ConfigurationNode> itNodes = fetchNodeList(key).iterator(); 741 Iterator<?> itValues; 742 if (!isDelimiterParsingDisabled() || !(value instanceof String)) 743 { 744 itValues = PropertyConverter.toIterator(value, getListDelimiter()); 745 } 746 else 747 { 748 itValues = Collections.singleton(value).iterator(); 749 } 750 751 while (itNodes.hasNext() && itValues.hasNext()) 752 { 753 itNodes.next().setValue(itValues.next()); 754 } 755 756 // Add additional nodes if necessary 757 while (itValues.hasNext()) 758 { 759 addPropertyDirect(key, itValues.next()); 760 } 761 762 // Remove remaining nodes 763 while (itNodes.hasNext()) 764 { 765 clearNode(itNodes.next()); 766 } 767 768 fireEvent(EVENT_SET_PROPERTY, key, value, false); 769 } 770 771 /** 772 * Clears this configuration. This is a more efficient implementation than 773 * the one inherited from the base class. It directly removes all data from 774 * the root node. 775 */ 776 @Override 777 public void clear() 778 { 779 fireEvent(EVENT_CLEAR, null, null, true); 780 getRootNode().removeAttributes(); 781 getRootNode().removeChildren(); 782 getRootNode().setValue(null); 783 fireEvent(EVENT_CLEAR, null, null, false); 784 } 785 786 /** 787 * Removes all values of the property with the given name and of keys that 788 * start with this name. So if there is a property with the key 789 * "foo" and a property with the key "foo.bar", a call 790 * of {@code clearTree("foo")} would remove both properties. 791 * 792 * @param key the key of the property to be removed 793 */ 794 public void clearTree(String key) 795 { 796 fireEvent(EVENT_CLEAR_TREE, key, null, true); 797 List<ConfigurationNode> nodes = fetchNodeList(key); 798 799 for (ConfigurationNode node : nodes) 800 { 801 removeNode(node); 802 } 803 fireEvent(EVENT_CLEAR_TREE, key, nodes, false); 804 } 805 806 /** 807 * Removes the property with the given key. Properties with names that start 808 * with the given key (i.e. properties below the specified key in the 809 * hierarchy) won't be affected. 810 * 811 * @param key the key of the property to be removed 812 */ 813 @Override 814 public void clearProperty(String key) 815 { 816 fireEvent(EVENT_CLEAR_PROPERTY, key, null, true); 817 List<ConfigurationNode> nodes = fetchNodeList(key); 818 819 for (ConfigurationNode node : nodes) 820 { 821 clearNode(node); 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<String> 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 @Override 854 public Iterator<String> getKeys(String prefix) 855 { 856 DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix); 857 if (containsKey(prefix)) 858 { 859 // explicitly add the prefix 860 visitor.getKeyList().add(prefix); 861 } 862 863 List<ConfigurationNode> nodes = fetchNodeList(prefix); 864 865 for (ConfigurationNode node : nodes) 866 { 867 for (ConfigurationNode c : node.getChildren()) 868 { 869 c.visit(visitor); 870 } 871 for (ConfigurationNode attr : node.getAttributes()) 872 { 873 attr.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 @Override 903 public Object clone() 904 { 905 try 906 { 907 HierarchicalConfiguration copy = (HierarchicalConfiguration) super 908 .clone(); 909 910 // clone the nodes, too 911 CloneVisitor v = new CloneVisitor(); 912 getRootNode().visit(v); 913 copy.setRootNode(v.getClone()); 914 915 return copy; 916 } 917 catch (CloneNotSupportedException cex) 918 { 919 // should not happen 920 throw new ConfigurationRuntimeException(cex); 921 } 922 } 923 924 /** 925 * Returns a configuration with the same content as this configuration, but 926 * with all variables replaced by their actual values. This implementation 927 * is specific for hierarchical configurations. It clones the current 928 * configuration and runs a specialized visitor on the clone, which performs 929 * interpolation on the single configuration nodes. 930 * 931 * @return a configuration with all variables interpolated 932 * @since 1.5 933 */ 934 @Override 935 public Configuration interpolatedConfiguration() 936 { 937 HierarchicalConfiguration c = (HierarchicalConfiguration) clone(); 938 c.getRootNode().visit(new ConfigurationNodeVisitorAdapter() 939 { 940 @Override 941 public void visitAfterChildren(ConfigurationNode node) 942 { 943 node.setValue(interpolate(node.getValue())); 944 } 945 }); 946 return c; 947 } 948 949 /** 950 * Helper method for fetching a list of all nodes that are addressed by the 951 * specified key. 952 * 953 * @param key the key 954 * @return a list with all affected nodes (never <b>null </b>) 955 */ 956 protected List<ConfigurationNode> fetchNodeList(String key) 957 { 958 return getExpressionEngine().query(getRootNode(), key); 959 } 960 961 /** 962 * Recursive helper method for fetching a property. This method processes 963 * all facets of a configuration key, traverses the tree of properties and 964 * fetches the the nodes of all matching properties. 965 * 966 * @param keyPart the configuration key iterator 967 * @param node the actual node 968 * @param nodes here the found nodes are stored 969 * @deprecated Property keys are now evaluated by the expression engine 970 * associated with the configuration; this method will no longer be called. 971 * If you want to modify the way properties are looked up, consider 972 * implementing you own {@code ExpressionEngine} implementation. 973 */ 974 @Deprecated 975 protected void findPropertyNodes(ConfigurationKey.KeyIterator keyPart, 976 Node node, Collection<ConfigurationNode> nodes) 977 { 978 } 979 980 /** 981 * Checks if the specified node is defined. 982 * 983 * @param node the node to be checked 984 * @return a flag if this node is defined 985 * @deprecated Use the method {@link #nodeDefined(ConfigurationNode)} 986 * instead. 987 */ 988 @Deprecated 989 protected boolean nodeDefined(Node node) 990 { 991 return nodeDefined((ConfigurationNode) node); 992 } 993 994 /** 995 * Checks if the specified node is defined. 996 * 997 * @param node the node to be checked 998 * @return a flag if this node is defined 999 */ 1000 protected boolean nodeDefined(ConfigurationNode node) 1001 { 1002 DefinedVisitor visitor = new DefinedVisitor(); 1003 node.visit(visitor); 1004 return visitor.isDefined(); 1005 } 1006 1007 /** 1008 * Removes the specified node from this configuration. This method ensures 1009 * that parent nodes that become undefined by this operation are also 1010 * removed. 1011 * 1012 * @param node the node to be removed 1013 * @deprecated Use the method {@link #removeNode(ConfigurationNode)} 1014 * instead. 1015 */ 1016 @Deprecated 1017 protected void removeNode(Node node) 1018 { 1019 removeNode((ConfigurationNode) node); 1020 } 1021 1022 /** 1023 * Removes the specified node from this configuration. This method ensures 1024 * that parent nodes that become undefined by this operation are also 1025 * removed. 1026 * 1027 * @param node the node to be removed 1028 */ 1029 protected void removeNode(ConfigurationNode node) 1030 { 1031 ConfigurationNode parent = node.getParentNode(); 1032 if (parent != null) 1033 { 1034 parent.removeChild(node); 1035 if (!nodeDefined(parent)) 1036 { 1037 removeNode(parent); 1038 } 1039 } 1040 } 1041 1042 /** 1043 * Clears the value of the specified node. If the node becomes undefined by 1044 * this operation, it is removed from the hierarchy. 1045 * 1046 * @param node the node to be cleared 1047 * @deprecated Use the method {@link #clearNode(ConfigurationNode)} 1048 * instead 1049 */ 1050 @Deprecated 1051 protected void clearNode(Node node) 1052 { 1053 clearNode((ConfigurationNode) node); 1054 } 1055 1056 /** 1057 * Clears the value of the specified node. If the node becomes undefined by 1058 * this operation, it is removed from the hierarchy. 1059 * 1060 * @param node the node to be cleared 1061 */ 1062 protected void clearNode(ConfigurationNode node) 1063 { 1064 node.setValue(null); 1065 if (!nodeDefined(node)) 1066 { 1067 removeNode(node); 1068 } 1069 } 1070 1071 /** 1072 * Returns a reference to the parent node of an add operation. Nodes for new 1073 * properties can be added as children of this node. If the path for the 1074 * specified key does not exist so far, it is created now. 1075 * 1076 * @param keyIt the iterator for the key of the new property 1077 * @param startNode the node to start the search with 1078 * @return the parent node for the add operation 1079 * @deprecated Adding new properties is now to a major part delegated to the 1080 * {@code ExpressionEngine} associated with this configuration instance. 1081 * This method will no longer be called. Developers who want to modify the 1082 * process of adding new properties should consider implementing their own 1083 * expression engine. 1084 */ 1085 @Deprecated 1086 protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt, Node startNode) 1087 { 1088 return null; 1089 } 1090 1091 /** 1092 * Finds the last existing node for an add operation. This method traverses 1093 * the configuration tree along the specified key. The last existing node on 1094 * this path is returned. 1095 * 1096 * @param keyIt the key iterator 1097 * @param node the actual node 1098 * @return the last existing node on the given path 1099 * @deprecated Adding new properties is now to a major part delegated to the 1100 * {@code ExpressionEngine} associated with this configuration instance. 1101 * This method will no longer be called. Developers who want to modify the 1102 * process of adding new properties should consider implementing their own 1103 * expression engine. 1104 */ 1105 @Deprecated 1106 protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt, Node node) 1107 { 1108 return null; 1109 } 1110 1111 /** 1112 * Creates the missing nodes for adding a new property. This method ensures 1113 * that there are corresponding nodes for all components of the specified 1114 * configuration key. 1115 * 1116 * @param keyIt the key iterator 1117 * @param root the base node of the path to be created 1118 * @return the last node of the path 1119 * @deprecated Adding new properties is now to a major part delegated to the 1120 * {@code ExpressionEngine} associated with this configuration instance. 1121 * This method will no longer be called. Developers who want to modify the 1122 * process of adding new properties should consider implementing their own 1123 * expression engine. 1124 */ 1125 @Deprecated 1126 protected Node createAddPath(ConfigurationKey.KeyIterator keyIt, Node root) 1127 { 1128 return null; 1129 } 1130 1131 /** 1132 * Creates a new {@code Node} object with the specified name. This 1133 * method can be overloaded in derived classes if a specific node type is 1134 * needed. This base implementation always returns a new object of the 1135 * {@code Node} class. 1136 * 1137 * @param name the name of the new node 1138 * @return the new node 1139 */ 1140 protected Node createNode(String name) 1141 { 1142 return new Node(name); 1143 } 1144 1145 /** 1146 * Helper method for processing a node add data object obtained from the 1147 * expression engine. This method will create all new nodes. 1148 * 1149 * @param data the data object 1150 * @return the new node 1151 * @since 1.3 1152 */ 1153 private ConfigurationNode processNodeAddData(NodeAddData data) 1154 { 1155 ConfigurationNode node = data.getParent(); 1156 1157 // Create missing nodes on the path 1158 for (String name : data.getPathNodes()) 1159 { 1160 ConfigurationNode child = createNode(name); 1161 node.addChild(child); 1162 node = child; 1163 } 1164 1165 // Add new target node 1166 ConfigurationNode child = createNode(data.getNewNodeName()); 1167 if (data.isAttribute()) 1168 { 1169 node.addAttribute(child); 1170 } 1171 else 1172 { 1173 node.addChild(child); 1174 } 1175 return child; 1176 } 1177 1178 /** 1179 * Clears all reference fields in a node structure. A configuration node can 1180 * store a so-called "reference". The meaning of this data is 1181 * determined by a concrete sub class. Typically such references are 1182 * specific for a configuration instance. If this instance is cloned or 1183 * copied, they must be cleared. This can be done using this method. 1184 * 1185 * @param node the root node of the node hierarchy, in which the references 1186 * are to be cleared 1187 * @since 1.4 1188 */ 1189 protected static void clearReferences(ConfigurationNode node) 1190 { 1191 node.visit(new ConfigurationNodeVisitorAdapter() 1192 { 1193 @Override 1194 public void visitBeforeChildren(ConfigurationNode node) 1195 { 1196 node.setReference(null); 1197 } 1198 }); 1199 } 1200 1201 /** 1202 * Transforms the specified object into a Node. This method treats view 1203 * nodes in a special way. This is necessary because ViewNode does not 1204 * extend HierarchicalConfiguration.Node; thus the API for the node visitor 1205 * is slightly different. Therefore a view node is transformed into a 1206 * special compatibility Node object. 1207 * 1208 * @param obj the original node object 1209 * @return the node to be used 1210 */ 1211 private static Node getNodeFor(Object obj) 1212 { 1213 Node nd; 1214 if (obj instanceof ViewNode) 1215 { 1216 final ViewNode viewNode = (ViewNode) obj; 1217 nd = new Node(viewNode) 1218 { 1219 @Override 1220 public void setReference(Object reference) 1221 { 1222 super.setReference(reference); 1223 // also set the reference at the original node 1224 viewNode.setReference(reference); 1225 } 1226 }; 1227 } 1228 else 1229 { 1230 nd = (Node) obj; 1231 } 1232 return nd; 1233 } 1234 1235 /** 1236 * A data class for storing (hierarchical) property information. A property 1237 * can have a value and an arbitrary number of child properties. From 1238 * version 1.3 on this class is only a thin wrapper over the 1239 * {@link org.apache.commons.configuration.tree.DefaultConfigurationNode DefaultconfigurationNode} 1240 * class that exists mainly for the purpose of backwards compatibility. 1241 */ 1242 public static class Node extends DefaultConfigurationNode implements Serializable 1243 { 1244 /** 1245 * The serial version UID. 1246 */ 1247 private static final long serialVersionUID = -6357500633536941775L; 1248 1249 /** 1250 * Creates a new instance of {@code Node}. 1251 */ 1252 public Node() 1253 { 1254 super(); 1255 } 1256 1257 /** 1258 * Creates a new instance of {@code Node} and sets the name. 1259 * 1260 * @param name the node's name 1261 */ 1262 public Node(String name) 1263 { 1264 super(name); 1265 } 1266 1267 /** 1268 * Creates a new instance of {@code Node} and sets the name and the value. 1269 * 1270 * @param name the node's name 1271 * @param value the value 1272 */ 1273 public Node(String name, Object value) 1274 { 1275 super(name, value); 1276 } 1277 1278 /** 1279 * Creates a new instance of {@code Node} based on the given 1280 * source node. All properties of the source node, including its 1281 * children and attributes, will be copied. 1282 * 1283 * @param src the node to be copied 1284 */ 1285 public Node(ConfigurationNode src) 1286 { 1287 this(src.getName(), src.getValue()); 1288 setReference(src.getReference()); 1289 for (ConfigurationNode nd : src.getChildren()) 1290 { 1291 // Don't change the parent node 1292 ConfigurationNode parent = nd.getParentNode(); 1293 addChild(nd); 1294 nd.setParentNode(parent); 1295 } 1296 1297 for (ConfigurationNode nd : src.getAttributes()) 1298 { 1299 // Don't change the parent node 1300 ConfigurationNode parent = nd.getParentNode(); 1301 addAttribute(nd); 1302 nd.setParentNode(parent); 1303 } 1304 } 1305 1306 /** 1307 * Returns the parent of this node. 1308 * 1309 * @return this node's parent (can be <b>null</b>) 1310 */ 1311 public Node getParent() 1312 { 1313 return (Node) getParentNode(); 1314 } 1315 1316 /** 1317 * Sets the parent of this node. 1318 * 1319 * @param node the parent node 1320 */ 1321 public void setParent(Node node) 1322 { 1323 setParentNode(node); 1324 } 1325 1326 /** 1327 * Adds the given node to the children of this node. 1328 * 1329 * @param node the child to be added 1330 */ 1331 public void addChild(Node node) 1332 { 1333 addChild((ConfigurationNode) node); 1334 } 1335 1336 /** 1337 * Returns a flag whether this node has child elements. 1338 * 1339 * @return <b>true</b> if there is a child node, <b>false</b> otherwise 1340 */ 1341 public boolean hasChildren() 1342 { 1343 return getChildrenCount() > 0 || getAttributeCount() > 0; 1344 } 1345 1346 /** 1347 * Removes the specified child from this node. 1348 * 1349 * @param child the child node to be removed 1350 * @return a flag if the child could be found 1351 */ 1352 public boolean remove(Node child) 1353 { 1354 return child.isAttribute() ? removeAttribute(child) : removeChild(child); 1355 } 1356 1357 /** 1358 * Removes all children with the given name. 1359 * 1360 * @param name the name of the children to be removed 1361 * @return a flag if children with this name existed 1362 */ 1363 public boolean remove(String name) 1364 { 1365 boolean childrenRemoved = removeChild(name); 1366 boolean attrsRemoved = removeAttribute(name); 1367 return childrenRemoved || attrsRemoved; 1368 } 1369 1370 /** 1371 * A generic method for traversing this node and all of its children. 1372 * This method sends the passed in visitor to this node and all of its 1373 * children. 1374 * 1375 * @param visitor the visitor 1376 * @param key here a configuration key with the name of the root node of 1377 * the iteration can be passed; if this key is not <b>null </b>, the 1378 * full paths to the visited nodes are builded and passed to the 1379 * visitor's {@code visit()} methods 1380 */ 1381 public void visit(NodeVisitor visitor, ConfigurationKey key) 1382 { 1383 int length = 0; 1384 if (key != null) 1385 { 1386 length = key.length(); 1387 if (getName() != null) 1388 { 1389 key 1390 .append(StringUtils 1391 .replace( 1392 isAttribute() ? ConfigurationKey 1393 .constructAttributeKey(getName()) 1394 : getName(), 1395 String 1396 .valueOf(ConfigurationKey.PROPERTY_DELIMITER), 1397 ConfigurationKey.ESCAPED_DELIMITER)); 1398 } 1399 } 1400 1401 visitor.visitBeforeChildren(this, key); 1402 1403 for (Iterator<ConfigurationNode> it = getChildren().iterator(); it.hasNext() 1404 && !visitor.terminate();) 1405 { 1406 Object obj = it.next(); 1407 getNodeFor(obj).visit(visitor, key); 1408 } 1409 for (Iterator<ConfigurationNode> it = getAttributes().iterator(); it.hasNext() 1410 && !visitor.terminate();) 1411 { 1412 Object obj = it.next(); 1413 getNodeFor(obj).visit(visitor, key); 1414 } 1415 1416 visitor.visitAfterChildren(this, key); 1417 if (key != null) 1418 { 1419 key.setLength(length); 1420 } 1421 } 1422 } 1423 1424 /** 1425 * <p>Definition of a visitor class for traversing a node and all of its 1426 * children.</p><p>This class defines the interface of a visitor for 1427 * {@code Node} objects and provides a default implementation. The 1428 * method {@code visit()} of {@code Node} implements a generic 1429 * iteration algorithm based on the <em>Visitor</em> pattern. By providing 1430 * different implementations of visitors it is possible to collect different 1431 * data during the iteration process.</p> 1432 * 1433 */ 1434 public static class NodeVisitor 1435 { 1436 /** 1437 * Visits the specified node. This method is called during iteration for 1438 * each node before its children have been visited. 1439 * 1440 * @param node the actual node 1441 * @param key the key of this node (may be <b>null </b>) 1442 */ 1443 public void visitBeforeChildren(Node node, ConfigurationKey key) 1444 { 1445 } 1446 1447 /** 1448 * Visits the specified node after its children have been processed. 1449 * This gives a visitor the opportunity of collecting additional data 1450 * after the child nodes have been visited. 1451 * 1452 * @param node the node to be visited 1453 * @param key the key of this node (may be <b>null </b>) 1454 */ 1455 public void visitAfterChildren(Node node, ConfigurationKey key) 1456 { 1457 } 1458 1459 /** 1460 * Returns a flag that indicates if iteration should be stopped. This 1461 * method is called after each visited node. It can be useful for 1462 * visitors that search a specific node. If this node is found, the 1463 * whole process can be stopped. This base implementation always returns 1464 * <b>false </b>. 1465 * 1466 * @return a flag if iteration should be stopped 1467 */ 1468 public boolean terminate() 1469 { 1470 return false; 1471 } 1472 } 1473 1474 /** 1475 * A specialized visitor that checks if a node is defined. 1476 * "Defined" in this terms means that the node or at least one of 1477 * its sub nodes is associated with a value. 1478 * 1479 */ 1480 static class DefinedVisitor extends ConfigurationNodeVisitorAdapter 1481 { 1482 /** Stores the defined flag. */ 1483 private boolean defined; 1484 1485 /** 1486 * Checks if iteration should be stopped. This can be done if the first 1487 * defined node is found. 1488 * 1489 * @return a flag if iteration should be stopped 1490 */ 1491 @Override 1492 public boolean terminate() 1493 { 1494 return isDefined(); 1495 } 1496 1497 /** 1498 * Visits the node. Checks if a value is defined. 1499 * 1500 * @param node the actual node 1501 */ 1502 @Override 1503 public void visitBeforeChildren(ConfigurationNode node) 1504 { 1505 defined = node.getValue() != null; 1506 } 1507 1508 /** 1509 * Returns the defined flag. 1510 * 1511 * @return the defined flag 1512 */ 1513 public boolean isDefined() 1514 { 1515 return defined; 1516 } 1517 } 1518 1519 /** 1520 * A specialized visitor that fills a list with keys that are defined in a 1521 * node hierarchy. 1522 */ 1523 class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter 1524 { 1525 /** Stores the list to be filled. */ 1526 private Set<String> keyList; 1527 1528 /** A stack with the keys of the already processed nodes. */ 1529 private Stack<String> parentKeys; 1530 1531 /** 1532 * Default constructor. 1533 */ 1534 public DefinedKeysVisitor() 1535 { 1536 keyList = new LinkedHashSet<String>(); 1537 parentKeys = new Stack<String>(); 1538 } 1539 1540 /** 1541 * Creates a new {@code DefinedKeysVisitor} instance and sets the 1542 * prefix for the keys to fetch. 1543 * 1544 * @param prefix the prefix 1545 */ 1546 public DefinedKeysVisitor(String prefix) 1547 { 1548 this(); 1549 parentKeys.push(prefix); 1550 } 1551 1552 /** 1553 * Returns the list with all defined keys. 1554 * 1555 * @return the list with the defined keys 1556 */ 1557 public Set<String> getKeyList() 1558 { 1559 return keyList; 1560 } 1561 1562 /** 1563 * Visits the node after its children has been processed. Removes this 1564 * node's key from the stack. 1565 * 1566 * @param node the node 1567 */ 1568 @Override 1569 public void visitAfterChildren(ConfigurationNode node) 1570 { 1571 parentKeys.pop(); 1572 } 1573 1574 /** 1575 * Visits the specified node. If this node has a value, its key is added 1576 * to the internal list. 1577 * 1578 * @param node the node to be visited 1579 */ 1580 @Override 1581 public void visitBeforeChildren(ConfigurationNode node) 1582 { 1583 String parentKey = parentKeys.isEmpty() ? null 1584 : (String) parentKeys.peek(); 1585 String key = getExpressionEngine().nodeKey(node, parentKey); 1586 parentKeys.push(key); 1587 if (node.getValue() != null) 1588 { 1589 keyList.add(key); 1590 } 1591 } 1592 } 1593 1594 /** 1595 * A specialized visitor that is able to create a deep copy of a node 1596 * hierarchy. 1597 */ 1598 static class CloneVisitor extends ConfigurationNodeVisitorAdapter 1599 { 1600 /** A stack with the actual object to be copied. */ 1601 private Stack<ConfigurationNode> copyStack; 1602 1603 /** Stores the result of the clone process. */ 1604 private ConfigurationNode result; 1605 1606 /** 1607 * Creates a new instance of {@code CloneVisitor}. 1608 */ 1609 public CloneVisitor() 1610 { 1611 copyStack = new Stack<ConfigurationNode>(); 1612 } 1613 1614 /** 1615 * Visits the specified node after its children have been processed. 1616 * 1617 * @param node the node 1618 */ 1619 @Override 1620 public void visitAfterChildren(ConfigurationNode node) 1621 { 1622 ConfigurationNode copy = copyStack.pop(); 1623 if (copyStack.isEmpty()) 1624 { 1625 result = copy; 1626 } 1627 } 1628 1629 /** 1630 * Visits and copies the specified node. 1631 * 1632 * @param node the node 1633 */ 1634 @Override 1635 public void visitBeforeChildren(ConfigurationNode node) 1636 { 1637 ConfigurationNode copy = (ConfigurationNode) node.clone(); 1638 copy.setParentNode(null); 1639 1640 if (!copyStack.isEmpty()) 1641 { 1642 if (node.isAttribute()) 1643 { 1644 copyStack.peek().addAttribute(copy); 1645 } 1646 else 1647 { 1648 copyStack.peek().addChild(copy); 1649 } 1650 } 1651 1652 copyStack.push(copy); 1653 } 1654 1655 /** 1656 * Returns the result of the clone process. This is the root node of the 1657 * cloned node hierarchy. 1658 * 1659 * @return the cloned root node 1660 */ 1661 public ConfigurationNode getClone() 1662 { 1663 return result; 1664 } 1665 } 1666 1667 /** 1668 * A specialized visitor base class that can be used for storing the tree of 1669 * configuration nodes. The basic idea is that each node can be associated 1670 * with a reference object. This reference object has a concrete meaning in 1671 * a derived class, e.g. an entry in a JNDI context or an XML element. When 1672 * the configuration tree is set up, the {@code load()} method is 1673 * responsible for setting the reference objects. When the configuration 1674 * tree is later modified, new nodes do not have a defined reference object. 1675 * This visitor class processes all nodes and finds the ones without a 1676 * defined reference object. For those nodes the {@code insert()} 1677 * method is called, which must be defined in concrete sub classes. This 1678 * method can perform all steps to integrate the new node into the original 1679 * structure. 1680 * 1681 */ 1682 protected abstract static class BuilderVisitor extends NodeVisitor 1683 { 1684 /** 1685 * Visits the specified node before its children have been traversed. 1686 * 1687 * @param node the node to visit 1688 * @param key the current key 1689 */ 1690 @Override 1691 public void visitBeforeChildren(Node node, ConfigurationKey key) 1692 { 1693 Collection<ConfigurationNode> subNodes = new LinkedList<ConfigurationNode>(node.getChildren()); 1694 subNodes.addAll(node.getAttributes()); 1695 Iterator<ConfigurationNode> children = subNodes.iterator(); 1696 Node sibling1 = null; 1697 Node nd = null; 1698 1699 while (children.hasNext()) 1700 { 1701 // find the next new node 1702 do 1703 { 1704 sibling1 = nd; 1705 Object obj = children.next(); 1706 nd = getNodeFor(obj); 1707 } while (nd.getReference() != null && children.hasNext()); 1708 1709 if (nd.getReference() == null) 1710 { 1711 // find all following new nodes 1712 List<Node> newNodes = new LinkedList<Node>(); 1713 newNodes.add(nd); 1714 while (children.hasNext()) 1715 { 1716 Object obj = children.next(); 1717 nd = getNodeFor(obj); 1718 if (nd.getReference() == null) 1719 { 1720 newNodes.add(nd); 1721 } 1722 else 1723 { 1724 break; 1725 } 1726 } 1727 1728 // Insert all new nodes 1729 Node sibling2 = (nd.getReference() == null) ? null : nd; 1730 for (Node insertNode : newNodes) 1731 { 1732 if (insertNode.getReference() == null) 1733 { 1734 Object ref = insert(insertNode, node, sibling1, sibling2); 1735 if (ref != null) 1736 { 1737 insertNode.setReference(ref); 1738 } 1739 sibling1 = insertNode; 1740 } 1741 } 1742 } 1743 } 1744 } 1745 1746 /** 1747 * Inserts a new node into the structure constructed by this builder. 1748 * This method is called for each node that has been added to the 1749 * configuration tree after the configuration has been loaded from its 1750 * source. These new nodes have to be inserted into the original 1751 * structure. The passed in nodes define the position of the node to be 1752 * inserted: its parent and the siblings between to insert. The return 1753 * value is interpreted as the new reference of the affected 1754 * {@code Node} object; if it is not <b>null </b>, it is passed 1755 * to the node's {@code setReference()} method. 1756 * 1757 * @param newNode the node to be inserted 1758 * @param parent the parent node 1759 * @param sibling1 the sibling after which the node is to be inserted; 1760 * can be <b>null </b> if the new node is going to be the first child 1761 * node 1762 * @param sibling2 the sibling before which the node is to be inserted; 1763 * can be <b>null </b> if the new node is going to be the last child 1764 * node 1765 * @return the reference object for the node to be inserted 1766 */ 1767 protected abstract Object insert(Node newNode, Node parent, Node sibling1, Node sibling2); 1768 } 1769}