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