001: package net.myvietnam.mvncore.configuration;
002:
003: /* ====================================================================
004: * The Apache Software License, Version 1.1
005: *
006: * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
007: * reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution, if
022: * any, must include the following acknowledgement:
023: * "This product includes software developed by the
024: * Apache Software Foundation (http://www.apache.org/)."
025: * Alternately, this acknowledgement may appear in the software itself,
026: * if and wherever such third-party acknowledgements normally appear.
027: *
028: * 4. The names "The Jakarta Project", "Commons", and "Apache Software
029: * Foundation" must not be used to endorse or promote products derived
030: * from this software without prior written permission. For written
031: * permission, please contact apache@apache.org.
032: *
033: * 5. Products derived from this software may not be called "Apache"
034: * nor may "Apache" appear in their names without prior written
035: * permission of the Apache Software Foundation.
036: *
037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
048: * SUCH DAMAGE.
049: * ====================================================================
050: *
051: * This software consists of voluntary contributions made by many
052: * individuals on behalf of the Apache Software Foundation. For more
053: * information on the Apache Software Foundation, please see
054: * <http://www.apache.org/>.
055: */
056:
057: import java.io.Serializable;
058: import java.util.ArrayList;
059: import java.util.Collection;
060: import java.util.HashSet;
061: import java.util.Iterator;
062: import java.util.LinkedList;
063: import java.util.List;
064: import java.util.Map;
065: import java.util.Set;
066: import java.util.Stack;
067:
068: import org.apache.commons.collections.SequencedHashMap;
069: import org.apache.commons.lang.StringUtils;
070:
071: /**
072: * <p>A specialized configuration class that extends its base class by the
073: * ability of keeping more structure in the stored properties.</p>
074: * <p>There are some sources of configuration data that cannot be stored
075: * very well in a <code>BaseConfiguration</code> object because then their
076: * structure is lost. This is especially true for XML documents. This class
077: * can deal with such structured configuration sources by storing the
078: * properties in a tree-like organization.</p>
079: * <p>The internal used storage form allows for a more sophisticated access to
080: * single properties. As an example consider the following XML document:</p>
081: * <p><pre>
082: * <database>
083: * <tables>
084: * <table>
085: * <name>users</name>
086: * <fields>
087: * <field>
088: * <name>lid</name>
089: * <type>long</name>
090: * </field>
091: * <field>
092: * <name>usrName</name>
093: * <type>java.lang.String</type>
094: * </field>
095: * ...
096: * </fields>
097: * </table>
098: * <table>
099: * <name>documents</name>
100: * <fields>
101: * <field>
102: * <name>docid</name>
103: * <type>long</type>
104: * </field>
105: * ...
106: * </fields>
107: * </table>
108: * ...
109: * </tables>
110: * </database>
111: * </pre></p>
112: * <p>If this document is parsed and stored in a
113: * <code>HierarchicalConfiguration</code> object (which can be done by one of
114: * the sub classes), there are enhanced possibilities of accessing properties.
115: * The keys for querying information can contain indices that select a certain
116: * element if there are multiple hits.</p>
117: * <p>For instance the key <code>tables.table(0).name</code> can be used to
118: * find out the name of the first table. In opposite
119: * <code>tables.table.name</code> would return a collection with the names of
120: * all available tables. Similarily the key
121: * <code>tables.table(1).fields.field.name</code> returns a collection with the
122: * names of all fields of the second table. If another index is added after the
123: * <code>field</code> element, a single field can be accessed:
124: * <code>tables.table(1).fields.field(0).name</code>.</p>
125: * <p>There is a <code>getMaxIndex()</code> method that returns the maximum
126: * allowed index that can be added to a given property key. This method can be
127: * used to iterate over all values defined for a certain property.</p>
128: *
129: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
130: * @version $Id: HierarchicalConfiguration.java,v 1.3 2006/10/26 08:30:35 minhnn Exp $
131: */
132: public class HierarchicalConfiguration extends AbstractConfiguration {
133: /** Constant for a new dummy key.*/
134: private static final String NEW_KEY = "newKey";
135:
136: /** Stores the root node of this configuration.*/
137: private Node root = new Node();
138:
139: /**
140: * Creates a new instance of <code>HierarchicalConfiguration</code>.
141: */
142: public HierarchicalConfiguration() {
143: super ();
144: }
145:
146: /**
147: * Creates a new instance of <code>HierarchicalConfiguration</code>
148: * and initializes it with default properties.
149: * @param defaults default properties to be used
150: */
151: public HierarchicalConfiguration(Configuration defaults) {
152: super (defaults);
153: }
154:
155: /**
156: * Returns the root node of this hierarchical configuration.
157: * @return the root node
158: */
159: public Node getRoot() {
160: return root;
161: }
162:
163: /**
164: * Sets the root node of this hierarchical configuration.
165: * @param node the root node
166: */
167: public void setRoot(Node node) {
168: if (node == null) {
169: throw new IllegalArgumentException(
170: "Root node must not be null!");
171: } /* if */
172: root = node;
173: }
174:
175: /**
176: * Fetches the specified property. Performs a recursive lookup in the
177: * tree with the configuration properties.
178: * @param key the key to be looked up
179: * @return the found value
180: */
181: protected Object getPropertyDirect(String key) {
182: List nodes = fetchNodeList(key);
183:
184: if (nodes.size() == 0) {
185: return null;
186: } /* if */
187: else {
188: Container cont = new Container();
189: for (Iterator it = nodes.iterator(); it.hasNext();) {
190: Node nd = (Node) it.next();
191: if (nd.getValue() != null) {
192: cont.add(nd.getValue());
193: } /* if */
194: } /* for */
195:
196: if (cont.size() < 1) {
197: return null;
198: } /* if */
199: else {
200: return (cont.size() == 1) ? cont.get(0) : cont;
201: } /* else */
202: } /* else */
203: }
204:
205: /**
206: * <p>Adds the property with the specified key.</p>
207: * <p>To be able to deal with the structure supported by this configuration
208: * implementation the passed in key is of importance, especially the
209: * indices it might contain. The following example should clearify this:
210: * Suppose the actual configuration contains the following elements:</p>
211: * <p><pre>
212: * tables
213: * +-- table
214: * +-- name = user
215: * +-- fields
216: * +-- field
217: * +-- name = uid
218: * +-- field
219: * +-- name = firstName
220: * ...
221: * +-- table
222: * +-- name = documents
223: * +-- fields
224: * ...
225: * </pre></p>
226: * <p>In this example a database structure is defined, e.g. all fields of
227: * the first table could be accessed using the key
228: * <code>tables.table(0).fields.field.name</code>. If now properties are
229: * to be added, it must be exactly specified at which position in the
230: * hierarchy the new property is to be inserted. So to add a new field name
231: * to a table it is not enough to say just</p>
232: * <p><pre>
233: * config.addProperty("tables.table.fields.field.name", "newField");
234: * </pre></p>
235: * <p>The statement given above contains some ambiguity. For instance
236: * it is not clear, to which table the new field should be added. If this
237: * method finds such an ambiguity, it is resolved by following the last
238: * valid path. Here this would be the last table. The same is true for the
239: * <code>field</code>; because there are multiple fields and no explicit
240: * index is provided, a new <code>name</code> property would be
241: * added to the last field - which is propably not what was desired.</p>
242: * <p>To make things clear explicit indices should be provided whenever
243: * possible. In the example above the exact table could be specified by
244: * providing an index for the <code>table</code> element as in
245: * <code>tables.table(1).fields</code>. By specifying an index it can also
246: * be expressed that at a given position in the configuration tree a new
247: * branch should be added. In the example above we did not want to add
248: * an additional <code>name</code> element to the last field of the table,
249: * but we want a complete new <code>field</code> element. This can be
250: * achieved by specifying an invalid index (like -1) after the element
251: * where a new branch should be created. Given this our example would run:
252: * </p><p><pre>
253: * config.addProperty("tables.table(1).fields.field(-1).name", "newField");
254: * </pre></p>
255: * <p>With this notation it is possible to add new branches everywhere.
256: * We could for instance create a new <code>table</code> element by
257: * specifying</p>
258: * <p><pre>
259: * config.addProperty("tables.table(-1).fields.field.name", "newField2");
260: * </pre></p>
261: * <p>(Note that because after the <code>table</code> element a new
262: * branch is created indices in following elements are not relevant; the
263: * branch is new so there cannot be any ambiguities.)</p>
264: * @param key the key of the new property
265: * @param obj the value of the new property
266: */
267: protected void addPropertyDirect(String key, Object obj) {
268: ConfigurationKey.KeyIterator it = new ConfigurationKey(key)
269: .iterator();
270: Node parent = fetchAddNode(it, getRoot());
271:
272: Node child = new Node(it.currentKey(true));
273: child.setValue(obj);
274: parent.addChild(child);
275: }
276:
277: /**
278: * Adds a collection of nodes at the specified position of the
279: * configuration tree. This method works similar to
280: * <code>addProperty()</code>, but instead of a single property a whole
281: * collection of nodes can be added - and thus complete configuration
282: * sub trees. E.g. with this method it is possible to add parts of
283: * another <code>HierarchicalConfiguration</code> object to this object.
284: * @param key the key where the nodes are to be added; can be <b>null</b>,
285: * then they are added to the root node
286: * @param nodes a collection with the <code>Node</code> objects to be
287: * added
288: */
289: public void addNodes(String key, Collection nodes) {
290: if (nodes == null || nodes.isEmpty()) {
291: return;
292: } /* if */
293:
294: Node parent;
295: if (StringUtils.isEmpty(key)) {
296: parent = getRoot();
297: } /* if */
298: else {
299: ConfigurationKey.KeyIterator kit = new ConfigurationKey(key)
300: .iterator();
301: parent = fetchAddNode(kit, getRoot());
302:
303: // fetchAddNode() does not really fetch the last component,
304: // but one before. So we must perform an additional step.
305: ConfigurationKey keyNew = new ConfigurationKey(kit
306: .currentKey(true));
307: keyNew.append(NEW_KEY);
308: parent = fetchAddNode(keyNew.iterator(), parent);
309: } /* else */
310:
311: for (Iterator it = nodes.iterator(); it.hasNext();) {
312: parent.addChild((Node) it.next());
313: } /* for */
314: }
315:
316: /**
317: * Checks if this configuration is empty. Empty means that there are
318: * no keys with any values, though there can be some (empty) nodes.
319: * @return a flag if this configuration is empty
320: */
321: public boolean isEmpty() {
322: return !nodeDefined(getRoot());
323: }
324:
325: /**
326: * Checks if the specified key is contained in this configuration.
327: * Note that for this configuration the term "contained" means
328: * that the key has an associated value. If there is a node for this key
329: * that has no value but children (either defined or undefined), this
330: * method will still return <b>false</b>.
331: * @param key the key to be chekced
332: * @return a flag if this key is contained in this configuration
333: */
334: public boolean containsKey(String key) {
335: return getPropertyDirect(key) != null;
336: }
337:
338: /**
339: * Removes all values of the property with the given name.
340: * @param key the key of the property to be removed
341: */
342: public void clearProperty(String key) {
343: List nodes = fetchNodeList(key);
344:
345: for (Iterator it = nodes.iterator(); it.hasNext();) {
346: removeNode((Node) it.next());
347: } /* for */
348: }
349:
350: /**
351: * <p>Returns an iterator with all keys defined in this configuration.</p>
352: * <p>Note that the keys returned by this method will not contain
353: * any indices. This means that some structure will be lost.</p>
354: * @return an iterator with the defined keys in this configuration
355: */
356: public Iterator getKeys() {
357: DefinedKeysVisitor visitor = new DefinedKeysVisitor();
358: getRoot().visit(visitor, new ConfigurationKey());
359: return visitor.getKeyList().iterator();
360: }
361:
362: /**
363: * Creates a new <code>Configuration</code> object containing all keys
364: * that start with the specified prefix. This implementation will return
365: * a <code>HierarchicalConfiguration</code> object so that the structure
366: * of the keys will be saved.
367: * @param prefix the prefix of the keys for the subset
368: * @return a new configuration object representing the selected subset
369: */
370: public Configuration subset(String prefix) {
371: Collection nodes = fetchNodeList(prefix);
372: if (nodes.isEmpty()) {
373: return null;
374: } /* if */
375:
376: HierarchicalConfiguration result = new HierarchicalConfiguration();
377: CloneVisitor visitor = new CloneVisitor();
378:
379: for (Iterator it = nodes.iterator(); it.hasNext();) {
380: Node nd = (Node) it.next();
381: nd.visit(visitor, null);
382:
383: Container children = visitor.getClone().getChildren();
384: if (children.size() > 0) {
385: for (int i = 0; i < children.size(); i++) {
386: result.getRoot().addChild((Node) children.get(i));
387: } /* for */
388: } /* if */
389: else {
390: // In this case we cannot shorten the key because only
391: // values are found without further child nodes.
392: result.getRoot().addChild(visitor.getClone());
393: } /* else */
394: } /* for */
395:
396: return (result.isEmpty()) ? null : result;
397: }
398:
399: /**
400: * Returns the maximum defined index for the given key. This is
401: * useful if there are multiple values for this key. They can then be
402: * addressed separately by specifying indices from 0 to the return value
403: * of this method.
404: * @param key the key to be checked
405: * @return the maximum defined index for this key
406: */
407: public int getMaxIndex(String key) {
408: return fetchNodeList(key).size() - 1;
409: }
410:
411: /**
412: * Helper method for fetching a list of all nodes that are addressed by
413: * the specified key.
414: * @param key the key
415: * @return a list with all affected nodes (never <b>null</b>)
416: */
417: protected List fetchNodeList(String key) {
418: List nodes = new LinkedList();
419: findPropertyNodes(new ConfigurationKey(key).iterator(),
420: getRoot(), nodes);
421: return nodes;
422: }
423:
424: /**
425: * Recursive helper method for fetching a property. This method
426: * processes all facets of a configuration key, traverses the tree of
427: * properties and fetches the the nodes of all matching properties.
428: * @param keyPart the configuration key iterator
429: * @param node the actual node
430: * @param data here the found nodes are stored
431: */
432: protected void findPropertyNodes(
433: ConfigurationKey.KeyIterator keyPart, Node node,
434: Collection data) {
435: if (!keyPart.hasNext()) {
436: data.add(node);
437: } /* if */
438:
439: else {
440: String key = keyPart.nextKey(true);
441: Container children = node.getChildren(key);
442: if (keyPart.hasIndex()) {
443: if (keyPart.getIndex() < children.size()
444: && keyPart.getIndex() >= 0) {
445: findPropertyNodes(
446: (ConfigurationKey.KeyIterator) keyPart
447: .clone(), (Node) children
448: .get(keyPart.getIndex()), data);
449: } /* if */
450: } /* if */
451:
452: else {
453: for (Iterator it = children.iterator(); it.hasNext();) {
454: findPropertyNodes(
455: (ConfigurationKey.KeyIterator) keyPart
456: .clone(), (Node) it.next(), data);
457: } /* for */
458: } /* else */
459: }
460: }
461:
462: /**
463: * Checks if the specified node is defined.
464: * @param node the node to be checked
465: * @return a flag if this node is defined
466: */
467: protected boolean nodeDefined(Node node) {
468: DefinedVisitor visitor = new DefinedVisitor();
469: node.visit(visitor, null);
470: return visitor.isDefined();
471: }
472:
473: /**
474: * Removes the specified node from this configuration. This method
475: * ensures that parent nodes that become undefined by this operation
476: * are also removed.
477: * @param node the node to be removed
478: */
479: protected void removeNode(Node node) {
480: Node parent = node.getParent();
481: if (parent != null) {
482: parent.remove(node);
483: if (!nodeDefined(parent)) {
484: removeNode(parent);
485: } /* if */
486: } /* if */
487: }
488:
489: /**
490: * Returns a reference to the parent node of an add operation.
491: * Nodes for new properties can be added as children of this node.
492: * If the path for the specified key does not exist so far, it is created
493: * now.
494: * @param keyIt the iterator for the key of the new property
495: * @param startNode the node to start the search with
496: * @return the parent node for the add operation
497: */
498: protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt,
499: Node startNode) {
500: if (!keyIt.hasNext()) {
501: throw new IllegalArgumentException("Key must be defined!");
502: } /* if */
503:
504: return createAddPath(keyIt, findLastPathNode(keyIt, startNode));
505: }
506:
507: /**
508: * Finds the last existing node for an add operation. This method
509: * traverses the configuration tree along the specified key. The last
510: * existing node on this path is returned.
511: * @param keyIt the key iterator
512: * @param node the actual node
513: * @return the last existing node on the given path
514: */
515: protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt,
516: Node node) {
517: String keyPart = keyIt.nextKey(true);
518:
519: if (keyIt.hasNext()) {
520: Container c = node.getChildren(keyPart);
521: int idx = (keyIt.hasIndex()) ? keyIt.getIndex()
522: : c.size() - 1;
523: if (idx < 0 || idx >= c.size()) {
524: return node;
525: } /* if */
526: else {
527: return findLastPathNode(keyIt, (Node) c.get(idx));
528: } /* else */
529: } /* if */
530:
531: else {
532: return node;
533: } /* else */
534: }
535:
536: /**
537: * Creates the missing nodes for adding a new property. This method
538: * ensures that there are corresponding nodes for all components of the
539: * specified configuration key.
540: * @param keyIt the key iterator
541: * @param root the base node of the path to be created
542: * @return the last node of the path
543: */
544: protected Node createAddPath(ConfigurationKey.KeyIterator keyIt,
545: Node root) {
546: if (keyIt.hasNext()) {
547: Node child = new Node(keyIt.currentKey(true));
548: root.addChild(child);
549: keyIt.next();
550: return createAddPath(keyIt, child);
551: } /* if */
552: else {
553: return root;
554: } /* else */
555: }
556:
557: /**
558: * Helper method for adding all elements of a collection to a
559: * container.
560: * @param cont the container
561: * @param items the collection to be added
562: */
563: private static void addContainer(Container cont, Collection items) {
564: for (Iterator it = items.iterator(); it.hasNext();) {
565: cont.add(it.next());
566: } /* for */
567: }
568:
569: /**
570: * A data class for storing (hierarchical) property information. A property
571: * can have a value and an arbitrary number of child properties.
572: *
573: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
574: */
575: public static class Node implements Serializable, Cloneable {
576: /** Stores a reference to this node's parent.*/
577: private Node parent;
578:
579: /** Stores the name of this node.*/
580: private String name;
581:
582: /** Stores the value of this node.*/
583: private Object value;
584:
585: /** Stores the children of this node.*/
586: private Map children;
587:
588: /**
589: * Creates a new instance of <code>Node</code>.
590: */
591: public Node() {
592: this (null);
593: }
594:
595: /**
596: * Creates a new instance of <code>Node</code> and sets the name.
597: * @param name the node's name
598: */
599: public Node(String name) {
600: setName(name);
601: }
602:
603: /**
604: * Returns the name of this node.
605: * @return the node name
606: */
607: public String getName() {
608: return name;
609: }
610:
611: /**
612: * Returns the value of this node.
613: * @return the node value (may be <b>null</b>)
614: */
615: public Object getValue() {
616: return value;
617: }
618:
619: /**
620: * Returns the parent of this node.
621: * @return this node's parent (can be <b>null</b>)
622: */
623: public Node getParent() {
624: return parent;
625: }
626:
627: /**
628: * Sets the name of this node.
629: * @param string the node name
630: */
631: public void setName(String string) {
632: name = string;
633: }
634:
635: /**
636: * Sets the value of this node.
637: * @param object the node value
638: */
639: public void setValue(Object object) {
640: value = object;
641: }
642:
643: /**
644: * Sets the parent of this node.
645: * @param node the parent node
646: */
647: public void setParent(Node node) {
648: parent = node;
649: }
650:
651: /**
652: * Adds the specified child object to this node. Note that there can
653: * be multiple children with the same name.
654: * @param child the child to be added
655: */
656: public void addChild(Node child) {
657: if (children == null) {
658: children = new SequencedHashMap();
659: } /* if */
660:
661: List c = (List) children.get(child.getName());
662: if (c == null) {
663: c = new ArrayList();
664: children.put(child.getName(), c);
665: } /* if */
666:
667: c.add(child);
668: child.setParent(this );
669: }
670:
671: /**
672: * Returns a list with the child nodes of this node.
673: * @return a list with the children (can be empty, but never
674: * <b>null</b>)
675: */
676: public Container getChildren() {
677: Container result = new Container();
678:
679: if (children != null) {
680: for (Iterator it = children.values().iterator(); it
681: .hasNext();) {
682: addContainer(result, (Collection) it.next());
683: } /* for */
684: } /* if */
685:
686: return result;
687: }
688:
689: /**
690: * Returns a list with this node's children with the given name.
691: * @param name the name of the children
692: * @return a list with all chidren with this name; may be empty, but
693: * never <b>null</b>
694: */
695: public Container getChildren(String name) {
696: if (name == null || children == null) {
697: return getChildren();
698: } /* if */
699:
700: Container cont = new Container();
701: List c = (List) children.get(name);
702: if (c != null) {
703: addContainer(cont, c);
704: } /* if */
705:
706: return cont;
707: }
708:
709: /**
710: * Removes the specified child from this node.
711: * @param child the child node to be removed
712: * @return a flag if the child could be found
713: */
714: public boolean remove(Node child) {
715: if (children == null) {
716: return false;
717: } /* if */
718:
719: List c = (List) children.get(child.getName());
720: if (c == null) {
721: return false;
722: } /* if */
723:
724: else {
725: if (c.remove(child)) {
726: if (c.isEmpty()) {
727: children.remove(child.getName());
728: } /* if */
729: return true;
730: } /* if */
731: else {
732: return false;
733: } /* else */
734: } /* else */
735: }
736:
737: /**
738: * Removes all children with the given name.
739: * @param name the name of the children to be removed
740: * @return a flag if children with this name existed
741: */
742: public boolean remove(String name) {
743: if (children == null) {
744: return false;
745: } /* if */
746:
747: return children.remove(name) != null;
748: }
749:
750: /**
751: * Removes all children of this node.
752: */
753: public void removeChildren() {
754: children = null;
755: }
756:
757: /**
758: * A generic method for traversing this node and all of its children.
759: * This method sends the passed in visitor to this node and all of its
760: * children.
761: * @param visitor the visitor
762: * @param key here a configuration key with the name of the root node
763: * of the iteration can be passed; if this key is not <b>null</b>, the
764: * full pathes to the visited nodes are builded and passed to the
765: * visitor's <code>visit()</code> methods
766: */
767: public void visit(NodeVisitor visitor, ConfigurationKey key) {
768: int length = 0;
769: if (key != null) {
770: length = key.length();
771: if (getName() != null) {
772: key.append(getName());
773: } /* if */
774: } /* if */
775:
776: visitor.visitBeforeChildren(this , key);
777:
778: if (children != null) {
779: for (Iterator it = children.values().iterator(); it
780: .hasNext()
781: && !visitor.terminate();) {
782: Collection col = (Collection) it.next();
783: for (Iterator it2 = col.iterator(); it2.hasNext()
784: && !visitor.terminate();) {
785: ((Node) it2.next()).visit(visitor, key);
786: } /* for */
787: } /* for */
788: } /* if */
789:
790: if (key != null) {
791: key.setLength(length);
792: } /* if */
793: visitor.visitAfterChildren(this , key);
794: }
795:
796: /**
797: * Creates a copy of this object. This is not a deep copy, the children
798: * are not cloned.
799: * @return a copy of this object
800: */
801: protected Object clone() {
802: try {
803: return super .clone();
804: } /* try */
805: catch (CloneNotSupportedException cex) {
806: return null; // should not happen
807: } /* catch */
808: }
809: }
810:
811: /**
812: * <p>Definition of a visitor class for traversing a node and all of its
813: * children.</p>
814: * <p>This class defines the interface of a visitor for <code>Node</code>
815: * objects and provides a default implementation. The method
816: * <code>visit()</code> of <code>Node</code> implements a generic
817: * iteration algorithm based on the <em>Visitor</em> pattern. By
818: * providing different implementations of visitors it is possible to
819: * collect different data during the iteration process.</p>
820: *
821: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
822: */
823: public static class NodeVisitor {
824: /**
825: * Visits the specified node. This method is called during iteration
826: * for each node before its children have been visited.
827: * @param node the actual node
828: * @param key the key of this node (may be <b>null</b>)
829: */
830: public void visitBeforeChildren(Node node, ConfigurationKey key) {
831: }
832:
833: /**
834: * Visits the specified node after its children have been processed.
835: * This gives a visitor the opportunity of collecting additional data
836: * after the child nodes have been visited.
837: * @param node the node to be visited
838: * @param key the key of this node (may be <b>null</b>)
839: */
840: public void visitAfterChildren(Node node, ConfigurationKey key) {
841: }
842:
843: /**
844: * Returns a flag that indicates if iteration should be stopped. This
845: * method is called after each visited node. It can be useful for
846: * visitors that search a specific node. If this node is found, the
847: * whole process can be stopped. This base implementation always
848: * returns <b>false</b>.
849: * @return a flag if iteration should be stopped
850: */
851: public boolean terminate() {
852: return false;
853: }
854: }
855:
856: /**
857: * A specialized visitor that checks if a node is defined.
858: * "Defined" in this terms means that the node or at least one
859: * of its sub nodes is associated with a value.
860: *
861: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
862: */
863: static class DefinedVisitor extends NodeVisitor {
864: /** Stores the defined flag.*/
865: private boolean defined;
866:
867: /**
868: * Checks if iteration should be stopped. This can be done if the first
869: * defined node is found.
870: * @return a flag if iteration should be stopped
871: */
872: public boolean terminate() {
873: return isDefined();
874: }
875:
876: /**
877: * Visits the node. Checks if a value is defined.
878: * @param node the actual node
879: * @param key the key of this node
880: */
881: public void visitBeforeChildren(Node node, ConfigurationKey key) {
882: defined = node.getValue() != null;
883: }
884:
885: /**
886: * Returns the defined flag.
887: * @return the defined flag
888: */
889: public boolean isDefined() {
890: return defined;
891: }
892: }
893:
894: /**
895: * A specialized visitor that fills a list with keys that are defined in
896: * a node hierarchy.
897: *
898: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
899: */
900: static class DefinedKeysVisitor extends NodeVisitor {
901: /** Stores the list to be filled.*/
902: private Set keyList;
903:
904: /**
905: * Default constructor.
906: */
907: public DefinedKeysVisitor() {
908: keyList = new HashSet();
909: }
910:
911: /**
912: * Returns the list with all defined keys.
913: * @return the list with the defined keys
914: */
915: public Set getKeyList() {
916: return keyList;
917: }
918:
919: /**
920: * Visits the specified node. If this node has a value, its key is
921: * added to the internal list.
922: * @param node the node to be visited
923: * @param key the key of this node
924: */
925: public void visitBeforeChildren(Node node, ConfigurationKey key) {
926: if (node.getValue() != null && key != null) {
927: keyList.add(key.toString());
928: } /* if */
929: }
930: }
931:
932: /**
933: * A specialized visitor that is able to create a deep copy of a node
934: * hierarchy.
935: *
936: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
937: */
938: static class CloneVisitor extends NodeVisitor {
939: /** A stack with the actual object to be copied.*/
940: private Stack copyStack;
941:
942: /** Stores the result of the clone process.*/
943: private Node result;
944:
945: /**
946: * Creates a new instance of <code>CloneVisitor</code>.
947: */
948: public CloneVisitor() {
949: copyStack = new Stack();
950: }
951:
952: /**
953: * Visits the specified node after its children have been processed.
954: * @param node the node
955: * @param key the key of this node
956: */
957: public void visitAfterChildren(Node node, ConfigurationKey key) {
958: copyStack.pop();
959: if (copyStack.isEmpty()) {
960: result = node;
961: } /* if */
962: }
963:
964: /**
965: * Visits and copies the specified node.
966: * @param node the node
967: * @param key the key of this node
968: */
969: public void visitBeforeChildren(Node node, ConfigurationKey key) {
970: Node copy = (Node) node.clone();
971: copy.removeChildren();
972:
973: if (!copyStack.isEmpty()) {
974: ((Node) copyStack.peek()).addChild(copy);
975: } /* if */
976:
977: copyStack.push(copy);
978: }
979:
980: /**
981: * Returns the result of the clone process. This is the root node of
982: * the cloned node hierarchy.
983: * @return the cloned root node
984: */
985: public Node getClone() {
986: return result;
987: }
988: }
989: }
|