View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import java.io.Serializable;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Set;
27  import java.util.Stack;
28  
29  import org.apache.commons.collections.set.ListOrderedSet;
30  import org.apache.commons.collections.iterators.SingletonIterator;
31  import org.apache.commons.configuration.event.ConfigurationEvent;
32  import org.apache.commons.configuration.event.ConfigurationListener;
33  import org.apache.commons.configuration.tree.ConfigurationNode;
34  import org.apache.commons.configuration.tree.ConfigurationNodeVisitor;
35  import org.apache.commons.configuration.tree.ConfigurationNodeVisitorAdapter;
36  import org.apache.commons.configuration.tree.DefaultConfigurationNode;
37  import org.apache.commons.configuration.tree.DefaultExpressionEngine;
38  import org.apache.commons.configuration.tree.ExpressionEngine;
39  import org.apache.commons.configuration.tree.NodeAddData;
40  import org.apache.commons.lang.StringUtils;
41  
42  /**
43   * <p>A specialized configuration class that extends its base class by the
44   * ability of keeping more structure in the stored properties.</p><p>There
45   * are some sources of configuration data that cannot be stored very well in a
46   * <code>BaseConfiguration</code> object because then their structure is lost.
47   * This is especially true for XML documents. This class can deal with such
48   * structured configuration sources by storing the properties in a tree-like
49   * organization.</p><p>The internal used storage form allows for a more
50   * sophisticated access to single properties. As an example consider the
51   * following XML document:</p><p>
52   *
53   * <pre>
54   * &lt;database&gt;
55   *   &lt;tables&gt;
56   *     &lt;table&gt;
57   *       &lt;name&gt;users&lt;/name&gt;
58   *       &lt;fields&gt;
59   *         &lt;field&gt;
60   *           &lt;name&gt;lid&lt;/name&gt;
61   *           &lt;type&gt;long&lt;/name&gt;
62   *         &lt;/field&gt;
63   *         &lt;field&gt;
64   *           &lt;name&gt;usrName&lt;/name&gt;
65   *           &lt;type&gt;java.lang.String&lt;/type&gt;
66   *         &lt;/field&gt;
67   *        ...
68   *       &lt;/fields&gt;
69   *     &lt;/table&gt;
70   *     &lt;table&gt;
71   *       &lt;name&gt;documents&lt;/name&gt;
72   *       &lt;fields&gt;
73   *         &lt;field&gt;
74   *           &lt;name&gt;docid&lt;/name&gt;
75   *           &lt;type&gt;long&lt;/type&gt;
76   *         &lt;/field&gt;
77   *         ...
78   *       &lt;/fields&gt;
79   *     &lt;/table&gt;
80   *     ...
81   *   &lt;/tables&gt;
82   * &lt;/database&gt;
83   * </pre>
84   *
85   * </p><p>If this document is parsed and stored in a
86   * <code>HierarchicalConfiguration</code> object (which can be done by one of
87   * the sub classes), there are enhanced possibilities of accessing properties.
88   * The keys for querying information can contain indices that select a certain
89   * element if there are multiple hits.</p><p>For instance the key
90   * <code>tables.table(0).name</code> can be used to find out the name of the
91   * first table. In opposite <code>tables.table.name</code> would return a
92   * collection with the names of all available tables. Similarly the key
93   * <code>tables.table(1).fields.field.name</code> returns a collection with
94   * the names of all fields of the second table. If another index is added after
95   * the <code>field</code> element, a single field can be accessed:
96   * <code>tables.table(1).fields.field(0).name</code>.</p><p>There is a
97   * <code>getMaxIndex()</code> method that returns the maximum allowed index
98   * that can be added to a given property key. This method can be used to iterate
99   * 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 593319 2007-11-08 21:14:21Z 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 root node of this hierarchical configuration. This method
198      * exists for backwards compatibility only. New code should use the
199      * <code>{@link #getRootNode()}</code> method instead, which operates on
200      * the preferred data type <code>ConfigurationNode</code>.
201      *
202      * @return the root node
203      */
204     public Node getRoot()
205     {
206         return root;
207     }
208 
209     /**
210      * Sets the root node of this hierarchical configuration. This method
211      * exists for backwards compatibility only. New code should use the
212      * <code>{@link #setRootNode(ConfigurationNode)}</code> method instead,
213      * which operates on the preferred data type <code>ConfigurationNode</code>.
214      *
215      * @param node the root node
216      */
217     public void setRoot(Node node)
218     {
219         if (node == null)
220         {
221             throw new IllegalArgumentException("Root node must not be null!");
222         }
223         root = node;
224         rootNode = null;
225     }
226 
227     /**
228      * Returns the root node of this hierarchical configuration.
229      *
230      * @return the root node
231      * @since 1.3
232      */
233     public ConfigurationNode getRootNode()
234     {
235         return (rootNode != null) ? rootNode : root;
236     }
237 
238     /**
239      * Sets the root node of this hierarchical configuration.
240      *
241      * @param rootNode the root node
242      * @since 1.3
243      */
244     public void setRootNode(ConfigurationNode rootNode)
245     {
246         if (rootNode == null)
247         {
248             throw new IllegalArgumentException("Root node must not be null!");
249         }
250         this.rootNode = rootNode;
251 
252         // For backward compatibility also set the old root field.
253         root = (rootNode instanceof Node) ? (Node) rootNode : new Node(rootNode);
254     }
255 
256     /**
257      * Returns the default expression engine.
258      *
259      * @return the default expression engine
260      * @since 1.3
261      */
262     public static synchronized ExpressionEngine getDefaultExpressionEngine()
263     {
264         if (defaultExpressionEngine == null)
265         {
266             defaultExpressionEngine = new DefaultExpressionEngine();
267         }
268         return defaultExpressionEngine;
269     }
270 
271     /**
272      * Sets the default expression engine. This expression engine will be used
273      * if no specific engine was set for an instance. It is shared between all
274      * hierarchical configuration instances. So modifying its properties will
275      * impact all instances, for which no specific engine is set.
276      *
277      * @param engine the new default expression engine
278      * @since 1.3
279      */
280     public static synchronized void setDefaultExpressionEngine(ExpressionEngine engine)
281     {
282         if (engine == null)
283         {
284             throw new IllegalArgumentException(
285                     "Default expression engine must not be null!");
286         }
287         defaultExpressionEngine = engine;
288     }
289 
290     /**
291      * Returns the expression engine used by this configuration. This method
292      * will never return <b>null</b>; if no specific expression engine was set,
293      * the default expression engine will be returned.
294      *
295      * @return the current expression engine
296      * @since 1.3
297      */
298     public ExpressionEngine getExpressionEngine()
299     {
300         return (expressionEngine != null) ? expressionEngine
301                 : getDefaultExpressionEngine();
302     }
303 
304     /**
305      * Sets the expression engine to be used by this configuration. All property
306      * keys this configuration has to deal with will be interpreted by this
307      * engine.
308      *
309      * @param expressionEngine the new expression engine; can be <b>null</b>,
310      * then the default expression engine will be used
311      * @since 1.3
312      */
313     public void setExpressionEngine(ExpressionEngine expressionEngine)
314     {
315         this.expressionEngine = expressionEngine;
316     }
317 
318     /**
319      * Fetches the specified property. This task is delegated to the associated
320      * expression engine.
321      *
322      * @param key the key to be looked up
323      * @return the found value
324      */
325     public Object getProperty(String key)
326     {
327         List nodes = fetchNodeList(key);
328 
329         if (nodes.size() == 0)
330         {
331             return null;
332         }
333         else
334         {
335             List list = new ArrayList();
336             for (Iterator it = nodes.iterator(); it.hasNext();)
337             {
338                 ConfigurationNode node = (ConfigurationNode) it.next();
339                 if (node.getValue() != null)
340                 {
341                     list.add(node.getValue());
342                 }
343             }
344 
345             if (list.size() < 1)
346             {
347                 return null;
348             }
349             else
350             {
351                 return (list.size() == 1) ? list.get(0) : list;
352             }
353         }
354     }
355 
356     /**
357      * Adds the property with the specified key. This task will be delegated to
358      * the associated <code>ExpressionEngine</code>, so the passed in key
359      * must match the requirements of this implementation.
360      *
361      * @param key the key of the new property
362      * @param obj the value of the new property
363      */
364     protected void addPropertyDirect(String key, Object obj)
365     {
366         NodeAddData data = getExpressionEngine().prepareAdd(getRootNode(), key);
367         ConfigurationNode node = processNodeAddData(data);
368         node.setValue(obj);
369     }
370 
371     /**
372      * Adds a collection of nodes at the specified position of the configuration
373      * tree. This method works similar to <code>addProperty()</code>, but
374      * instead of a single property a whole collection of nodes can be added -
375      * and thus complete configuration sub trees. E.g. with this method it is
376      * possible to add parts of another <code>HierarchicalConfiguration</code>
377      * object to this object. (However be aware that a
378      * <code>ConfigurationNode</code> object can only belong to a single
379      * configuration. So if nodes from one configuration are directly added to
380      * another one using this method, the structure of the source configuration
381      * will be broken. In this case you should clone the nodes to be added
382      * before calling <code>addNodes()</code>.) If the passed in key refers to
383      * an existing and unique node, the new nodes are added to this node.
384      * Otherwise a new node will be created at the specified position in the
385      * hierarchy.
386      *
387      * @param key the key where the nodes are to be added; can be <b>null </b>,
388      * then they are added to the root node
389      * @param nodes a collection with the <code>Node</code> objects to be
390      * added
391      */
392     public void addNodes(String key, Collection nodes)
393     {
394         if (nodes == null || nodes.isEmpty())
395         {
396             return;
397         }
398 
399         fireEvent(EVENT_ADD_NODES, key, nodes, true);
400         ConfigurationNode parent;
401         List target = fetchNodeList(key);
402         if (target.size() == 1)
403         {
404             // existing unique key
405             parent = (ConfigurationNode) target.get(0);
406         }
407         else
408         {
409             // otherwise perform an add operation
410             parent = processNodeAddData(getExpressionEngine().prepareAdd(
411                     getRootNode(), key));
412         }
413 
414         if (parent.isAttribute())
415         {
416             throw new IllegalArgumentException(
417                     "Cannot add nodes to an attribute node!");
418         }
419 
420         // a visitor to ensure that the nodes' references are cleared; this is
421         // necessary if the nodes are moved from another configuration
422         ConfigurationNodeVisitor clearRefVisitor = new ConfigurationNodeVisitorAdapter()
423         {
424             public void visitBeforeChildren(ConfigurationNode node)
425             {
426                 node.setReference(null);
427             }
428         };
429 
430         for (Iterator it = nodes.iterator(); it.hasNext();)
431         {
432             ConfigurationNode child = (ConfigurationNode) it.next();
433             if (child.isAttribute())
434             {
435                 parent.addAttribute(child);
436             }
437             else
438             {
439                 parent.addChild(child);
440             }
441             child.visit(clearRefVisitor);
442         }
443         fireEvent(EVENT_ADD_NODES, key, nodes, false);
444     }
445 
446     /**
447      * Checks if this configuration is empty. Empty means that there are no keys
448      * with any values, though there can be some (empty) nodes.
449      *
450      * @return a flag if this configuration is empty
451      */
452     public boolean isEmpty()
453     {
454         return !nodeDefined(getRootNode());
455     }
456 
457     /**
458      * Creates a new <code>Configuration</code> object containing all keys
459      * that start with the specified prefix. This implementation will return a
460      * <code>HierarchicalConfiguration</code> object so that the structure of
461      * the keys will be saved. The nodes selected by the prefix (it is possible
462      * that multiple nodes are selected) are mapped to the root node of the
463      * returned configuration, i.e. their children and attributes will become
464      * children and attributes of the new root node. However a value of the root
465      * node is only set if exactly one of the selected nodes contain a value (if
466      * multiple nodes have a value, there is simply no way to decide how these
467      * values are merged together). Note that the returned
468      * <code>Configuration</code> object is not connected to its source
469      * configuration: updates on the source configuration are not reflected in
470      * the subset and vice versa.
471      *
472      * @param prefix the prefix of the keys for the subset
473      * @return a new configuration object representing the selected subset
474      */
475     public Configuration subset(String prefix)
476     {
477         Collection nodes = fetchNodeList(prefix);
478         if (nodes.isEmpty())
479         {
480             return new HierarchicalConfiguration();
481         }
482 
483         final HierarchicalConfiguration parent = this;
484         HierarchicalConfiguration result = new HierarchicalConfiguration()
485         {
486             // Override interpolate to always interpolate on the parent
487             protected Object interpolate(Object value)
488             {
489                 return parent.interpolate(value);
490             }
491         };
492         CloneVisitor visitor = new CloneVisitor();
493 
494         // Initialize the new root node
495         Object value = null;
496         int valueCount = 0;
497         for (Iterator it = nodes.iterator(); it.hasNext();)
498         {
499             ConfigurationNode nd = (ConfigurationNode) it.next();
500             if (nd.getValue() != null)
501             {
502                 value = nd.getValue();
503                 valueCount++;
504             }
505             nd.visit(visitor);
506 
507             for (Iterator it2 = visitor.getClone().getChildren().iterator(); it2
508                     .hasNext();)
509             {
510                 result.getRootNode().addChild((ConfigurationNode) it2.next());
511             }
512             for (Iterator it2 = visitor.getClone().getAttributes().iterator(); it2
513                     .hasNext();)
514             {
515                 result.getRootNode().addAttribute(
516                         (ConfigurationNode) it2.next());
517             }
518         }
519 
520         // Determine the value of the new root
521         if (valueCount == 1)
522         {
523             result.getRootNode().setValue(value);
524         }
525         return (result.isEmpty()) ? new HierarchicalConfiguration() : result;
526     }
527 
528     /**
529      * <p>
530      * Returns a hierarchical subnode configuration object that wraps the
531      * configuration node specified by the given key. This method provides an
532      * easy means of accessing sub trees of a hierarchical configuration. In the
533      * returned configuration the sub tree can directly be accessed, it becomes
534      * the root node of this configuration. Because of this the passed in key
535      * must select exactly one configuration node; otherwise an
536      * <code>IllegalArgumentException</code> will be thrown.
537      * </p>
538      * <p>
539      * The difference between this method and the
540      * <code>{@link #subset(String)}</code> method is that
541      * <code>subset()</code> supports arbitrary subsets of configuration nodes
542      * while <code>configurationAt()</code> only returns a single sub tree.
543      * Please refer to the documentation of the
544      * <code>SubnodeConfiguration</code> class to obtain further information
545      * about subnode configurations and when they should be used.
546      * </p>
547      * <p>
548      * With the <code>supportUpdate</code> flag the behavior of the returned
549      * <code>SubnodeConfiguration</code> regarding updates of its parent
550      * configuration can be determined. A subnode configuration operates on the
551      * same nodes as its parent, so changes at one configuration are normally
552      * directly visible for the other configuration. There are however changes
553      * of the parent configuration, which are not recognized by the subnode
554      * configuration per default. An example for this is a reload operation (for
555      * file-based configurations): Here the complete node set of the parent
556      * configuration is replaced, but the subnode configuration still references
557      * the old nodes. If such changes should be detected by the subnode
558      * configuration, the <code>supportUpdates</code> flag must be set to
559      * <b>true</b>. This causes the subnode configuration to reevaluate the key
560      * used for its creation each time it is accessed. This guarantees that the
561      * subnode configuration always stays in sync with its key, even if the
562      * parent configuration's data significantly changes. If such a change
563      * makes the key invalid - because it now no longer points to exactly one
564      * node -, the subnode configuration is not reconstructed, but keeps its
565      * old data. It is then quasi detached from its parent.
566      * </p>
567      *
568      * @param key the key that selects the sub tree
569      * @param supportUpdates a flag whether the returned subnode configuration
570      * should be able to handle updates of its parent
571      * @return a hierarchical configuration that contains this sub tree
572      * @see SubnodeConfiguration
573      * @since 1.5
574      */
575     public SubnodeConfiguration configurationAt(String key,
576             boolean supportUpdates)
577     {
578         List nodes = fetchNodeList(key);
579         if (nodes.size() != 1)
580         {
581             throw new IllegalArgumentException(
582                     "Passed in key must select exactly one node: " + key);
583         }
584         return supportUpdates ? createSubnodeConfiguration(
585                 (ConfigurationNode) nodes.get(0), key)
586                 : createSubnodeConfiguration((ConfigurationNode) nodes.get(0));
587     }
588 
589     /**
590      * Returns a hierarchical subnode configuration for the node specified by
591      * the given key. This is a short form for <code>configurationAt(key,
592      * <b>false</b>)</code>.
593      *
594      * @param key the key that selects the sub tree
595      * @return a hierarchical configuration that contains this sub tree
596      * @see SubnodeConfiguration
597      * @since 1.3
598      */
599     public SubnodeConfiguration configurationAt(String key)
600     {
601         return configurationAt(key, false);
602     }
603 
604     /**
605      * Returns a list of sub configurations for all configuration nodes selected
606      * by the given key. This method will evaluate the passed in key (using the
607      * current <code>ExpressionEngine</code>) and then create a subnode
608      * configuration for each returned node (like
609      * <code>{@link #configurationAt(String)}</code>}). This is especially
610      * useful when dealing with list-like structures. As an example consider the
611      * configuration that contains data about database tables and their fields.
612      * If you need access to all fields of a certain table, you can simply do
613      *
614      * <pre>
615      * List fields = config.configurationsAt("tables.table(0).fields.field");
616      * for(Iterator it = fields.iterator(); it.hasNext();)
617      * {
618      *     HierarchicalConfiguration sub = (HierarchicalConfiguration) it.next();
619      *     // now the children and attributes of the field node can be
620      *     // directly accessed
621      *     String fieldName = sub.getString("name");
622      *     String fieldType = sub.getString("type");
623      *     ...
624      * </pre>
625      *
626      * @param key the key for selecting the desired nodes
627      * @return a list with hierarchical configuration objects; each
628      * configuration represents one of the nodes selected by the passed in key
629      * @since 1.3
630      */
631     public List configurationsAt(String key)
632     {
633         List nodes = fetchNodeList(key);
634         List configs = new ArrayList(nodes.size());
635         for (Iterator it = nodes.iterator(); it.hasNext();)
636         {
637             configs.add(createSubnodeConfiguration((ConfigurationNode) it.next()));
638         }
639         return configs;
640     }
641 
642     /**
643      * Creates a subnode configuration for the specified node. This method is
644      * called by <code>configurationAt()</code> and
645      * <code>configurationsAt()</code>.
646      *
647      * @param node the node, for which a subnode configuration is to be created
648      * @return the configuration for the given node
649      * @since 1.3
650      */
651     protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node)
652     {
653         SubnodeConfiguration result = new SubnodeConfiguration(this, node);
654         registerSubnodeConfiguration(result);
655         return result;
656     }
657 
658     /**
659      * Creates a new subnode configuration for the specified node and sets its
660      * construction key. A subnode configuration created this way will be aware
661      * of structural changes of its parent.
662      *
663      * @param node the node, for which a subnode configuration is to be created
664      * @param subnodeKey the key used to construct the configuration
665      * @return the configuration for the given node
666      * @since 1.5
667      */
668     protected SubnodeConfiguration createSubnodeConfiguration(
669             ConfigurationNode node, String subnodeKey)
670     {
671         SubnodeConfiguration result = createSubnodeConfiguration(node);
672         result.setSubnodeKey(subnodeKey);
673         return result;
674     }
675 
676     /**
677      * This method is always called when a subnode configuration created from
678      * this configuration has been modified. This implementation transforms the
679      * received event into an event of type <code>EVENT_SUBNODE_CHANGED</code>
680      * and notifies the registered listeners.
681      *
682      * @param event the event describing the change
683      * @since 1.5
684      */
685     protected void subnodeConfigurationChanged(ConfigurationEvent event)
686     {
687         fireEvent(EVENT_SUBNODE_CHANGED, null, event, event.isBeforeUpdate());
688     }
689 
690     /**
691      * Registers this instance at the given subnode configuration. This
692      * implementation will register a change listener, so that modifications of
693      * the subnode configuration can be tracked.
694      *
695      * @param config the subnode configuration
696      * @since 1.5
697      */
698     void registerSubnodeConfiguration(SubnodeConfiguration config)
699     {
700         config.addConfigurationListener(new ConfigurationListener()
701         {
702             public void configurationChanged(ConfigurationEvent event)
703             {
704                 subnodeConfigurationChanged(event);
705             }
706         });
707     }
708 
709     /**
710      * Checks if the specified key is contained in this configuration. Note that
711      * for this configuration the term &quot;contained&quot; means that the key
712      * has an associated value. If there is a node for this key that has no
713      * value but children (either defined or undefined), this method will still
714      * return <b>false </b>.
715      *
716      * @param key the key to be chekced
717      * @return a flag if this key is contained in this configuration
718      */
719     public boolean containsKey(String key)
720     {
721         return getProperty(key) != null;
722     }
723 
724     /**
725      * Sets the value of the specified property.
726      *
727      * @param key the key of the property to set
728      * @param value the new value of this property
729      */
730     public void setProperty(String key, Object value)
731     {
732         fireEvent(EVENT_SET_PROPERTY, key, value, true);
733 
734         // Update the existing nodes for this property
735         Iterator itNodes = fetchNodeList(key).iterator();
736         Iterator itValues;
737         if (!isDelimiterParsingDisabled())
738         {
739             itValues = PropertyConverter.toIterator(value, getListDelimiter());
740         }
741         else
742         {
743             itValues = new SingletonIterator(value);
744         }
745 
746         while (itNodes.hasNext() && itValues.hasNext())
747         {
748             ((ConfigurationNode) itNodes.next()).setValue(itValues.next());
749         }
750 
751         // Add additional nodes if necessary
752         while (itValues.hasNext())
753         {
754             addPropertyDirect(key, itValues.next());
755         }
756 
757         // Remove remaining nodes
758         while (itNodes.hasNext())
759         {
760             clearNode((ConfigurationNode) itNodes.next());
761         }
762 
763         fireEvent(EVENT_SET_PROPERTY, key, value, false);
764     }
765 
766     /**
767      * Removes all values of the property with the given name and of keys that
768      * start with this name. So if there is a property with the key
769      * &quot;foo&quot; and a property with the key &quot;foo.bar&quot;, a call
770      * of <code>clearTree("foo")</code> would remove both properties.
771      *
772      * @param key the key of the property to be removed
773      */
774     public void clearTree(String key)
775     {
776         fireEvent(EVENT_CLEAR_TREE, key, null, true);
777         List nodes = fetchNodeList(key);
778 
779         for (Iterator it = nodes.iterator(); it.hasNext();)
780         {
781             removeNode((ConfigurationNode) it.next());
782         }
783         fireEvent(EVENT_CLEAR_TREE, key, nodes, false);
784     }
785 
786     /**
787      * Removes the property with the given key. Properties with names that start
788      * with the given key (i.e. properties below the specified key in the
789      * hierarchy) won't be affected.
790      *
791      * @param key the key of the property to be removed
792      */
793     public void clearProperty(String key)
794     {
795         fireEvent(EVENT_CLEAR_PROPERTY, key, null, true);
796         List nodes = fetchNodeList(key);
797 
798         for (Iterator it = nodes.iterator(); it.hasNext();)
799         {
800             clearNode((ConfigurationNode) it.next());
801         }
802 
803         fireEvent(EVENT_CLEAR_PROPERTY, key, null, false);
804     }
805 
806     /**
807      * Returns an iterator with all keys defined in this configuration.
808      * Note that the keys returned by this method will not contain any
809      * indices. This means that some structure will be lost.</p>
810      *
811      * @return an iterator with the defined keys in this configuration
812      */
813     public Iterator getKeys()
814     {
815         DefinedKeysVisitor visitor = new DefinedKeysVisitor();
816         getRootNode().visit(visitor);
817 
818         return visitor.getKeyList().iterator();
819     }
820 
821     /**
822      * Returns an iterator with all keys defined in this configuration that
823      * start with the given prefix. The returned keys will not contain any
824      * indices.
825      *
826      * @param prefix the prefix of the keys to start with
827      * @return an iterator with the found keys
828      */
829     public Iterator getKeys(String prefix)
830     {
831         DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
832         List nodes = fetchNodeList(prefix);
833 
834         for (Iterator itNodes = nodes.iterator(); itNodes.hasNext();)
835         {
836             ConfigurationNode node = (ConfigurationNode) itNodes.next();
837             for (Iterator it = node.getChildren().iterator(); it.hasNext();)
838             {
839                 ((ConfigurationNode) it.next()).visit(visitor);
840             }
841             for (Iterator it = node.getAttributes().iterator(); it.hasNext();)
842             {
843                 ((ConfigurationNode) it.next()).visit(visitor);
844             }
845         }
846 
847         return visitor.getKeyList().iterator();
848     }
849 
850     /**
851      * Returns the maximum defined index for the given key. This is useful if
852      * there are multiple values for this key. They can then be addressed
853      * separately by specifying indices from 0 to the return value of this
854      * method.
855      *
856      * @param key the key to be checked
857      * @return the maximum defined index for this key
858      */
859     public int getMaxIndex(String key)
860     {
861         return fetchNodeList(key).size() - 1;
862     }
863 
864     /**
865      * Creates a copy of this object. This new configuration object will contain
866      * copies of all nodes in the same structure. Registered event listeners
867      * won't be cloned; so they are not registered at the returned copy.
868      *
869      * @return the copy
870      * @since 1.2
871      */
872     public Object clone()
873     {
874         try
875         {
876             HierarchicalConfiguration copy = (HierarchicalConfiguration) super
877                     .clone();
878 
879             // clone the nodes, too
880             CloneVisitor v = new CloneVisitor();
881             getRootNode().visit(v);
882             copy.setRootNode(v.getClone());
883 
884             return copy;
885         }
886         catch (CloneNotSupportedException cex)
887         {
888             // should not happen
889             throw new ConfigurationRuntimeException(cex);
890         }
891     }
892 
893     /**
894      * Returns a configuration with the same content as this configuration, but
895      * with all variables replaced by their actual values. This implementation
896      * is specific for hierarchical configurations. It clones the current
897      * configuration and runs a specialized visitor on the clone, which performs
898      * interpolation on the single configuration nodes.
899      *
900      * @return a configuration with all variables interpolated
901      * @since 1.5
902      */
903     public Configuration interpolatedConfiguration()
904     {
905         HierarchicalConfiguration c = (HierarchicalConfiguration) clone();
906         c.getRootNode().visit(new ConfigurationNodeVisitorAdapter()
907         {
908             public void visitAfterChildren(ConfigurationNode node)
909             {
910                 node.setValue(interpolate(node.getValue()));
911             }
912         });
913         return c;
914     }
915 
916     /**
917      * Helper method for fetching a list of all nodes that are addressed by the
918      * specified key.
919      *
920      * @param key the key
921      * @return a list with all affected nodes (never <b>null </b>)
922      */
923     protected List fetchNodeList(String key)
924     {
925         return getExpressionEngine().query(getRootNode(), key);
926     }
927 
928     /**
929      * Recursive helper method for fetching a property. This method processes
930      * all facets of a configuration key, traverses the tree of properties and
931      * fetches the the nodes of all matching properties.
932      *
933      * @param keyPart the configuration key iterator
934      * @param node the actual node
935      * @param nodes here the found nodes are stored
936      * @deprecated Property keys are now evaluated by the expression engine
937      * associated with the configuration; this method will no longer be called.
938      * If you want to modify the way properties are looked up, consider
939      * implementing you own <code>ExpressionEngine</code> implementation.
940      */
941     protected void findPropertyNodes(ConfigurationKey.KeyIterator keyPart,
942             Node node, Collection nodes)
943     {
944     }
945 
946     /**
947      * Checks if the specified node is defined.
948      *
949      * @param node the node to be checked
950      * @return a flag if this node is defined
951      * @deprecated Use the method <code>{@link #nodeDefined(ConfigurationNode)}</code>
952      * instead.
953      */
954     protected boolean nodeDefined(Node node)
955     {
956         return nodeDefined((ConfigurationNode) node);
957     }
958 
959     /**
960      * Checks if the specified node is defined.
961      *
962      * @param node the node to be checked
963      * @return a flag if this node is defined
964      */
965     protected boolean nodeDefined(ConfigurationNode node)
966     {
967         DefinedVisitor visitor = new DefinedVisitor();
968         node.visit(visitor);
969         return visitor.isDefined();
970     }
971 
972     /**
973      * Removes the specified node from this configuration. This method ensures
974      * that parent nodes that become undefined by this operation are also
975      * removed.
976      *
977      * @param node the node to be removed
978      * @deprecated Use the method <code>{@link #removeNode(ConfigurationNode)}</code>
979      * instead.
980      */
981     protected void removeNode(Node node)
982     {
983         removeNode((ConfigurationNode) node);
984     }
985 
986     /**
987      * Removes the specified node from this configuration. This method ensures
988      * that parent nodes that become undefined by this operation are also
989      * removed.
990      *
991      * @param node the node to be removed
992      */
993     protected void removeNode(ConfigurationNode node)
994     {
995         ConfigurationNode parent = node.getParentNode();
996         if (parent != null)
997         {
998             parent.removeChild(node);
999             if (!nodeDefined(parent))
1000             {
1001                 removeNode(parent);
1002             }
1003         }
1004     }
1005 
1006     /**
1007      * Clears the value of the specified node. If the node becomes undefined by
1008      * this operation, it is removed from the hierarchy.
1009      *
1010      * @param node the node to be cleared
1011      * @deprecated Use the method <code>{@link #clearNode(ConfigurationNode)}</code>
1012      * instead
1013      */
1014     protected void clearNode(Node node)
1015     {
1016         clearNode((ConfigurationNode) node);
1017     }
1018 
1019     /**
1020      * Clears the value of the specified node. If the node becomes undefined by
1021      * this operation, it is removed from the hierarchy.
1022      *
1023      * @param node the node to be cleared
1024      */
1025     protected void clearNode(ConfigurationNode node)
1026     {
1027         node.setValue(null);
1028         if (!nodeDefined(node))
1029         {
1030             removeNode(node);
1031         }
1032     }
1033 
1034     /**
1035      * Returns a reference to the parent node of an add operation. Nodes for new
1036      * properties can be added as children of this node. If the path for the
1037      * specified key does not exist so far, it is created now.
1038      *
1039      * @param keyIt the iterator for the key of the new property
1040      * @param startNode the node to start the search with
1041      * @return the parent node for the add operation
1042      * @deprecated Adding new properties is now to a major part delegated to the
1043      * <code>ExpressionEngine</code> associated with this configuration instance.
1044      * This method will no longer be called. Developers who want to modify the
1045      * process of adding new properties should consider implementing their own
1046      * expression engine.
1047      */
1048     protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt, Node startNode)
1049     {
1050         return null;
1051     }
1052 
1053     /**
1054      * Finds the last existing node for an add operation. This method traverses
1055      * the configuration tree along the specified key. The last existing node on
1056      * this path is returned.
1057      *
1058      * @param keyIt the key iterator
1059      * @param node the actual node
1060      * @return the last existing node on the given path
1061      * @deprecated Adding new properties is now to a major part delegated to the
1062      * <code>ExpressionEngine</code> associated with this configuration instance.
1063      * This method will no longer be called. Developers who want to modify the
1064      * process of adding new properties should consider implementing their own
1065      * expression engine.
1066      */
1067     protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt, Node node)
1068     {
1069         return null;
1070     }
1071 
1072     /**
1073      * Creates the missing nodes for adding a new property. This method ensures
1074      * that there are corresponding nodes for all components of the specified
1075      * configuration key.
1076      *
1077      * @param keyIt the key iterator
1078      * @param root the base node of the path to be created
1079      * @return the last node of the path
1080      * @deprecated Adding new properties is now to a major part delegated to the
1081      * <code>ExpressionEngine</code> associated with this configuration instance.
1082      * This method will no longer be called. Developers who want to modify the
1083      * process of adding new properties should consider implementing their own
1084      * expression engine.
1085      */
1086     protected Node createAddPath(ConfigurationKey.KeyIterator keyIt, Node root)
1087     {
1088         return null;
1089     }
1090 
1091     /**
1092      * Creates a new <code>Node</code> object with the specified name. This
1093      * method can be overloaded in derived classes if a specific node type is
1094      * needed. This base implementation always returns a new object of the
1095      * <code>Node</code> class.
1096      *
1097      * @param name the name of the new node
1098      * @return the new node
1099      */
1100     protected Node createNode(String name)
1101     {
1102         return new Node(name);
1103     }
1104 
1105     /**
1106      * Helper method for processing a node add data object obtained from the
1107      * expression engine. This method will create all new nodes.
1108      *
1109      * @param data the data object
1110      * @return the new node
1111      * @since 1.3
1112      */
1113     private ConfigurationNode processNodeAddData(NodeAddData data)
1114     {
1115         ConfigurationNode node = data.getParent();
1116 
1117         // Create missing nodes on the path
1118         for (Iterator it = data.getPathNodes().iterator(); it.hasNext();)
1119         {
1120             ConfigurationNode child = createNode((String) it.next());
1121             node.addChild(child);
1122             node = child;
1123         }
1124 
1125         // Add new target node
1126         ConfigurationNode child = createNode(data.getNewNodeName());
1127         if (data.isAttribute())
1128         {
1129             node.addAttribute(child);
1130         }
1131         else
1132         {
1133             node.addChild(child);
1134         }
1135         return child;
1136     }
1137 
1138     /**
1139      * Clears all reference fields in a node structure. A configuration node can
1140      * store a so-called &quot;reference&quot;. The meaning of this data is
1141      * determined by a concrete sub class. Typically such references are
1142      * specific for a configuration instance. If this instance is cloned or
1143      * copied, they must be cleared. This can be done using this method.
1144      *
1145      * @param node the root node of the node hierarchy, in which the references
1146      * are to be cleared
1147      * @since 1.4
1148      */
1149     protected static void clearReferences(ConfigurationNode node)
1150     {
1151         node.visit(new ConfigurationNodeVisitorAdapter()
1152         {
1153             public void visitBeforeChildren(ConfigurationNode node)
1154             {
1155                 node.setReference(null);
1156             }
1157         });
1158     }
1159 
1160     /**
1161      * A data class for storing (hierarchical) property information. A property
1162      * can have a value and an arbitrary number of child properties. From
1163      * version 1.3 on this class is only a thin wrapper over the
1164      * <code>{@link org.apache.commons.configuration.tree.DefaultConfigurationNode DefaultconfigurationNode}</code>
1165      * class that exists mainly for the purpose of backwards compatibility.
1166      */
1167     public static class Node extends DefaultConfigurationNode implements Serializable
1168     {
1169         /**
1170          * The serial version UID.
1171          */
1172         private static final long serialVersionUID = -6357500633536941775L;
1173 
1174         /**
1175          * Creates a new instance of <code>Node</code>.
1176          */
1177         public Node()
1178         {
1179             super();
1180         }
1181 
1182         /**
1183          * Creates a new instance of <code>Node</code> and sets the name.
1184          *
1185          * @param name the node's name
1186          */
1187         public Node(String name)
1188         {
1189             super(name);
1190         }
1191 
1192         /**
1193          * Creates a new instance of <code>Node</code> and sets the name and the value.
1194          *
1195          * @param name the node's name
1196          * @param value the value
1197          */
1198         public Node(String name, Object value)
1199         {
1200             super(name, value);
1201         }
1202 
1203         /**
1204          * Creates a new instance of <code>Node</code> based on the given
1205          * source node. All properties of the source node, including its
1206          * children and attributes, will be copied.
1207          *
1208          * @param src the node to be copied
1209          */
1210         public