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><p>There
35 * are some sources of configuration data that cannot be stored very well in a
36 * <code>BaseConfiguration</code> object because then their structure is lost.
37 * This is especially true for XML documents. This class can deal with such
38 * structured configuration sources by storing the properties in a tree-like
39 * organization.</p><p>The internal used storage form allows for a more
40 * sophisticated access to single properties. As an example consider the
41 * following XML document:</p><p>
42 *
43 * <pre>
44 * <database>
45 * <tables>
46 * <table>
47 * <name>users</name>
48 * <fields>
49 * <field>
50 * <name>lid</name>
51 * <type>long</name>
52 * </field>
53 * <field>
54 * <name>usrName</name>
55 * <type>java.lang.String</type>
56 * </field>
57 * ...
58 * </fields>
59 * </table>
60 * <table>
61 * <name>documents</name>
62 * <fields>
63 * <field>
64 * <name>docid</name>
65 * <type>long</type>
66 * </field>
67 * ...
68 * </fields>
69 * </table>
70 * ...
71 * </tables>
72 * </database>
73 * </pre>
74 *
75 * </p><p>If this document is parsed and stored in a
76 * <code>HierarchicalConfiguration</code> object (which can be done by one of
77 * the sub classes), there are enhanced possibilities of accessing properties.
78 * The keys for querying information can contain indices that select a certain
79 * element if there are multiple hits.</p><p>For instance the key
80 * <code>tables.table(0).name</code> can be used to find out the name of the
81 * first table. In opposite <code>tables.table.name</code> would return a
82 * collection with the names of all available tables. Similarily the key
83 * <code>tables.table(1).fields.field.name</code> returns a collection with
84 * the names of all fields of the second table. If another index is added after
85 * the <code>field</code> element, a single field can be accessed:
86 * <code>tables.table(1).fields.field(0).name</code>.</p><p>There is a
87 * <code>getMaxIndex()</code> method that returns the maximum allowed index
88 * that can be added to a given property key. This method can be used to iterate
89 * over all values defined for a certain property.</p>
90 *
91 * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger </a>
92 * @version $Id: HierarchicalConfiguration.java,v 1.14 2004/12/02 22:05:52
93 * ebourg Exp $
94 */
95 public class HierarchicalConfiguration extends AbstractConfiguration
96 {
97 /*** Constant for a new dummy key. */
98 private static final String NEW_KEY = "newKey";
99
100 /*** Stores the root node of this configuration. */
101 private Node root = new Node();
102
103 /***
104 * Creates a new instance of <code>HierarchicalConfiguration</code>.
105 */
106 public HierarchicalConfiguration()
107 {
108 super();
109 }
110
111 /***
112 * Returns the root node of this hierarchical configuration.
113 *
114 * @return the root node
115 */
116 public Node getRoot()
117 {
118 return root;
119 }
120
121 /***
122 * Sets the root node of this hierarchical configuration.
123 *
124 * @param node the root node
125 */
126 public void setRoot(Node node)
127 {
128 if (node == null)
129 {
130 throw new IllegalArgumentException("Root node must not be null!");
131 }
132 root = node;
133 }
134
135 /***
136 * Fetches the specified property. Performs a recursive lookup in the tree
137 * with the configuration properties.
138 *
139 * @param key the key to be looked up
140 * @return the found value
141 */
142 public Object getProperty(String key)
143 {
144 List nodes = fetchNodeList(key);
145
146 if (nodes.size() == 0)
147 {
148 return null;
149 }
150 else
151 {
152 List list = new ArrayList();
153 for (Iterator it = nodes.iterator(); it.hasNext();)
154 {
155 Node node = (Node) it.next();
156 if (node.getValue() != null)
157 {
158 list.add(node.getValue());
159 }
160 }
161
162 if (list.size() < 1)
163 {
164 return null;
165 }
166 else
167 {
168 return (list.size() == 1) ? list.get(0) : list;
169 }
170 }
171 }
172
173 /***
174 * <p>Adds the property with the specified key.</p><p>To be able to deal
175 * with the structure supported by this configuration implementation the
176 * passed in key is of importance, especially the indices it might contain.
177 * The following example should clearify this: Suppose the actual
178 * configuration contains the following elements:</p><p>
179 *
180 * <pre>
181 * tables
182 * +-- table
183 * +-- name = user
184 * +-- fields
185 * +-- field
186 * +-- name = uid
187 * +-- field
188 * +-- name = firstName
189 * ...
190 * +-- table
191 * +-- name = documents
192 * +-- fields
193 * ...
194 * </pre>
195 *
196 * </p><p>In this example a database structure is defined, e.g. all fields
197 * of the first table could be accessed using the key
198 * <code>tables.table(0).fields.field.name</code>. If now properties are
199 * to be added, it must be exactly specified at which position in the
200 * hierarchy the new property is to be inserted. So to add a new field name
201 * to a table it is not enough to say just</p><p>
202 *
203 * <pre>
204 * config.addProperty("tables.table.fields.field.name", "newField");
205 * </pre>
206 *
207 * </p><p>The statement given above contains some ambiguity. For instance
208 * it is not clear, to which table the new field should be added. If this
209 * method finds such an ambiguity, it is resolved by following the last
210 * valid path. Here this would be the last table. The same is true for the
211 * <code>field</code>; because there are multiple fields and no explicit
212 * index is provided, a new <code>name</code> property would be added to
213 * the last field - which is propably not what was desired.</p><p>To make
214 * things clear explicit indices should be provided whenever possible. In
215 * the example above the exact table could be specified by providing an
216 * index for the <code>table</code> element as in
217 * <code>tables.table(1).fields</code>. By specifying an index it can
218 * also be expressed that at a given position in the configuration tree a
219 * new branch should be added. In the example above we did not want to add
220 * an additional <code>name</code> element to the last field of the table,
221 * but we want a complete new <code>field</code> element. This can be
222 * achieved by specifying an invalid index (like -1) after the element where
223 * a new branch should be created. Given this our example would run:</p>
224 * <p>
225 *
226 * <pre>
227 * config.addProperty("tables.table(1).fields.field(-1).name", "newField");
228 * </pre>
229 *
230 * </p><p>With this notation it is possible to add new branches
231 * everywhere. We could for instance create a new <code>table</code>
232 * element by specifying</p><p>
233 *
234 * <pre>
235 * config.addProperty("tables.table(-1).fields.field.name", "newField2");
236 * </pre>
237 *
238 * </p><p>(Note that because after the <code>table</code> element a new
239 * branch is created indices in following elements are not relevant; the
240 * branch is new so there cannot be any ambiguities.)</p>
241 *
242 * @param key the key of the new property
243 * @param obj the value of the new property
244 */
245 protected void addPropertyDirect(String key, Object obj)
246 {
247 ConfigurationKey.KeyIterator it = new ConfigurationKey(key).iterator();
248 Node parent = fetchAddNode(it, getRoot());
249
250 Node child = createNode(it.currentKey(true));
251 child.setValue(obj);
252 parent.addChild(child);
253 }
254
255 /***
256 * Adds a collection of nodes at the specified position of the configuration
257 * tree. This method works similar to <code>addProperty()</code>, but
258 * instead of a single property a whole collection of nodes can be added -
259 * and thus complete configuration sub trees. E.g. with this method it is
260 * possible to add parts of another <code>HierarchicalConfiguration</code>
261 * object to this object.
262 *
263 * @param key the key where the nodes are to be added; can be <b>null </b>,
264 * then they are added to the root node
265 * @param nodes a collection with the <code>Node</code> objects to be
266 * added
267 */
268 public void addNodes(String key, Collection nodes)
269 {
270 if (nodes == null || nodes.isEmpty())
271 {
272 return;
273 }
274
275 Node parent;
276 if (StringUtils.isEmpty(key))
277 {
278 parent = getRoot();
279 }
280 else
281 {
282 ConfigurationKey.KeyIterator kit = new ConfigurationKey(key).iterator();
283 parent = fetchAddNode(kit, getRoot());
284
285
286
287 ConfigurationKey keyNew = new ConfigurationKey(kit.currentKey(true));
288 keyNew.append(NEW_KEY);
289 parent = fetchAddNode(keyNew.iterator(), parent);
290 }
291
292 for (Iterator it = nodes.iterator(); it.hasNext();)
293 {
294 parent.addChild((Node) it.next());
295 }
296 }
297
298 /***
299 * Checks if this configuration is empty. Empty means that there are no keys
300 * with any values, though there can be some (empty) nodes.
301 *
302 * @return a flag if this configuration is empty
303 */
304 public boolean isEmpty()
305 {
306 return !nodeDefined(getRoot());
307 }
308
309 /***
310 * Creates a new <code>Configuration</code> object containing all keys
311 * that start with the specified prefix. This implementation will return a
312 * <code>HierarchicalConfiguration</code> object so that the structure of
313 * the keys will be saved.
314 *
315 * @param prefix the prefix of the keys for the subset
316 * @return a new configuration object representing the selected subset
317 */
318 public Configuration subset(String prefix)
319 {
320 Collection nodes = fetchNodeList(prefix);
321 if (nodes.isEmpty())
322 {
323 return new HierarchicalConfiguration();
324 }
325
326 HierarchicalConfiguration result = new HierarchicalConfiguration();
327 CloneVisitor visitor = new CloneVisitor();
328
329 for (Iterator it = nodes.iterator(); it.hasNext();)
330 {
331 Node nd = (Node) it.next();
332 nd.visit(visitor, null);
333
334 List children = visitor.getClone().getChildren();
335 if (children.size() > 0)
336 {
337 for (int i = 0; i < children.size(); i++)
338 {
339 result.getRoot().addChild((Node) children.get(i));
340 }
341 }
342 }
343
344 return (result.isEmpty()) ? new HierarchicalConfiguration() : result;
345 }
346
347 /***
348 * Checks if the specified key is contained in this configuration. Note that
349 * for this configuration the term "contained" means that the key
350 * has an associated value. If there is a node for this key that has no
351 * value but children (either defined or undefined), this method will still
352 * return <b>false </b>.
353 *
354 * @param key the key to be chekced
355 * @return a flag if this key is contained in this configuration
356 */
357 public boolean containsKey(String key)
358 {
359 return getProperty(key) != null;
360 }
361
362 /***
363 * @inheritDoc
364 */
365 public void setProperty(String key, Object value)
366 {
367 Iterator itNodes = fetchNodeList(key).iterator();
368 Iterator itValues = PropertyConverter.toIterator(value, getDelimiter());
369 while (itNodes.hasNext() && itValues.hasNext())
370 {
371 ((Node) itNodes.next()).setValue(itValues.next());
372 }
373
374
375 while (itValues.hasNext())
376 {
377 addPropertyDirect(key, itValues.next());
378 }
379
380
381 while (itNodes.hasNext())
382 {
383 clearNode((Node) itNodes.next());
384 }
385 }
386
387 /***
388 * Removes all values of the property with the given name and of keys that
389 * start with this name. So if there is a property with the key
390 * "foo" and a property with the key "foo.bar", a call
391 * of <code>clearTree("foo")</code> would remove both properties.
392 *
393 * @param key the key of the property to be removed
394 */
395 public void clearTree(String key)
396 {
397 List nodes = fetchNodeList(key);
398
399 for (Iterator it = nodes.iterator(); it.hasNext();)
400 {
401 removeNode((Node) it.next());
402 }
403 }
404
405 /***
406 * Removes the property with the given key. Properties with names that start
407 * with the given key (i.e. properties below the specified key in the
408 * hierarchy) won't be affected.
409 *
410 * @param key the key of the property to be removed
411 */
412 public void clearProperty(String key)
413 {
414 List nodes = fetchNodeList(key);
415
416 for (Iterator it = nodes.iterator(); it.hasNext();)
417 {
418 clearNode((Node) it.next());
419 }
420 }
421
422 /***
423 * <p>Returns an iterator with all keys defined in this configuration.</p>
424 * <p>Note that the keys returned by this method will not contain any
425 * indices. This means that some structure will be lost.</p>
426 *
427 * @return an iterator with the defined keys in this configuration
428 */
429 public Iterator getKeys()
430 {
431 DefinedKeysVisitor visitor = new DefinedKeysVisitor();
432 getRoot().visit(visitor, new ConfigurationKey());
433 return visitor.getKeyList().iterator();
434 }
435
436 /***
437 * Returns an iterator with all keys defined in this configuration that
438 * start with the given prefix. The returned keys will not contain any
439 * indices.
440 *
441 * @param prefix the prefix of the keys to start with
442 * @return an iterator with the found keys
443 */
444 public Iterator getKeys(String prefix)
445 {
446 DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
447 List nodes = fetchNodeList(prefix);
448 ConfigurationKey key = new ConfigurationKey();
449
450 for (Iterator itNodes = nodes.iterator(); itNodes.hasNext();)
451 {
452 Node node = (Node) itNodes.next();
453 for (Iterator it = node.getChildren().iterator(); it.hasNext();)
454 {
455 ((Node) it.next()).visit(visitor, key);
456 }
457 }
458
459 return visitor.getKeyList().iterator();
460 }
461
462 /***
463 * Returns the maximum defined index for the given key. This is useful if
464 * there are multiple values for this key. They can then be addressed
465 * separately by specifying indices from 0 to the return value of this
466 * method.
467 *
468 * @param key the key to be checked
469 * @return the maximum defined index for this key
470 */
471 public int getMaxIndex(String key)
472 {
473 return fetchNodeList(key).size() - 1;
474 }
475
476 /***
477 * Helper method for fetching a list of all nodes that are addressed by the
478 * specified key.
479 *
480 * @param key the key
481 * @return a list with all affected nodes (never <b>null </b>)
482 */
483 protected List fetchNodeList(String key)
484 {
485 List nodes = new LinkedList();
486 findPropertyNodes(new ConfigurationKey(key).iterator(), getRoot(), nodes);
487 return nodes;
488 }
489
490 /***
491 * Recursive helper method for fetching a property. This method processes
492 * all facets of a configuration key, traverses the tree of properties and
493 * fetches the the nodes of all matching properties.
494 *
495 * @param keyPart the configuration key iterator
496 * @param node the actual node
497 * @param data here the found nodes are stored
498 */
499 protected void findPropertyNodes(ConfigurationKey.KeyIterator keyPart, Node node, Collection data)
500 {
501 if (!keyPart.hasNext())
502 {
503 data.add(node);
504 }
505 else
506 {
507 String key = keyPart.nextKey(true);
508 List children = node.getChildren(key);
509 if (keyPart.hasIndex())
510 {
511 if (keyPart.getIndex() < children.size() && keyPart.getIndex() >= 0)
512 {
513 findPropertyNodes((ConfigurationKey.KeyIterator) keyPart.clone(), (Node) children.get(keyPart
514 .getIndex()), data);
515 }
516 }
517 else
518 {
519 for (Iterator it = children.iterator(); it.hasNext();)
520 {
521 findPropertyNodes((ConfigurationKey.KeyIterator) keyPart.clone(), (Node) it.next(), data);
522 }
523 }
524 }
525 }
526
527 /***
528 * Checks if the specified node is defined.
529 *
530 * @param node the node to be checked
531 * @return a flag if this node is defined
532 */
533 protected boolean nodeDefined(Node node)
534 {
535 DefinedVisitor visitor = new DefinedVisitor();
536 node.visit(visitor, null);
537 return visitor.isDefined();
538 }
539
540 /***
541 * Removes the specified node from this configuration. This method ensures
542 * that parent nodes that become undefined by this operation are also
543 * removed.
544 *
545 * @param node the node to be removed
546 */
547 protected void removeNode(Node node)
548 {
549 Node parent = node.getParent();
550 if (parent != null)
551 {
552 parent.remove(node);
553 if (!nodeDefined(parent))
554 {
555 removeNode(parent);
556 }
557 }
558 }
559
560 /***
561 * Clears the value of the specified node. If the node becomes undefined by
562 * this operation, it is removed from the hierarchy.
563 *
564 * @param node the node to be cleard
565 */
566 protected void clearNode(Node node)
567 {
568 node.setValue(null);
569 if (!nodeDefined(node))
570 {
571 removeNode(node);
572 }
573 }
574
575 /***
576 * Returns a reference to the parent node of an add operation. Nodes for new
577 * properties can be added as children of this node. If the path for the
578 * specified key does not exist so far, it is created now.
579 *
580 * @param keyIt the iterator for the key of the new property
581 * @param startNode the node to start the search with
582 * @return the parent node for the add operation
583 */
584 protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt, Node startNode)
585 {
586 if (!keyIt.hasNext())
587 {
588 throw new IllegalArgumentException("Key must be defined!");
589 }
590
591 return createAddPath(keyIt, findLastPathNode(keyIt, startNode));
592 }
593
594 /***
595 * Finds the last existing node for an add operation. This method traverses
596 * the configuration tree along the specified key. The last existing node on
597 * this path is returned.
598 *
599 * @param keyIt the key iterator
600 * @param node the actual node
601 * @return the last existing node on the given path
602 */
603 protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt, Node node)
604 {
605 String keyPart = keyIt.nextKey(true);
606
607 if (keyIt.hasNext())
608 {
609 List list = node.getChildren(keyPart);
610 int idx = (keyIt.hasIndex()) ? keyIt.getIndex() : list.size() - 1;
611 if (idx < 0 || idx >= list.size())
612 {
613 return node;
614 }
615 else
616 {
617 return findLastPathNode(keyIt, (Node) list.get(idx));
618 }
619 }
620
621 else
622 {
623 return node;
624 }
625 }
626
627 /***
628 * Creates the missing nodes for adding a new property. This method ensures
629 * that there are corresponding nodes for all components of the specified
630 * configuration key.
631 *
632 * @param keyIt the key iterator
633 * @param root the base node of the path to be created
634 * @return the last node of the path
635 */
636 protected Node createAddPath(ConfigurationKey.KeyIterator keyIt, Node root)
637 {
638 if (keyIt.hasNext())
639 {
640 Node child = createNode(keyIt.currentKey(true));
641 root.addChild(child);
642 keyIt.next();
643 return createAddPath(keyIt, child);
644 }
645 else
646 {
647 return root;
648 }
649 }
650
651 /***
652 * Creates a new <code>Node</code> object with the specified name. This
653 * method can be overloaded in derived classes if a specific node type is
654 * needed. This base implementation always returns a new object of the
655 * <code>Node</code> class.
656 *
657 * @param name the name of the new node
658 * @return the new node
659 */
660 protected Node createNode(String name)
661 {
662 return new Node(name);
663 }
664
665 /***
666 * A data class for storing (hierarchical) property information. A property
667 * can have a value and an arbitrary number of child properties.
668 *
669 */
670 public static class Node implements Serializable, Cloneable
671 {
672 /*** Stores a reference to this node's parent. */
673 private Node parent;
674
675 /*** Stores the name of this node. */
676 private String name;
677
678 /*** Stores the value of this node. */
679 private Object value;
680
681 /*** Stores a reference to an object this node is associated with. */
682 private Object reference;
683
684 /*** Stores the children of this node. */
685 private LinkedMap children;
686
687
688
689
690
691 /***
692 * Creates a new instance of <code>Node</code>.
693 */
694 public Node()
695 {
696 this(null);
697 }
698
699 /***
700 * Creates a new instance of <code>Node</code> and sets the name.
701 *
702 * @param name the node's name
703 */
704 public Node(String name)
705 {
706 setName(name);
707 }
708
709 /***
710 * Returns the name of this node.
711 *
712 * @return the node name
713 */
714 public String getName()
715 {
716 return name;
717 }
718
719 /***
720 * Returns the value of this node.
721 *
722 * @return the node value (may be <b>null </b>)
723 */
724 public Object getValue()
725 {
726 return value;
727 }
728
729 /***
730 * Returns the parent of this node.
731 *
732 * @return this node's parent (can be <b>null </b>)
733 */
734 public Node getParent()
735 {
736 return parent;
737 }
738
739 /***
740 * Sets the name of this node.
741 *
742 * @param string the node name
743 */
744 public void setName(String string)
745 {
746 name = string;
747 }
748
749 /***
750 * Sets the value of this node.
751 *
752 * @param object the node value
753 */
754 public void setValue(Object object)
755 {
756 value = object;
757 }
758
759 /***
760 * Sets the parent of this node.
761 *
762 * @param node the parent node
763 */
764 public void setParent(Node node)
765 {
766 parent = node;
767 }
768
769 /***
770 * Returns the reference object for this node.
771 *
772 * @return the reference object
773 */
774 public Object getReference()
775 {
776 return reference;
777 }
778
779 /***
780 * Sets the reference object for this node. A node can be associated
781 * with a reference object whose concrete meaning is determined by a sub
782 * class of <code>HierarchicalConfiguration</code>. In an XML
783 * configuration e.g. this reference could be an element in a
784 * corresponding XML document. The reference is used by the
785 * <code>BuilderVisitor</code> class when the configuration is stored.
786 *
787 * @param ref the reference object
788 */
789 public void setReference(Object ref)
790 {
791 reference = ref;
792 }
793
794 /***
795 * Adds the specified child object to this node. Note that there can be
796 * multiple children with the same name.
797 *
798 * @param child the child to be added
799 */
800 public void addChild(Node child)
801 {
802 if (children == null)
803 {
804 children = new LinkedMap();
805 }
806
807 List c = (List) children.get(child.getName());
808 if (c == null)
809 {
810 c = new ArrayList();
811 children.put(child.getName(), c);
812 }
813
814 c.add(child);
815 child.setParent(this);
816 }
817
818 /***
819 * Returns a list with the child nodes of this node.
820 *
821 * @return a list with the children (can be empty, but never <b>null
822 * </b>)
823 */
824 public List getChildren()
825 {
826 List result = new ArrayList();
827
828 if (children != null)
829 {
830 for (Iterator it = children.values().iterator(); it.hasNext();)
831 {
832 result.addAll((Collection) it.next());
833 }
834 }
835
836 return result;
837 }
838
839 /***
840 * Returns a list with this node's children with the given name.
841 *
842 * @param name the name of the children
843 * @return a list with all chidren with this name; may be empty, but
844 * never <b>null </b>
845 */
846 public List getChildren(String name)
847 {
848 if (name == null || children == null)
849 {
850 return getChildren();
851 }
852
853 List list = new ArrayList();
854 List c = (List) children.get(name);
855 if (c != null)
856 {
857 list.addAll(c);
858 }
859
860 return list;
861 }
862
863 /***
864 * Removes the specified child from this node.
865 *
866 * @param child the child node to be removed
867 * @return a flag if the child could be found
868 */
869 public boolean remove(Node child)
870 {
871 if (children == null)
872 {
873 return false;
874 }
875
876 List c = (List) children.get(child.getName());
877 if (c == null)
878 {
879 return false;
880 }
881
882 else
883 {
884 if (c.remove(child))
885 {
886 child.removeReference();
887 if (c.isEmpty())
888 {
889 children.remove(child.getName());
890 }
891 return true;
892 }
893 else
894 {
895 return false;
896 }
897 }
898 }
899
900 /***
901 * Removes all children with the given name.
902 *
903 * @param name the name of the children to be removed
904 * @return a flag if children with this name existed
905 */
906 public boolean remove(String name)
907 {
908 if (children == null)
909 {
910 return false;
911 }
912
913 List nodes = (List) children.remove(name);
914 if (nodes != null)
915 {
916 nodesRemoved(nodes);
917 return true;
918 }
919 else
920 {
921 return false;
922 }
923 }
924
925 /***
926 * Removes all children of this node.
927 */
928 public void removeChildren()
929 {
930 if (children != null)
931 {
932 Iterator it = children.values().iterator();
933 children = null;
934 while (it.hasNext())
935 {
936 nodesRemoved((Collection) it.next());
937 }
938 }
939 }
940
941 /***
942 * A generic method for traversing this node and all of its children.
943 * This method sends the passed in visitor to this node and all of its
944 * children.
945 *
946 * @param visitor the visitor
947 * @param key here a configuration key with the name of the root node of
948 * the iteration can be passed; if this key is not <b>null </b>, the
949 * full pathes to the visited nodes are builded and passed to the
950 * visitor's <code>visit()</code> methods
951 */
952 public void visit(NodeVisitor visitor, ConfigurationKey key)
953 {
954 int length = 0;
955 if (key != null)
956 {
957 length = key.length();
958 if (getName() != null)
959 {
960 key.append(getName());
961 }
962 }
963
964 visitor.visitBeforeChildren(this, key);
965
966 if (children != null)
967 {
968 for (Iterator it = children.values().iterator(); it.hasNext() && !visitor.terminate();)
969 {
970 Collection col = (Collection) it.next();
971 for (Iterator it2 = col.iterator(); it2.hasNext() && !visitor.terminate();)
972 {
973 ((Node) it2.next()).visit(visitor, key);
974 }
975 }
976 }
977
978 if (key != null)
979 {
980 key.setLength(length);
981 }
982 visitor.visitAfterChildren(this, key);
983 }
984
985 /***
986 * Creates a copy of this object. This is not a deep copy, the children
987 * are not cloned.
988 *
989 * @return a copy of this object
990 */
991 public Object clone()
992 {
993 try
994 {
995 return super.clone();
996 }
997 catch (CloneNotSupportedException cex)
998 {
999 return null;
1000 }
1001 }
1002
1003 /***
1004 * Deals with the reference when a node is removed. This method is
1005 * called for each removed child node. It can be overloaded in sub
1006 * classes, for which the reference has a concrete meaning and remove
1007 * operations need some update actions. This default implementation is
1008 * empty.
1009 */
1010 protected void removeReference()
1011 {
1012 }
1013
1014 /***
1015 * Helper method for calling <code>removeReference()</code> on a list
1016 * of removed nodes. Used by methods that can remove multiple child
1017 * nodes in one step.
1018 *
1019 * @param nodes collection with the nodes to be removed
1020 */
1021 private void nodesRemoved(Collection nodes)
1022 {
1023 for (Iterator it = nodes.iterator(); it.hasNext();)
1024 {
1025 ((Node) it.next()).removeReference();
1026 }
1027 }
1028 }
1029
1030 /***
1031 * <p>Definition of a visitor class for traversing a node and all of its
1032 * children.</p><p>This class defines the interface of a visitor for
1033 * <code>Node</code> objects and provides a default implementation. The
1034 * method <code>visit()</code> of <code>Node</code> implements a generic
1035 * iteration algorithm based on the <em>Visitor</em> pattern. By providing
1036 * different implementations of visitors it is possible to collect different
1037 * data during the iteration process.</p>
1038 *
1039 */
1040 public static class NodeVisitor
1041 {
1042 /***
1043 * Visits the specified node. This method is called during iteration for
1044 * each node before its children have been visited.
1045 *
1046 * @param node the actual node
1047 * @param key the key of this node (may be <b>null </b>)
1048 */
1049 public void visitBeforeChildren(Node node, ConfigurationKey key)
1050 {
1051 }
1052
1053 /***
1054 * Visits the specified node after its children have been processed.
1055 * This gives a visitor the opportunity of collecting additional data
1056 * after the child nodes have been visited.
1057 *
1058 * @param node the node to be visited
1059 * @param key the key of this node (may be <b>null </b>)
1060 */
1061 public void visitAfterChildren(Node node, ConfigurationKey key)
1062 {
1063 }
1064
1065 /***
1066 * Returns a flag that indicates if iteration should be stopped. This
1067 * method is called after each visited node. It can be useful for
1068 * visitors that search a specific node. If this node is found, the
1069 * whole process can be stopped. This base implementation always returns
1070 * <b>false </b>.
1071 *
1072 * @return a flag if iteration should be stopped
1073 */
1074 public boolean terminate()
1075 {
1076 return false;
1077 }
1078 }
1079
1080 /***
1081 * A specialized visitor that checks if a node is defined.
1082 * "Defined" in this terms means that the node or at least one of
1083 * its sub nodes is associated with a value.
1084 *
1085 */
1086 static class DefinedVisitor extends NodeVisitor
1087 {
1088 /*** Stores the defined flag. */
1089 private boolean defined;
1090
1091 /***
1092 * Checks if iteration should be stopped. This can be done if the first
1093 * defined node is found.
1094 *
1095 * @return a flag if iteration should be stopped
1096 */
1097 public boolean terminate()
1098 {
1099 return isDefined();
1100 }
1101
1102 /***
1103 * Visits the node. Checks if a value is defined.
1104 *
1105 * @param node the actual node
1106 * @param key the key of this node
1107 */
1108 public void visitBeforeChildren(Node node, ConfigurationKey key)
1109 {
1110 defined = node.getValue() != null;
1111 }
1112
1113 /***
1114 * Returns the defined flag.
1115 *
1116 * @return the defined flag
1117 */
1118 public boolean isDefined()
1119 {
1120 return defined;
1121 }
1122 }
1123
1124 /***
1125 * A specialized visitor that fills a list with keys that are defined in a
1126 * node hierarchy.
1127 *
1128 */
1129 static class DefinedKeysVisitor extends NodeVisitor
1130 {
1131 /*** Stores the list to be filled. */
1132 private Set keyList;
1133
1134 /*** Stores a prefix for the keys. */
1135 private String prefix;
1136
1137 /***
1138 * Default constructor.
1139 */
1140 public DefinedKeysVisitor()
1141 {
1142 keyList = new HashSet();
1143 }
1144
1145 /***
1146 * Creates a new <code>DefinedKeysVisitor</code> instance and sets the
1147 * prefix for the keys to fetch.
1148 *
1149 * @param prefix the prefix
1150 */
1151 public DefinedKeysVisitor(String prefix)
1152 {
1153 this();
1154 this.prefix = prefix;
1155 }
1156
1157 /***
1158 * Returns the list with all defined keys.
1159 *
1160 * @return the list with the defined keys
1161 */
1162 public Set getKeyList()
1163 {
1164 return keyList;
1165 }
1166
1167 /***
1168 * Visits the specified node. If this node has a value, its key is added
1169 * to the internal list.
1170 *
1171 * @param node the node to be visited
1172 * @param key the key of this node
1173 */
1174 public void visitBeforeChildren(Node node, ConfigurationKey key)
1175 {
1176 if (node.getValue() != null && key != null)
1177 {
1178 addKey(key);
1179 }
1180 }
1181
1182 /***
1183 * Adds the specified key to the internal list.
1184 *
1185 * @param key the key to add
1186 */
1187 protected void addKey(ConfigurationKey key)
1188 {
1189 if (prefix == null)
1190 {
1191 keyList.add(key.toString());
1192 }
1193 else
1194 {
1195 StringBuffer buf = new StringBuffer(prefix);
1196 if (!key.isAttributeKey())
1197 {
1198 buf.append(ConfigurationKey.PROPERTY_DELIMITER);
1199 }
1200 buf.append(key);
1201 keyList.add(buf.toString());
1202 }
1203 }
1204 }
1205
1206 /***
1207 * A specialized visitor that is able to create a deep copy of a node
1208 * hierarchy.
1209 *
1210 */
1211 static class CloneVisitor extends NodeVisitor
1212 {
1213 /*** A stack with the actual object to be copied. */
1214 private Stack copyStack;
1215
1216 /*** Stores the result of the clone process. */
1217 private Node result;
1218
1219 /***
1220 * Creates a new instance of <code>CloneVisitor</code>.
1221 */
1222 public CloneVisitor()
1223 {
1224 copyStack = new Stack();
1225 }
1226
1227 /***
1228 * Visits the specified node after its children have been processed.
1229 *
1230 * @param node the node
1231 * @param key the key of this node
1232 */
1233 public void visitAfterChildren(Node node, ConfigurationKey key)
1234 {
1235 copyStack.pop();
1236 if (copyStack.isEmpty())
1237 {
1238 result = node;
1239 }
1240 }
1241
1242 /***
1243 * Visits and copies the specified node.
1244 *
1245 * @param node the node
1246 * @param key the key of this node
1247 */
1248 public void visitBeforeChildren(Node node, ConfigurationKey key)
1249 {
1250 Node copy = (Node) node.clone();
1251 copy.removeChildren();
1252
1253 if (!copyStack.isEmpty())
1254 {
1255 ((Node) copyStack.peek()).addChild(copy);
1256 }
1257
1258 copyStack.push(copy);
1259 }
1260
1261 /***
1262 * Returns the result of the clone process. This is the root node of the
1263 * cloned node hierarchy.
1264 *
1265 * @return the cloned root node
1266 */
1267 public Node getClone()
1268 {
1269 return result;
1270 }
1271 }
1272
1273 /***
1274 * A specialized visitor base class that can be used for storing the tree of
1275 * configuration nodes. The basic idea is that each node can be associated
1276 * with a reference object. This reference object has a concrete meaning in
1277 * a derived class, e.g. an entry in a JNDI context or an XML element. When
1278 * the configuration tree is set up, the <code>load()</code> method is
1279 * responsible for setting the reference objects. When the configuration
1280 * tree is later modified, new nodes do not have a defined reference object.
1281 * This visitor class processes all nodes and finds the ones without a
1282 * defined reference object. For those nodes the <code>insert()</code>
1283 * method is called, which must be defined in concrete sub classes. This
1284 * method can perform all steps to integrate the new node into the original
1285 * structure.
1286 *
1287 */
1288 protected abstract static class BuilderVisitor extends NodeVisitor
1289 {
1290 /***
1291 * @inheritDoc
1292 */
1293 public void visitBeforeChildren(Node node, ConfigurationKey key)
1294 {
1295 Iterator children = node.getChildren().iterator();
1296 Node sibling1 = null;
1297 Node nd = null;
1298
1299 while (children.hasNext())
1300 {
1301
1302 do
1303 {
1304 sibling1 = nd;
1305 nd = (Node) children.next();
1306 } while (nd.getReference() != null && children.hasNext());
1307
1308 if (nd.getReference() == null)
1309 {
1310
1311 List newNodes = new LinkedList();
1312 newNodes.add(nd);
1313 while (children.hasNext())
1314 {
1315 nd = (Node) children.next();
1316 if (nd.getReference() == null)
1317 {
1318 newNodes.add(nd);
1319 }
1320 else
1321 {
1322 break;
1323 }
1324 }
1325
1326
1327 Node sibling2 = (nd.getReference() == null) ? null : nd;
1328 for (Iterator it = newNodes.iterator(); it.hasNext();)
1329 {
1330 Node insertNode = (Node) it.next();
1331 if (insertNode.getReference() == null)
1332 {
1333 Object ref = insert(insertNode, node, sibling1, sibling2);
1334 if (ref != null)
1335 {
1336 insertNode.setReference(ref);
1337 }
1338 }
1339 }
1340 }
1341 }
1342 }
1343
1344 /***
1345 * Inserts a new node into the structure constructed by this builder.
1346 * This method is called for each node that has been added to the
1347 * configuration tree after the configuration has been loaded from its
1348 * source. These new nodes have to be inserted into the original
1349 * structure. The passed in nodes define the position of the node to be
1350 * inserted: its parent and the siblings between to insert. The return
1351 * value is interpreted as the new reference of the affected
1352 * <code>Node</code> object; if it is not <b>null </b>, it is passed
1353 * to the node's <code>setReference()</code> method.
1354 *
1355 * @param newNode the node to be inserted
1356 * @param parent the parent node
1357 * @param sibling1 the sibling after which the node is to be inserted;
1358 * can be <b>null </b> if the new node is going to be the first child
1359 * node
1360 * @param sibling2 the sibling before which the node is to be inserted;
1361 * can be <b>null </b> if the new node is going to be the last child
1362 * node
1363 * @return the reference object for the node to be inserted
1364 */
1365 protected abstract Object insert(Node newNode, Node parent, Node sibling1, Node sibling2);
1366 }
1367 }