View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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>&quot;database.tables&quot;</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                 // simply retrieve a property; this is enough for
672                 // triggering a reload
673                 ((ConfigData) it.next()).getConfiguration().getProperty(
674                         PROP_RELOAD_CHECK);
675             }
676             catch (Exception ex)
677             {
678                 // ignore all exceptions, e.g. missing property exceptions
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         // find the root node in this hierarchy
722         while (current != null)
723         {
724             root = current;
725             current = current.getParentNode();
726         }
727 
728         // Check with the root nodes of the child configurations
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 &quot;transformed&quot; 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                 // Build the complete path
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             // Copy data of the root node to the new path
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 }