View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.configuration;
18  
19  import java.io.Serializable;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.HashSet;
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.map.LinkedMap;
30  import org.apache.commons.lang.StringUtils;
31  
32  /***
33   * <p>A specialized configuration class that extends its base class by the
34   * ability of keeping more structure in the stored properties.</p>
35   * <p>There are some sources of configuration data that cannot be stored
36   * very well in a <code>BaseConfiguration</code> object because then their
37   * structure is lost. This is especially true for XML documents. This class
38   * can deal with such structured configuration sources by storing the
39   * properties in a tree-like organization.</p>
40   * <p>The internal used storage form allows for a more sophisticated access to
41   * single properties. As an example consider the following XML document:</p>
42   * <p><pre>
43   * &lt;database&gt;
44   *   &lt;tables&gt;
45   *     &lt;table&gt;
46   *       &lt;name&gt;users&lt;/name&gt;
47   *       &lt;fields&gt;
48   *         &lt;field&gt;
49   *           &lt;name&gt;lid&lt;/name&gt;
50   *           &lt;type&gt;long&lt;/name&gt;
51   *         &lt;/field&gt;
52   *         &lt;field&gt;
53   *           &lt;name&gt;usrName&lt;/name&gt;
54   *           &lt;type&gt;java.lang.String&lt;/type&gt;
55   *         &lt;/field&gt;
56   *        ...
57   *       &lt;/fields&gt;
58   *     &lt;/table&gt;
59   *     &lt;table&gt;
60   *       &lt;name&gt;documents&lt;/name&gt;
61   *       &lt;fields&gt;
62   *         &lt;field&gt;
63   *           &lt;name&gt;docid&lt;/name&gt;
64   *           &lt;type&gt;long&lt;/type&gt;
65   *         &lt;/field&gt;
66   *         ...
67   *       &lt;/fields&gt;
68   *     &lt;/table&gt;
69   *     ...
70   *   &lt;/tables&gt;
71   * &lt;/database&gt;
72   * </pre></p>
73   * <p>If this document is parsed and stored in a
74   * <code>HierarchicalConfiguration</code> object (which can be done by one of
75   * the sub classes), there are enhanced possibilities of accessing properties.
76   * The keys for querying information can contain indices that select a certain
77   * element if there are multiple hits.</p>
78   * <p>For instance the key <code>tables.table(0).name</code> can be used to
79   * find out the name of the first table. In opposite
80   * <code>tables.table.name</code> would return a collection with the names of
81   * all available tables. Similarily the key
82   * <code>tables.table(1).fields.field.name</code> returns a collection with the
83   * names of all fields of the second table. If another index is added after the
84   * <code>field</code> element, a single field can be accessed:
85   * <code>tables.table(1).fields.field(0).name</code>.</p>
86   * <p>There is a <code>getMaxIndex()</code> method that returns the maximum
87   * allowed index that can be added to a given property key. This method can be
88   * used to iterate over all values defined for a certain property.</p>
89   *
90   * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
91   * @version $Id: HierarchicalConfiguration.java,v 1.11 2004/10/11 09:27:20 henning Exp $
92   */
93  public class HierarchicalConfiguration extends AbstractConfiguration
94  {
95      /*** Constant for a new dummy key.*/
96      private static final String NEW_KEY = "newKey";
97  
98      /*** Stores the root node of this configuration.*/
99      private Node root = new Node();
100 
101     /***
102      * Creates a new instance of <code>HierarchicalConfiguration</code>.
103      */
104     public HierarchicalConfiguration()
105     {
106         super();
107     }
108 
109     /***
110      * Returns the root node of this hierarchical configuration.
111      *
112      * @return the root node
113      */
114     public Node getRoot()
115     {
116         return root;
117     }
118 
119     /***
120      * Sets the root node of this hierarchical configuration.
121      *
122      * @param node the root node
123      */
124     public void setRoot(Node node)
125     {
126         if (node == null)
127         {
128             throw new IllegalArgumentException("Root node must not be null!");
129         }
130         root = node;
131     }
132 
133     /***
134      * Fetches the specified property. Performs a recursive lookup in the
135      * tree with the configuration properties.
136      *
137      * @param key the key to be looked up
138      * @return the found value
139      */
140     protected Object getPropertyDirect(String key)
141     {
142         List nodes = fetchNodeList(key);
143 
144         if (nodes.size() == 0)
145         {
146             return null;
147         }
148         else
149         {
150             List list = new ArrayList();
151             for (Iterator it = nodes.iterator(); it.hasNext();)
152             {
153                 Node node = (Node) it.next();
154                 if (node.getValue() != null)
155                 {
156                     list.add(node.getValue());
157                 }
158             }
159 
160             if (list.size() < 1)
161             {
162                 return null;
163             }
164             else
165             {
166                 return (list.size() == 1) ? list.get(0) : list;
167             }
168         }
169     }
170 
171     /***
172      * <p>Adds the property with the specified key.</p>
173      * <p>To be able to deal with the structure supported by this configuration
174      * implementation the passed in key is of importance, especially the
175      * indices it might contain. The following example should clearify this:
176      * Suppose the actual configuration contains the following elements:</p>
177      * <p><pre>
178      * tables
179      *    +-- table
180      *            +-- name = user
181      *            +-- fields
182      *                    +-- field
183      *                            +-- name = uid
184      *                    +-- field
185      *                            +-- name = firstName
186      *                    ...
187      *    +-- table
188      *            +-- name = documents
189      *            +-- fields
190      *                   ...
191      * </pre></p>
192      * <p>In this example a database structure is defined, e.g. all fields of
193      * the first table could be accessed using the key
194      * <code>tables.table(0).fields.field.name</code>. If now properties are
195      * to be added, it must be exactly specified at which position in the
196      * hierarchy the new property is to be inserted. So to add a new field name
197      * to a table it is not enough to say just</p>
198      * <p><pre>
199      * config.addProperty("tables.table.fields.field.name", "newField");
200      * </pre></p>
201      * <p>The statement given above contains some ambiguity. For instance
202      * it is not clear, to which table the new field should be added. If this
203      * method finds such an ambiguity, it is resolved by following the last
204      * valid path. Here this would be the last table. The same is true for the
205      * <code>field</code>; because there are multiple fields and no explicit
206      * index is provided, a new <code>name</code> property would be
207      * added to the last field - which is propably not what was desired.</p>
208      * <p>To make things clear explicit indices should be provided whenever
209      * possible. In the example above the exact table could be specified by
210      * providing an index for the <code>table</code> element as in
211      * <code>tables.table(1).fields</code>. By specifying an index it can also
212      * be expressed that at a given position in the configuration tree a new
213      * branch should be added. In the example above we did not want to add
214      * an additional <code>name</code> element to the last field of the table,
215      * but we want a complete new <code>field</code> element. This can be
216      * achieved by specifying an invalid index (like -1) after the element
217      * where a new branch should be created. Given this our example would run:
218      * </p><p><pre>
219      * config.addProperty("tables.table(1).fields.field(-1).name", "newField");
220      * </pre></p>
221      * <p>With this notation it is possible to add new branches everywhere.
222      * We could for instance create a new <code>table</code> element by
223      * specifying</p>
224      * <p><pre>
225      * config.addProperty("tables.table(-1).fields.field.name", "newField2");
226      * </pre></p>
227      * <p>(Note that because after the <code>table</code> element a new
228      * branch is created indices in following elements are not relevant; the
229      * branch is new so there cannot be any ambiguities.)</p>
230      *
231      * @param key the key of the new property
232      * @param obj the value of the new property
233      */
234     protected void addPropertyDirect(String key, Object obj)
235     {
236         ConfigurationKey.KeyIterator it = new ConfigurationKey(key).iterator();
237         Node parent = fetchAddNode(it, getRoot());
238 
239         Node child = new Node(it.currentKey(true));
240         child.setValue(obj);
241         parent.addChild(child);
242     }
243 
244     /***
245      * Adds a collection of nodes at the specified position of the
246      * configuration tree. This method works similar to
247      * <code>addProperty()</code>, but instead of a single property a whole
248      * collection of nodes can be added - and thus complete configuration
249      * sub trees. E.g. with this method it is possible to add parts of
250      * another <code>HierarchicalConfiguration</code> object to this object.
251      *
252      * @param key the key where the nodes are to be added; can be <b>null</b>,
253      * then they are added to the root node
254      * @param nodes a collection with the <code>Node</code> objects to be
255      * added
256      */
257     public void addNodes(String key, Collection nodes)
258     {
259         if (nodes == null || nodes.isEmpty())
260         {
261             return;
262         }
263 
264         Node parent;
265         if (StringUtils.isEmpty(key))
266         {
267             parent = getRoot();
268         }
269         else
270         {
271             ConfigurationKey.KeyIterator kit =
272                 new ConfigurationKey(key).iterator();
273             parent = fetchAddNode(kit, getRoot());
274 
275             // fetchAddNode() does not really fetch the last component,
276             // but one before. So we must perform an additional step.
277             ConfigurationKey keyNew =
278                 new ConfigurationKey(kit.currentKey(true));
279             keyNew.append(NEW_KEY);
280             parent = fetchAddNode(keyNew.iterator(), parent);
281         }
282 
283         for (Iterator it = nodes.iterator(); it.hasNext();)
284         {
285             parent.addChild((Node) it.next());
286         }
287     }
288 
289     /***
290      * Checks if this configuration is empty. Empty means that there are
291      * no keys with any values, though there can be some (empty) nodes.
292      *
293      * @return a flag if this configuration is empty
294      */
295     public boolean isEmpty()
296     {
297         return !nodeDefined(getRoot());
298     }
299 
300     /***
301      * Creates a new <code>Configuration</code> object containing all keys
302      * that start with the specified prefix. This implementation will return
303      * a <code>HierarchicalConfiguration</code> object so that the structure
304      * of the keys will be saved.
305      * @param prefix the prefix of the keys for the subset
306      * @return a new configuration object representing the selected subset
307      */
308     public Configuration subset(String prefix)
309     {
310         Collection nodes = fetchNodeList(prefix);
311         if (nodes.isEmpty())
312         {
313             return new HierarchicalConfiguration();
314         }
315 
316         HierarchicalConfiguration result = new HierarchicalConfiguration();
317         CloneVisitor visitor = new CloneVisitor();
318 
319         for (Iterator it = nodes.iterator(); it.hasNext();)
320         {
321             Node nd = (Node) it.next();
322             nd.visit(visitor, null);
323 
324             List children = visitor.getClone().getChildren();
325             if (children.size() > 0)
326             {
327                 for (int i = 0; i < children.size(); i++)
328                 {
329                     result.getRoot().addChild((Node) children.get(i));
330                 }
331             }
332         }
333 
334         return (result.isEmpty()) ? new HierarchicalConfiguration() : result;
335     }
336 
337     /***
338      * Checks if the specified key is contained in this configuration.
339      * Note that for this configuration the term &quot;contained&quot; means
340      * that the key has an associated value. If there is a node for this key
341      * that has no value but children (either defined or undefined), this
342      * method will still return <b>false</b>.
343      *
344      * @param key the key to be chekced
345      * @return a flag if this key is contained in this configuration
346      */
347     public boolean containsKey(String key)
348     {
349         return getPropertyDirect(key) != null;
350     }
351 
352     /***
353      * Removes all values of the property with the given name.
354      *
355      * @param key the key of the property to be removed
356      */
357     public void clearProperty(String key)
358     {
359         List nodes = fetchNodeList(key);
360 
361         for (Iterator it = nodes.iterator(); it.hasNext();)
362         {
363             removeNode((Node) it.next());
364         }
365     }
366 
367     /***
368      * <p>Returns an iterator with all keys defined in this configuration.</p>
369      * <p>Note that the keys returned by this method will not contain
370      * any indices. This means that some structure will be lost.</p>
371      *
372      * @return an iterator with the defined keys in this configuration
373      */
374     public Iterator getKeys()
375     {
376         DefinedKeysVisitor visitor = new DefinedKeysVisitor();
377         getRoot().visit(visitor, new ConfigurationKey());
378         return visitor.getKeyList().iterator();
379     }
380 
381     /***
382      * Returns the maximum defined index for the given key. This is
383      * useful if there are multiple values for this key. They can then be
384      * addressed separately by specifying indices from 0 to the return value
385      * of this method.
386      *
387      * @param key the key to be checked
388      * @return the maximum defined index for this key
389      */
390     public int getMaxIndex(String key)
391     {
392         return fetchNodeList(key).size() - 1;
393     }
394 
395     /***
396      * Helper method for fetching a list of all nodes that are addressed by
397      * the specified key.
398      *
399      * @param key the key
400      * @return a list with all affected nodes (never <b>null</b>)
401      */
402     protected List fetchNodeList(String key)
403     {
404         List nodes = new LinkedList();
405         findPropertyNodes(
406             new ConfigurationKey(key).iterator(),
407             getRoot(),
408             nodes);
409         return nodes;
410     }
411 
412     /***
413      * Recursive helper method for fetching a property. This method
414      * processes all facets of a configuration key, traverses the tree of
415      * properties and fetches the the nodes of all matching properties.
416      *
417      * @param keyPart the configuration key iterator
418      * @param node the actual node
419      * @param data here the found nodes are stored
420      */
421     protected void findPropertyNodes(
422         ConfigurationKey.KeyIterator keyPart,
423         Node node,
424         Collection data)
425     {
426         if (!keyPart.hasNext())
427         {
428             data.add(node);
429         }
430         else
431         {
432             String key = keyPart.nextKey(true);
433             List children = node.getChildren(key);
434             if (keyPart.hasIndex())
435             {
436                 if (keyPart.getIndex() < children.size()
437                     && keyPart.getIndex() >= 0)
438                 {
439                     findPropertyNodes(
440                         (ConfigurationKey.KeyIterator) keyPart.clone(),
441                         (Node) children.get(keyPart.getIndex()),
442                         data);
443                 }
444             }
445             else
446             {
447                 for (Iterator it = children.iterator(); it.hasNext();)
448                 {
449                     findPropertyNodes(
450                         (ConfigurationKey.KeyIterator) keyPart.clone(),
451                         (Node) it.next(),
452                         data);
453                 }
454             }
455         }
456     }
457 
458     /***
459      * Checks if the specified node is defined.
460      *
461      * @param node the node to be checked
462      * @return a flag if this node is defined
463      */
464     protected boolean nodeDefined(Node node)
465     {
466         DefinedVisitor visitor = new DefinedVisitor();
467         node.visit(visitor, null);
468         return visitor.isDefined();
469     }
470 
471     /***
472      * Removes the specified node from this configuration. This method
473      * ensures that parent nodes that become undefined by this operation
474      * are also removed.
475      *
476      * @param node the node to be removed
477      */
478     protected void removeNode(Node node)
479     {
480         Node parent = node.getParent();
481         if (parent != null)
482         {
483             parent.remove(node);
484             if (!nodeDefined(parent))
485             {
486                 removeNode(parent);
487             }
488         }
489     }
490 
491     /***
492      * Returns a reference to the parent node of an add operation.
493      * Nodes for new properties can be added as children of this node.
494      * If the path for the specified key does not exist so far, it is created
495      * now.
496      *
497      * @param keyIt the iterator for the key of the new property
498      * @param startNode the node to start the search with
499      * @return the parent node for the add operation
500      */
501     protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt, Node startNode)
502     {
503         if (!keyIt.hasNext())
504         {
505             throw new IllegalArgumentException("Key must be defined!");
506         }
507 
508         return createAddPath(keyIt, findLastPathNode(keyIt, startNode));
509     }
510 
511     /***
512      * Finds the last existing node for an add operation. This method
513      * traverses the configuration tree along the specified key. The last
514      * existing node on this path is returned.
515      *
516      * @param keyIt the key iterator
517      * @param node the actual node
518      * @return the last existing node on the given path
519      */
520     protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt, Node node)
521     {
522         String keyPart = keyIt.nextKey(true);
523 
524         if (keyIt.hasNext())
525         {
526             List list = node.getChildren(keyPart);
527             int idx = (keyIt.hasIndex()) ? keyIt.getIndex() : list.size() - 1;
528             if (idx < 0 || idx >= list.size())
529             {
530                 return node;
531             }
532             else
533             {
534                 return findLastPathNode(keyIt, (Node) list.get(idx));
535             }
536         }
537 
538         else
539         {
540             return node;
541         }
542     }
543 
544     /***
545      * Creates the missing nodes for adding a new property. This method
546      * ensures that there are corresponding nodes for all components of the
547      * specified configuration key.
548      *
549      * @param keyIt the key iterator
550      * @param root the base node of the path to be created
551      * @return the last node of the path
552      */
553     protected Node createAddPath(ConfigurationKey.KeyIterator keyIt, Node root)
554     {
555         if (keyIt.hasNext())
556         {
557             Node child = new Node(keyIt.currentKey(true));
558             root.addChild(child);
559             keyIt.next();
560             return createAddPath(keyIt, child);
561         }
562         else
563         {
564             return root;
565         }
566     }
567 
568     /***
569      * A data class for storing (hierarchical) property information. A property
570      * can have a value and an arbitrary number of child properties.
571      *
572      */
573     public static class Node implements Serializable, Cloneable
574     {
575         /*** Stores a reference to this node's parent.*/
576         private Node parent;
577 
578         /*** Stores the name of this node.*/
579         private String name;
580 
581         /*** Stores the value of this node.*/
582         private Object value;
583 
584         /*** Stores the children of this node.*/
585         private LinkedMap children; // Explict type here or we
586                                     // will get a findbugs error 
587                                     // because Map doesn't imply 
588                                     // Serializable
589 
590         /***
591          * Creates a new instance of <code>Node</code>.
592          */
593         public Node()
594         {
595             this(null);
596         }
597 
598         /***
599          * Creates a new instance of <code>Node</code> and sets the name.
600          *
601          * @param name the node's name
602          */
603         public Node(String name)
604         {
605             setName(name);
606         }
607 
608         /***
609          * Returns the name of this node.
610          *
611          * @return the node name
612          */
613         public String getName()
614         {
615             return name;
616         }
617 
618         /***
619          * Returns the value of this node.
620          *
621          * @return the node value (may be <b>null</b>)
622          */
623         public Object getValue()
624         {
625             return value;
626         }
627 
628         /***
629          * Returns the parent of this node.
630          *
631          * @return this node's parent (can be <b>null</b>)
632          */
633         public Node getParent()
634         {
635             return parent;
636         }
637 
638         /***
639          * Sets the name of this node.
640          *
641          * @param string the node name
642          */
643         public void setName(String string)
644         {
645             name = string;
646         }
647 
648         /***
649          * Sets the value of this node.
650          *
651          * @param object the node value
652          */
653         public void setValue(Object object)
654         {
655             value = object;
656         }
657 
658         /***
659          * Sets the parent of this node.
660          *
661          * @param node the parent node
662          */
663         public void setParent(Node node)
664         {
665             parent = node;
666         }
667 
668         /***
669          * Adds the specified child object to this node. Note that there can
670          * be multiple children with the same name.
671          *
672          * @param child the child to be added
673          */
674         public void addChild(Node child)
675         {
676             if (children == null)
677             {
678                 children = new LinkedMap();
679             }
680 
681             List c = (List) children.get(child.getName());
682             if (c == null)
683             {
684                 c = new ArrayList();
685                 children.put(child.getName(), c);
686             }
687 
688             c.add(child);
689             child.setParent(this);
690         }
691 
692         /***
693          * Returns a list with the child nodes of this node.
694          *
695          * @return a list with the children (can be empty, but never
696          * <b>null</b>)
697          */
698         public List getChildren()
699         {
700             List result = new ArrayList();
701 
702             if (children != null)
703             {
704                 for (Iterator it = children.values().iterator(); it.hasNext();)
705                 {
706                     result.addAll((Collection) it.next());
707                 }
708             }
709 
710             return result;
711         }
712 
713         /***
714          * Returns a list with this node's children with the given name.
715          *
716          * @param name the name of the children
717          * @return a list with all chidren with this name; may be empty, but
718          * never <b>null</b>
719          */
720         public List getChildren(String name)
721         {
722             if (name == null || children == null)
723             {
724                 return getChildren();
725             }
726 
727             List list = new ArrayList();
728             List c = (List) children.get(name);
729             if (c != null)
730             {
731                 list.addAll(c);
732             }
733 
734             return list;
735         }
736 
737         /***
738          * Removes the specified child from this node.
739          *
740          * @param child the child node to be removed
741          * @return a flag if the child could be found
742          */
743         public boolean remove(Node child)
744         {
745             if (children == null)
746             {
747                 return false;
748             }
749 
750             List c = (List) children.get(child.getName());
751             if (c == null)
752             {
753                 return false;
754             }
755 
756             else
757             {
758                 if (c.remove(child))
759                 {
760                     if (c.isEmpty())
761                     {
762                         children.remove(child.getName());
763                     }
764                     return true;
765                 }
766                 else
767                 {
768                     return false;
769                 }
770             }
771         }
772 
773         /***
774          * Removes all children with the given name.
775          *
776          * @param name the name of the children to be removed
777          * @return a flag if children with this name existed
778          */
779         public boolean remove(String name)
780         {
781             if (children == null)
782             {
783                 return false;
784             }
785 
786             return children.remove(name) != null;
787         }
788 
789         /***
790          * Removes all children of this node.
791          */
792         public void removeChildren()
793         {
794             children = null;
795         }
796 
797         /***
798          * A generic method for traversing this node and all of its children.
799          * This method sends the passed in visitor to this node and all of its
800          * children.
801          *
802          * @param visitor the visitor
803          * @param key here a configuration key with the name of the root node
804          * of the iteration can be passed; if this key is not <b>null</b>, the
805          * full pathes to the visited nodes are builded and passed to the
806          * visitor's <code>visit()</code> methods
807          */
808         public void visit(NodeVisitor visitor, ConfigurationKey key)
809         {
810             int length = 0;
811             if (key != null)
812             {
813                 length = key.length();
814                 if (getName() != null)
815                 {
816                     key.append(getName());
817                 }
818             }
819 
820             visitor.visitBeforeChildren(this, key);
821 
822             if (children != null)
823             {
824                 for (Iterator it = children.values().iterator();
825                     it.hasNext() && !visitor.terminate();
826                     )
827                 {
828                     Collection col = (Collection) it.next();
829                     for (Iterator it2 = col.iterator();
830                         it2.hasNext() && !visitor.terminate();
831                         )
832                     {
833                         ((Node) it2.next()).visit(visitor, key);
834                     }
835                 }
836             }
837 
838             if (key != null)
839             {
840                 key.setLength(length);
841             }
842             visitor.visitAfterChildren(this, key);
843         }
844 
845         /***
846          * Creates a copy of this object. This is not a deep copy, the children
847          * are not cloned.
848          *
849          * @return a copy of this object
850          */
851         protected Object clone()
852         {
853             try
854             {
855                 return super.clone();
856             }
857             catch (CloneNotSupportedException cex)
858             {
859                 return null; // should not happen
860             }
861         }
862     }
863 
864     /***
865      * <p>Definition of a visitor class for traversing a node and all of its
866      * children.</p>
867      * <p>This class defines the interface of a visitor for <code>Node</code>
868      * objects and provides a default implementation. The method
869      * <code>visit()</code> of <code>Node</code> implements a generic
870      * iteration algorithm based on the <em>Visitor</em> pattern. By
871      * providing different implementations of visitors it is possible to
872      * collect different data during the iteration process.</p>
873      *
874      */
875     public static class NodeVisitor
876     {
877         /***
878          * Visits the specified node. This method is called during iteration
879          * for each node before its children have been visited.
880          *
881          * @param node the actual node
882          * @param key the key of this node (may be <b>null</b>)
883          */
884         public void visitBeforeChildren(Node node, ConfigurationKey key)
885         {
886         }
887 
888         /***
889          * Visits the specified node after its children have been processed.
890          * This gives a visitor the opportunity of collecting additional data
891          * after the child nodes have been visited.
892          *
893          * @param node the node to be visited
894          * @param key the key of this node (may be <b>null</b>)
895          */
896         public void visitAfterChildren(Node node, ConfigurationKey key)
897         {
898         }
899 
900         /***
901          * Returns a flag that indicates if iteration should be stopped. This
902          * method is called after each visited node. It can be useful for
903          * visitors that search a specific node. If this node is found, the
904          * whole process can be stopped. This base implementation always
905          * returns <b>false</b>.
906          *
907          * @return a flag if iteration should be stopped
908          */
909         public boolean terminate()
910         {
911             return false;
912         }
913     }
914 
915     /***
916      * A specialized visitor that checks if a node is defined.
917      * &quot;Defined&quot; in this terms means that the node or at least one
918      * of its sub nodes is associated with a value.
919      *
920      */
921     static class DefinedVisitor extends NodeVisitor
922     {
923         /*** Stores the defined flag.*/
924         private boolean defined;
925 
926         /***
927          * Checks if iteration should be stopped. This can be done if the first
928          * defined node is found.
929          *
930          * @return a flag if iteration should be stopped
931          */
932         public boolean terminate()
933         {
934             return isDefined();
935         }
936 
937         /***
938          * Visits the node. Checks if a value is defined.
939          *
940          * @param node the actual node
941          * @param key the key of this node
942          */
943         public void visitBeforeChildren(Node node, ConfigurationKey key)
944         {
945             defined = node.getValue() != null;
946         }
947 
948         /***
949          * Returns the defined flag.
950          *
951          * @return the defined flag
952          */
953         public boolean isDefined()
954         {
955             return defined;
956         }
957     }
958 
959     /***
960      * A specialized visitor that fills a list with keys that are defined in
961      * a node hierarchy.
962      *
963      */
964     static class DefinedKeysVisitor extends NodeVisitor
965     {
966         /*** Stores the list to be filled.*/
967         private Set keyList;
968 
969         /***
970          * Default constructor.
971          */
972         public DefinedKeysVisitor()
973         {
974             keyList = new HashSet();
975         }
976 
977         /***
978          * Returns the list with all defined keys.
979          *
980          * @return the list with the defined keys
981          */
982         public Set getKeyList()
983         {
984             return keyList;
985         }
986 
987         /***
988          * Visits the specified node. If this node has a value, its key is
989          * added to the internal list.
990          *
991          * @param node the node to be visited
992          * @param key the key of this node
993          */
994         public void visitBeforeChildren(Node node, ConfigurationKey key)
995         {
996             if (node.getValue() != null && key != null)
997             {
998                 keyList.add(key.toString());
999             }
1000         }
1001     }
1002 
1003     /***
1004      * A specialized visitor that is able to create a deep copy of a node
1005      * hierarchy.
1006      *
1007      */
1008     static class CloneVisitor extends NodeVisitor
1009     {
1010         /*** A stack with the actual object to be copied.*/
1011         private Stack copyStack;
1012 
1013         /*** Stores the result of the clone process.*/
1014         private Node result;
1015 
1016         /***
1017          * Creates a new instance of <code>CloneVisitor</code>.
1018          */
1019         public CloneVisitor()
1020         {
1021             copyStack = new Stack();
1022         }
1023 
1024         /***
1025          * Visits the specified node after its children have been processed.
1026          *
1027          * @param node the node
1028          * @param key the key of this node
1029          */
1030         public void visitAfterChildren(Node node, ConfigurationKey key)
1031         {
1032             copyStack.pop();
1033             if (copyStack.isEmpty())
1034             {
1035                 result = node;
1036             }
1037         }
1038 
1039         /***
1040          * Visits and copies the specified node.
1041          *
1042          * @param node the node
1043          * @param key the key of this node
1044          */
1045         public void visitBeforeChildren(Node node, ConfigurationKey key)
1046         {
1047             Node copy = (Node) node.clone();
1048             copy.removeChildren();
1049 
1050             if (!copyStack.isEmpty())
1051             {
1052                 ((Node) copyStack.peek()).addChild(copy);
1053             }
1054 
1055             copyStack.push(copy);
1056         }
1057 
1058         /***
1059          * Returns the result of the clone process. This is the root node of
1060          * the cloned node hierarchy.
1061          *
1062          * @return the cloned root node
1063          */
1064         public Node getClone()
1065         {
1066             return result;
1067         }
1068     }
1069 }