1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 * <database>
44 * <tables>
45 * <table>
46 * <name>users</name>
47 * <fields>
48 * <field>
49 * <name>lid</name>
50 * <type>long</name>
51 * </field>
52 * <field>
53 * <name>usrName</name>
54 * <type>java.lang.String</type>
55 * </field>
56 * ...
57 * </fields>
58 * </table>
59 * <table>
60 * <name>documents</name>
61 * <fields>
62 * <field>
63 * <name>docid</name>
64 * <type>long</type>
65 * </field>
66 * ...
67 * </fields>
68 * </table>
69 * ...
70 * </tables>
71 * </database>
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
276
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 "contained" 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;
586
587
588
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;
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 * "Defined" 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 }