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