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