1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.configuration;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.apache.commons.configuration.event.ConfigurationEvent;
28 import org.apache.commons.configuration.event.ConfigurationListener;
29 import org.apache.commons.configuration.tree.ConfigurationNode;
30 import org.apache.commons.configuration.tree.DefaultConfigurationKey;
31 import org.apache.commons.configuration.tree.DefaultConfigurationNode;
32 import org.apache.commons.configuration.tree.DefaultExpressionEngine;
33 import org.apache.commons.configuration.tree.ExpressionEngine;
34 import org.apache.commons.configuration.tree.NodeCombiner;
35 import org.apache.commons.configuration.tree.UnionCombiner;
36 import org.apache.commons.configuration.tree.ViewNode;
37
38 /***
39 * <p>
40 * A hierarchical composite configuration class.
41 * </p>
42 * <p>
43 * This class maintains a list of configuration objects, which can be added
44 * using the divers <code>addConfiguration()</code> methods. After that the
45 * configurations can be accessed either by name (if one was provided when the
46 * configuration was added) or by index. For the whole set of managed
47 * configurations a logical node structure is constructed. For this purpose a
48 * <code>{@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}</code>
49 * object can be set. This makes it possible to specify different algorithms for
50 * the combination process.
51 * </p>
52 * <p>
53 * The big advantage of this class is that it creates a truly hierarchical
54 * structure of all the properties stored in the contained configurations - even
55 * if some of them are no hierarchical configurations per se. So all enhanced
56 * features provided by a hierarchical configuration (e.g. choosing an
57 * expression engine) are applicable.
58 * </p>
59 * <p>
60 * The class works by registering itself as an event listener at all added
61 * configurations. So it gets notified whenever one of these configurations is
62 * changed and can invalidate its internal node structure. The next time a
63 * property is accessed the node structure will be re-constructed using the
64 * current state of the managed configurations. Note that, depending on the used
65 * <code>NodeCombiner</code>, this may be a complex operation.
66 * </p>
67 * <p>
68 * Because of the way a <code>CombinedConfiguration</code> is working it has
69 * more or less view character: it provides a logic view on the configurations
70 * it contains. In this constellation not all methods defined for hierarchical
71 * configurations - especially methods that update the stored properties - can
72 * be implemented in a consistent manner. Using such methods (like
73 * <code>addProperty()</code>, or <code>clearProperty()</code> on a
74 * <code>CombinedConfiguration</code> is not strictly forbidden, however,
75 * depending on the current <code>{@link NodeCombiner}</code> and the involved
76 * properties, the results may be different than expected. Some examples may
77 * illustrate this:
78 * </p>
79 * <p>
80 * <ul>
81 * <li>Imagine a <code>CombinedConfiguration</code> <em>cc</em> containing
82 * two child configurations with the following content:
83 * <dl>
84 * <dt>user.properties</dt>
85 * <dd>
86 *
87 * <pre>
88 * gui.background = blue
89 * gui.position = (10, 10, 400, 200)
90 * </pre>
91 *
92 * </dd>
93 * <dt>default.properties</dt>
94 * <dd>
95 *
96 * <pre>
97 * gui.background = black
98 * gui.foreground = white
99 * home.dir = /data
100 * </pre>
101 *
102 * </dd>
103 * </dl>
104 * As a <code>NodeCombiner</code> a
105 * <code>{@link org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}</code>
106 * is used. This combiner will ensure that defined user settings take precedence
107 * over the default values. If the resulting <code>CombinedConfiguration</code>
108 * is queried for the background color, <code>blue</code> will be returned
109 * because this value is defined in <code>user.properties</code>. Now
110 * consider what happens if the key <code>gui.background</code> is removed
111 * from the <code>CombinedConfiguration</code>:
112 *
113 * <pre>cc.clearProperty("gui.background");</pre>
114 *
115 * Will a <code>cc.containsKey("gui.background")</code> now return <b>false</b>?
116 * No, it won't! The <code>clearProperty()</code> operation is executed on the
117 * node set of the combined configuration, which was constructed from the nodes
118 * of the two child configurations. It causes the value of the
119 * <em>background</em> node to be cleared, which is also part of the first
120 * child configuration. This modification of one of its child configurations
121 * causes the <code>CombinedConfiguration</code> to be re-constructed. This
122 * time the <code>OverrideCombiner</code> cannot find a
123 * <code>gui.background</code> property in the first child configuration, but
124 * it finds one in the second, and adds it to the resulting combined
125 * configuration. So the property is still present (with a different value now).</li>
126 * <li><code>addProperty()</code> can also be problematic: Most node
127 * combiners use special view nodes for linking parts of the original
128 * configurations' data together. If new properties are added to such a special
129 * node, they do not belong to any of the managed configurations and thus hang
130 * in the air. Using the same configurations as in the last example, the
131 * statement
132 *
133 * <pre>
134 * addProperty("database.user", "scott");
135 * </pre>
136 *
137 * would cause such a hanging property. If now one of the child configurations
138 * is changed and the <code>CombinedConfiguration</code> is re-constructed,
139 * this property will disappear! (Add operations are not problematic if they
140 * result in a child configuration being updated. For instance an
141 * <code>addProperty("home.url", "localhost");</code> will alter the second
142 * child configuration - because the prefix <em>home</em> is here already
143 * present; when the <code>CombinedConfiguration</code> is re-constructed,
144 * this change is taken into account.)</li>
145 * </ul>
146 * Because of such problems it is recommended to perform updates only on the
147 * managed child configurations.
148 * </p>
149 * <p>
150 * Whenever the node structure of a <code>CombinedConfiguration</code> becomes
151 * invalid (either because one of the contained configurations was modified or
152 * because the <code>invalidate()</code> method was directly called) an event
153 * is generated. So this can be detected by interested event listeners. This
154 * also makes it possible to add a combined configuration into another one.
155 * </p>
156 * <p>
157 * Implementation note: Adding and removing configurations to and from a
158 * combined configuration is not thread-safe. If a combined configuration is
159 * manipulated by multiple threads, the developer has to take care about
160 * properly synchronization.
161 * </p>
162 *
163 * @author <a
164 * href="http://commons.apache.org/configuration/team-list.html">Commons
165 * Configuration team</a>
166 * @since 1.3
167 * @version $Id: CombinedConfiguration.java 712401 2008-11-08 15:29:56Z oheger $
168 */
169 public class CombinedConfiguration extends HierarchicalConfiguration implements
170 ConfigurationListener, Cloneable
171 {
172 /***
173 * Constant for the invalidate event that is fired when the internal node
174 * structure becomes invalid.
175 */
176 public static final int EVENT_COMBINED_INVALIDATE = 40;
177
178 /***
179 * The serial version ID.
180 */
181 private static final long serialVersionUID = 8338574525528692307L;
182
183 /*** Constant for the expression engine for parsing the at path. */
184 private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine();
185
186 /*** Constant for the default node combiner. */
187 private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
188
189 /*** Constant for the name of the property used for the reload check.*/
190 private static final String PROP_RELOAD_CHECK = "CombinedConfigurationReloadCheck";
191
192 /*** Stores the combiner. */
193 private NodeCombiner nodeCombiner;
194
195 /*** Stores the combined root node. */
196 private volatile ConfigurationNode combinedRoot;
197
198 /*** Stores a list with the contained configurations. */
199 private List configurations;
200
201 /*** Stores a map with the named configurations. */
202 private Map namedConfigurations;
203
204 /***
205 * An expression engine used for converting child configurations to
206 * hierarchical ones.
207 */
208 private ExpressionEngine conversionExpressionEngine;
209
210 /*** A flag whether an enhanced reload check is to be performed.*/
211 private boolean forceReloadCheck;
212
213 /***
214 * Creates a new instance of <code>CombinedConfiguration</code> and
215 * initializes the combiner to be used.
216 *
217 * @param comb the node combiner (can be <b>null</b>, then a union combiner
218 * is used as default)
219 */
220 public CombinedConfiguration(NodeCombiner comb)
221 {
222 setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
223 clear();
224 }
225
226 /***
227 * Creates a new instance of <code>CombinedConfiguration</code> that uses
228 * a union combiner.
229 *
230 * @see org.apache.commons.configuration.tree.UnionCombiner
231 */
232 public CombinedConfiguration()
233 {
234 this(null);
235 }
236
237 /***
238 * Returns the node combiner that is used for creating the combined node
239 * structure.
240 *
241 * @return the node combiner
242 */
243 public NodeCombiner getNodeCombiner()
244 {
245 return nodeCombiner;
246 }
247
248 /***
249 * Sets the node combiner. This object will be used when the combined node
250 * structure is to be constructed. It must not be <b>null</b>, otherwise an
251 * <code>IllegalArgumentException</code> exception is thrown. Changing the
252 * node combiner causes an invalidation of this combined configuration, so
253 * that the new combiner immediately takes effect.
254 *
255 * @param nodeCombiner the node combiner
256 */
257 public void setNodeCombiner(NodeCombiner nodeCombiner)
258 {
259 if (nodeCombiner == null)
260 {
261 throw new IllegalArgumentException(
262 "Node combiner must not be null!");
263 }
264 this.nodeCombiner = nodeCombiner;
265 invalidate();
266 }
267
268 /***
269 * Returns a flag whether an enhanced reload check must be performed.
270 *
271 * @return the force reload check flag
272 * @since 1.4
273 */
274 public boolean isForceReloadCheck()
275 {
276 return forceReloadCheck;
277 }
278
279 /***
280 * Sets the force reload check flag. If this flag is set, each property
281 * access on this configuration will cause a reload check on the contained
282 * configurations. This is a workaround for a problem with some reload
283 * implementations that only check if a reload is required when they are
284 * triggered. Per default this mode is disabled. If the force reload check
285 * flag is set to <b>true</b>, accessing properties will be less
286 * performant, but reloads on contained configurations will be detected.
287 *
288 * @param forceReloadCheck the value of the flag
289 * @since 1.4
290 */
291 public void setForceReloadCheck(boolean forceReloadCheck)
292 {
293 this.forceReloadCheck = forceReloadCheck;
294 }
295
296 /***
297 * Returns the <code>ExpressionEngine</code> for converting flat child
298 * configurations to hierarchical ones.
299 *
300 * @return the conversion expression engine
301 * @since 1.6
302 */
303 public ExpressionEngine getConversionExpressionEngine()
304 {
305 return conversionExpressionEngine;
306 }
307
308 /***
309 * Sets the <code>ExpressionEngine</code> for converting flat child
310 * configurations to hierarchical ones. When constructing the root node for
311 * this combined configuration the properties of all child configurations
312 * must be combined to a single hierarchical node structure. In this
313 * process, non hierarchical configurations are converted to hierarchical
314 * ones first. This can be problematic if a child configuration contains
315 * keys that are no compatible with the default expression engine used by
316 * hierarchical configurations. Therefore it is possible to specify a
317 * specific expression engine to be used for this purpose.
318 *
319 * @param conversionExpressionEngine the conversion expression engine
320 * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine)
321 * @since 1.6
322 */
323 public void setConversionExpressionEngine(
324 ExpressionEngine conversionExpressionEngine)
325 {
326 this.conversionExpressionEngine = conversionExpressionEngine;
327 }
328
329 /***
330 * Adds a new configuration to this combined configuration. It is possible
331 * (but not mandatory) to give the new configuration a name. This name must
332 * be unique, otherwise a <code>ConfigurationRuntimeException</code> will
333 * be thrown. With the optional <code>at</code> argument you can specify
334 * where in the resulting node structure the content of the added
335 * configuration should appear. This is a string that uses dots as property
336 * delimiters (independent on the current expression engine). For instance
337 * if you pass in the string <code>"database.tables"</code>,
338 * all properties of the added configuration will occur in this branch.
339 *
340 * @param config the configuration to add (must not be <b>null</b>)
341 * @param name the name of this configuration (can be <b>null</b>)
342 * @param at the position of this configuration in the combined tree (can be
343 * <b>null</b>)
344 */
345 public void addConfiguration(AbstractConfiguration config, String name,
346 String at)
347 {
348 if (config == null)
349 {
350 throw new IllegalArgumentException(
351 "Added configuration must not be null!");
352 }
353 if (name != null && namedConfigurations.containsKey(name))
354 {
355 throw new ConfigurationRuntimeException(
356 "A configuration with the name '"
357 + name
358 + "' already exists in this combined configuration!");
359 }
360
361 ConfigData cd = new ConfigData(config, name, at);
362 configurations.add(cd);
363 if (name != null)
364 {
365 namedConfigurations.put(name, config);
366 }
367
368 config.addConfigurationListener(this);
369 invalidate();
370 }
371
372 /***
373 * Adds a new configuration to this combined configuration with an optional
374 * name. The new configuration's properties will be added under the root of
375 * the combined node structure.
376 *
377 * @param config the configuration to add (must not be <b>null</b>)
378 * @param name the name of this configuration (can be <b>null</b>)
379 */
380 public void addConfiguration(AbstractConfiguration config, String name)
381 {
382 addConfiguration(config, name, null);
383 }
384
385 /***
386 * Adds a new configuration to this combined configuration. The new
387 * configuration is not given a name. Its properties will be added under the
388 * root of the combined node structure.
389 *
390 * @param config the configuration to add (must not be <b>null</b>)
391 */
392 public void addConfiguration(AbstractConfiguration config)
393 {
394 addConfiguration(config, null, null);
395 }
396
397 /***
398 * Returns the number of configurations that are contained in this combined
399 * configuration.
400 *
401 * @return the number of contained configurations
402 */
403 public int getNumberOfConfigurations()
404 {
405 return configurations.size();
406 }
407
408 /***
409 * Returns the configuration at the specified index. The contained
410 * configurations are numbered in the order they were added to this combined
411 * configuration. The index of the first configuration is 0.
412 *
413 * @param index the index
414 * @return the configuration at this index
415 */
416 public Configuration getConfiguration(int index)
417 {
418 ConfigData cd = (ConfigData) configurations.get(index);
419 return cd.getConfiguration();
420 }
421
422 /***
423 * Returns the configuration with the given name. This can be <b>null</b>
424 * if no such configuration exists.
425 *
426 * @param name the name of the configuration
427 * @return the configuration with this name
428 */
429 public Configuration getConfiguration(String name)
430 {
431 return (Configuration) namedConfigurations.get(name);
432 }
433
434 /***
435 * Removes the specified configuration from this combined configuration.
436 *
437 * @param config the configuration to be removed
438 * @return a flag whether this configuration was found and could be removed
439 */
440 public boolean removeConfiguration(Configuration config)
441 {
442 for (int index = 0; index < getNumberOfConfigurations(); index++)
443 {
444 if (((ConfigData) configurations.get(index)).getConfiguration() == config)
445 {
446 removeConfigurationAt(index);
447 return true;
448 }
449 }
450
451 return false;
452 }
453
454 /***
455 * Removes the configuration at the specified index.
456 *
457 * @param index the index
458 * @return the removed configuration
459 */
460 public Configuration removeConfigurationAt(int index)
461 {
462 ConfigData cd = (ConfigData) configurations.remove(index);
463 if (cd.getName() != null)
464 {
465 namedConfigurations.remove(cd.getName());
466 }
467 cd.getConfiguration().removeConfigurationListener(this);
468 invalidate();
469 return cd.getConfiguration();
470 }
471
472 /***
473 * Removes the configuration with the specified name.
474 *
475 * @param name the name of the configuration to be removed
476 * @return the removed configuration (<b>null</b> if this configuration
477 * was not found)
478 */
479 public Configuration removeConfiguration(String name)
480 {
481 Configuration conf = getConfiguration(name);
482 if (conf != null)
483 {
484 removeConfiguration(conf);
485 }
486 return conf;
487 }
488
489 /***
490 * Returns a set with the names of all configurations contained in this
491 * combined configuration. Of course here are only these configurations
492 * listed, for which a name was specified when they were added.
493 *
494 * @return a set with the names of the contained configurations (never
495 * <b>null</b>)
496 */
497 public Set getConfigurationNames()
498 {
499 return namedConfigurations.keySet();
500 }
501
502 /***
503 * Invalidates this combined configuration. This means that the next time a
504 * property is accessed the combined node structure must be re-constructed.
505 * Invalidation of a combined configuration also means that an event of type
506 * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other
507 * events most times appear twice (once before and once after an update),
508 * this event is only fired once (after update).
509 */
510 public void invalidate()
511 {
512 combinedRoot = null;
513 fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
514 }
515
516 /***
517 * Event listener call back for configuration update events. This method is
518 * called whenever one of the contained configurations was modified. It
519 * invalidates this combined configuration.
520 *
521 * @param event the update event
522 */
523 public void configurationChanged(ConfigurationEvent event)
524 {
525 if (!event.isBeforeUpdate())
526 {
527 invalidate();
528 }
529 }
530
531 /***
532 * Returns the configuration root node of this combined configuration. This
533 * method will construct a combined node structure using the current node
534 * combiner if necessary.
535 *
536 * @return the combined root node
537 */
538 public ConfigurationNode getRootNode()
539 {
540 if (combinedRoot == null)
541 {
542 combinedRoot = constructCombinedNode();
543 }
544 return combinedRoot;
545 }
546
547 /***
548 * Clears this configuration. All contained configurations will be removed.
549 */
550 public void clear()
551 {
552 fireEvent(EVENT_CLEAR, null, null, true);
553 configurations = new ArrayList();
554 namedConfigurations = new HashMap();
555 fireEvent(EVENT_CLEAR, null, null, false);
556 invalidate();
557 }
558
559 /***
560 * Returns a copy of this object. This implementation performs a deep clone,
561 * i.e. all contained configurations will be cloned, too. For this to work,
562 * all contained configurations must be cloneable. Registered event
563 * listeners won't be cloned. The clone will use the same node combiner than
564 * the original.
565 *
566 * @return the copied object
567 */
568 public Object clone()
569 {
570 CombinedConfiguration copy = (CombinedConfiguration) super.clone();
571 copy.clear();
572 for (Iterator it = configurations.iterator(); it.hasNext();)
573 {
574 ConfigData cd = (ConfigData) it.next();
575 copy.addConfiguration((AbstractConfiguration) ConfigurationUtils
576 .cloneConfiguration(cd.getConfiguration()), cd.getName(),
577 cd.getAt());
578 }
579
580 copy.setRootNode(new DefaultConfigurationNode());
581 return copy;
582 }
583
584 /***
585 * Returns the configuration source, in which the specified key is defined.
586 * This method will determine the configuration node that is identified by
587 * the given key. The following constellations are possible:
588 * <ul>
589 * <li>If no node object is found for this key, <b>null</b> is returned.</li>
590 * <li>If the key maps to multiple nodes belonging to different
591 * configuration sources, a <code>IllegalArgumentException</code> is
592 * thrown (in this case no unique source can be determined).</li>
593 * <li>If exactly one node is found for the key, the (child) configuration
594 * object, to which the node belongs is determined and returned.</li>
595 * <li>For keys that have been added directly to this combined
596 * configuration and that do not belong to the namespaces defined by
597 * existing child configurations this configuration will be returned.</li>
598 * </ul>
599 *
600 * @param key the key of a configuration property
601 * @return the configuration, to which this property belongs or <b>null</b>
602 * if the key cannot be resolved
603 * @throws IllegalArgumentException if the key maps to multiple properties
604 * and the source cannot be determined, or if the key is <b>null</b>
605 * @since 1.5
606 */
607 public Configuration getSource(String key)
608 {
609 if (key == null)
610 {
611 throw new IllegalArgumentException("Key must not be null!");
612 }
613
614 List nodes = fetchNodeList(key);
615 if (nodes.isEmpty())
616 {
617 return null;
618 }
619
620 Iterator it = nodes.iterator();
621 Configuration source = findSourceConfiguration((ConfigurationNode) it
622 .next());
623 while (it.hasNext())
624 {
625 Configuration src = findSourceConfiguration((ConfigurationNode) it
626 .next());
627 if (src != source)
628 {
629 throw new IllegalArgumentException("The key " + key
630 + " is defined by multiple sources!");
631 }
632 }
633
634 return source;
635 }
636
637 /***
638 * Evaluates the passed in property key and returns a list with the matching
639 * configuration nodes. This implementation also evaluates the
640 * <em>force reload check</em> flag. If it is set,
641 * <code>performReloadCheck()</code> is invoked.
642 *
643 * @param key the property key
644 * @return a list with the matching configuration nodes
645 */
646 protected List fetchNodeList(String key)
647 {
648 if (isForceReloadCheck())
649 {
650 performReloadCheck();
651 }
652
653 return super.fetchNodeList(key);
654 }
655
656 /***
657 * Triggers the contained configurations to perform a reload check if
658 * necessary. This method is called when a property of this combined
659 * configuration is accessed and the <code>forceReloadCheck</code> property
660 * is set to <b>true</b>.
661 *
662 * @see #setForceReloadCheck(boolean)
663 * @since 1.6
664 */
665 protected void performReloadCheck()
666 {
667 for (Iterator it = configurations.iterator(); it.hasNext();)
668 {
669 try
670 {
671
672
673 ((ConfigData) it.next()).getConfiguration().getProperty(
674 PROP_RELOAD_CHECK);
675 }
676 catch (Exception ex)
677 {
678
679 ;
680 }
681 }
682 }
683
684 /***
685 * Creates the root node of this combined configuration.
686 *
687 * @return the combined root node
688 */
689 private ConfigurationNode constructCombinedNode()
690 {
691 if (getNumberOfConfigurations() < 1)
692 {
693 return new ViewNode();
694 }
695
696 else
697 {
698 Iterator it = configurations.iterator();
699 ConfigurationNode node = ((ConfigData) it.next())
700 .getTransformedRoot();
701 while (it.hasNext())
702 {
703 node = getNodeCombiner().combine(node,
704 ((ConfigData) it.next()).getTransformedRoot());
705 }
706 return node;
707 }
708 }
709
710 /***
711 * Determines the configuration that owns the specified node.
712 *
713 * @param node the node
714 * @return the owning configuration
715 */
716 private Configuration findSourceConfiguration(ConfigurationNode node)
717 {
718 ConfigurationNode root = null;
719 ConfigurationNode current = node;
720
721
722 while (current != null)
723 {
724 root = current;
725 current = current.getParentNode();
726 }
727
728
729 for (Iterator it = configurations.iterator(); it.hasNext();)
730 {
731 ConfigData cd = (ConfigData) it.next();
732 if (root == cd.getRootNode())
733 {
734 return cd.getConfiguration();
735 }
736 }
737
738 return this;
739 }
740
741 /***
742 * An internal helper class for storing information about contained
743 * configurations.
744 */
745 class ConfigData
746 {
747 /*** Stores a reference to the configuration. */
748 private AbstractConfiguration configuration;
749
750 /*** Stores the name under which the configuration is stored. */
751 private String name;
752
753 /*** Stores the at information as path of nodes. */
754 private Collection atPath;
755
756 /*** Stores the at string.*/
757 private String at;
758
759 /*** Stores the root node for this child configuration.*/
760 private ConfigurationNode rootNode;
761
762 /***
763 * Creates a new instance of <code>ConfigData</code> and initializes
764 * it.
765 *
766 * @param config the configuration
767 * @param n the name
768 * @param at the at position
769 */
770 public ConfigData(AbstractConfiguration config, String n, String at)
771 {
772 configuration = config;
773 name = n;
774 atPath = parseAt(at);
775 this.at = at;
776 }
777
778 /***
779 * Returns the stored configuration.
780 *
781 * @return the configuration
782 */
783 public AbstractConfiguration getConfiguration()
784 {
785 return configuration;
786 }
787
788 /***
789 * Returns the configuration's name.
790 *
791 * @return the name
792 */
793 public String getName()
794 {
795 return name;
796 }
797
798 /***
799 * Returns the at position of this configuration.
800 *
801 * @return the at position
802 */
803 public String getAt()
804 {
805 return at;
806 }
807
808 /***
809 * Returns the root node for this child configuration.
810 *
811 * @return the root node of this child configuration
812 * @since 1.5
813 */
814 public ConfigurationNode getRootNode()
815 {
816 return rootNode;
817 }
818
819 /***
820 * Returns the transformed root node of the stored configuration. The
821 * term "transformed" means that an eventually defined at path
822 * has been applied.
823 *
824 * @return the transformed root node
825 */
826 public ConfigurationNode getTransformedRoot()
827 {
828 ViewNode result = new ViewNode();
829 ViewNode atParent = result;
830
831 if (atPath != null)
832 {
833
834 for (Iterator it = atPath.iterator(); it.hasNext();)
835 {
836 ViewNode node = new ViewNode();
837 node.setName((String) it.next());
838 atParent.addChild(node);
839 atParent = node;
840 }
841 }
842
843
844 HierarchicalConfiguration hc = ConfigurationUtils
845 .convertToHierarchical(getConfiguration(),
846 getConversionExpressionEngine());
847 atParent.appendChildren(hc.getRootNode());
848 atParent.appendAttributes(hc.getRootNode());
849 rootNode = hc.getRootNode();
850
851 return result;
852 }
853
854 /***
855 * Splits the at path into its components.
856 *
857 * @param at the at string
858 * @return a collection with the names of the single components
859 */
860 private Collection parseAt(String at)
861 {
862 if (at == null)
863 {
864 return null;
865 }
866
867 Collection result = new ArrayList();
868 DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
869 AT_ENGINE, at).iterator();
870 while (it.hasNext())
871 {
872 result.add(it.nextKey());
873 }
874 return result;
875 }
876 }
877 }