KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > configuration > HierarchicalConfiguration


1 /*
2  * Copyright 2001-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License")
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.apache.commons.configuration;
18
19 import java.io.Serializable JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.Collection JavaDoc;
22 import java.util.HashSet JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.LinkedList JavaDoc;
25 import java.util.List JavaDoc;
26 import java.util.Set JavaDoc;
27 import java.util.Stack JavaDoc;
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  * &lt;database&gt;
45  * &lt;tables&gt;
46  * &lt;table&gt;
47  * &lt;name&gt;users&lt;/name&gt;
48  * &lt;fields&gt;
49  * &lt;field&gt;
50  * &lt;name&gt;lid&lt;/name&gt;
51  * &lt;type&gt;long&lt;/name&gt;
52  * &lt;/field&gt;
53  * &lt;field&gt;
54  * &lt;name&gt;usrName&lt;/name&gt;
55  * &lt;type&gt;java.lang.String&lt;/type&gt;
56  * &lt;/field&gt;
57  * ...
58  * &lt;/fields&gt;
59  * &lt;/table&gt;
60  * &lt;table&gt;
61  * &lt;name&gt;documents&lt;/name&gt;
62  * &lt;fields&gt;
63  * &lt;field&gt;
64  * &lt;name&gt;docid&lt;/name&gt;
65  * &lt;type&gt;long&lt;/type&gt;
66  * &lt;/field&gt;
67  * ...
68  * &lt;/fields&gt;
69  * &lt;/table&gt;
70  * ...
71  * &lt;/tables&gt;
72  * &lt;/database&gt;
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 JavaDoc 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 JavaDoc("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 JavaDoc getProperty(String JavaDoc key)
143     {
144         List JavaDoc nodes = fetchNodeList(key);
145
146         if (nodes.size() == 0)
147         {
148             return null;
149         }
150         else
151         {
152             List JavaDoc list = new ArrayList JavaDoc();
153             for (Iterator JavaDoc 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 JavaDoc key, Object JavaDoc 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 JavaDoc key, Collection JavaDoc 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             // fetchAddNode() does not really fetch the last component,
286
// but one before. So we must perform an additional step.
287
ConfigurationKey keyNew = new ConfigurationKey(kit.currentKey(true));
288             keyNew.append(NEW_KEY);
289             parent = fetchAddNode(keyNew.iterator(), parent);
290         }
291
292         for (Iterator JavaDoc 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 JavaDoc prefix)
319     {
320         Collection JavaDoc 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 JavaDoc it = nodes.iterator(); it.hasNext();)
330         {
331             Node nd = (Node) it.next();
332             nd.visit(visitor, null);
333
334             List JavaDoc 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 &quot;contained&quot; 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 JavaDoc key)
358     {
359         return getProperty(key) != null;
360     }
361
362     /**
363      * @inheritDoc
364      */

365     public void setProperty(String JavaDoc key, Object JavaDoc value)
366     {
367         Iterator JavaDoc itNodes = fetchNodeList(key).iterator();
368         Iterator JavaDoc itValues = PropertyConverter.toIterator(value, getDelimiter());
369         while (itNodes.hasNext() && itValues.hasNext())
370         {
371             ((Node) itNodes.next()).setValue(itValues.next());
372         }
373
374         // Add additional nodes if necessary
375
while (itValues.hasNext())
376         {
377             addPropertyDirect(key, itValues.next());
378         }
379
380         // Remove remaining nodes
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      * &quot;foo&quot; and a property with the key &quot;foo.bar&quot;, 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 JavaDoc key)
396     {
397         List JavaDoc nodes = fetchNodeList(key);
398
399         for (Iterator JavaDoc 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 JavaDoc key)
413     {
414         List JavaDoc nodes = fetchNodeList(key);
415
416         for (Iterator JavaDoc 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 JavaDoc 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 JavaDoc getKeys(String JavaDoc prefix)
445     {
446         DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
447         List JavaDoc nodes = fetchNodeList(prefix);
448         ConfigurationKey key = new ConfigurationKey();
449
450         for (Iterator JavaDoc itNodes = nodes.iterator(); itNodes.hasNext();)
451         {
452             Node node = (Node) itNodes.next();
453             for (Iterator JavaDoc 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 JavaDoc 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 JavaDoc fetchNodeList(String JavaDoc key)
484     {
485         List JavaDoc nodes = new LinkedList JavaDoc();
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 JavaDoc data)
500     {
501         if (!keyPart.hasNext())
502         {
503             data.add(node);
504         }
505         else
506         {
507             String JavaDoc key = keyPart.nextKey(true);
508             List JavaDoc 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 JavaDoc 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 JavaDoc("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 JavaDoc keyPart = keyIt.nextKey(true);
606
607         if (keyIt.hasNext())
608         {
609             List JavaDoc 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 JavaDoc 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 JavaDoc, Cloneable JavaDoc
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 JavaDoc name;
677
678         /** Stores the value of this node. */
679         private Object JavaDoc value;
680
681         /** Stores a reference to an object this node is associated with. */
682         private Object JavaDoc reference;
683
684         /** Stores the children of this node. */
685         private LinkedMap children; // Explict type here or we
686

687         // will get a findbugs error
688
// because Map doesn't imply
689
// Serializable
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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc c = (List JavaDoc) children.get(child.getName());
808             if (c == null)
809             {
810                 c = new ArrayList JavaDoc();
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 JavaDoc getChildren()
825         {
826             List JavaDoc result = new ArrayList JavaDoc();
827
828             if (children != null)
829             {
830                 for (Iterator JavaDoc it = children.values().iterator(); it.hasNext();)
831                 {
832                     result.addAll((Collection JavaDoc) 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 JavaDoc getChildren(String JavaDoc name)
847         {
848             if (name == null || children == null)
849             {
850                 return getChildren();
851             }
852
853             List JavaDoc list = new ArrayList JavaDoc();
854             List JavaDoc c = (List JavaDoc) 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 JavaDoc c = (List JavaDoc) 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 JavaDoc name)
907         {
908             if (children == null)
909             {
910                 return false;
911             }
912
913             List JavaDoc nodes = (List JavaDoc) 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 JavaDoc it = children.values().iterator();
933                 children = null;
934                 while (it.hasNext())
935                 {
936                     nodesRemoved((Collection JavaDoc) 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 JavaDoc it = children.values().iterator(); it.hasNext() && !visitor.terminate();)
969                 {
970                     Collection JavaDoc col = (Collection JavaDoc) it.next();
971                     for (Iterator JavaDoc 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 JavaDoc clone()
992         {
993             try
994             {
995                 return super.clone();
996             }
997             catch (CloneNotSupportedException JavaDoc cex)
998             {
999                 return null; // should not happen
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 JavaDoc nodes)
1022        {
1023            for (Iterator JavaDoc 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     * &quot;Defined&quot; 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 JavaDoc keyList;
1133
1134        /** Stores a prefix for the keys. */
1135        private String JavaDoc prefix;
1136
1137        /**
1138         * Default constructor.
1139         */

1140        public DefinedKeysVisitor()
1141        {
1142            keyList = new HashSet JavaDoc();
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 JavaDoc 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 JavaDoc 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 JavaDoc buf = new StringBuffer JavaDoc(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 JavaDoc 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 JavaDoc();
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 JavaDoc children = node.getChildren().iterator();
1296            Node sibling1 = null;
1297            Node nd = null;
1298
1299            while (children.hasNext())
1300            {
1301                // find the next new node
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                    // find all following new nodes
1311
List JavaDoc newNodes = new LinkedList JavaDoc();
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                    // Insert all new nodes
1327
Node sibling2 = (nd.getReference() == null) ? null : nd;
1328                    for (Iterator JavaDoc it = newNodes.iterator(); it.hasNext();)
1329                    {
1330                        Node insertNode = (Node) it.next();
1331                        if (insertNode.getReference() == null)
1332                        {
1333                            Object JavaDoc 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 JavaDoc insert(Node newNode, Node parent, Node sibling1, Node sibling2);
1366    }
1367}
Popular Tags