001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.configuration;
018    
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Set;
026    
027    import org.apache.commons.configuration.event.ConfigurationEvent;
028    import org.apache.commons.configuration.event.ConfigurationListener;
029    import org.apache.commons.configuration.tree.ConfigurationNode;
030    import org.apache.commons.configuration.tree.DefaultConfigurationKey;
031    import org.apache.commons.configuration.tree.DefaultConfigurationNode;
032    import org.apache.commons.configuration.tree.DefaultExpressionEngine;
033    import org.apache.commons.configuration.tree.ExpressionEngine;
034    import org.apache.commons.configuration.tree.NodeCombiner;
035    import org.apache.commons.configuration.tree.UnionCombiner;
036    import org.apache.commons.configuration.tree.ViewNode;
037    
038    /**
039     * <p>
040     * A hierarchical composite configuration class.
041     * </p>
042     * <p>
043     * This class maintains a list of configuration objects, which can be added
044     * using the divers <code>addConfiguration()</code> methods. After that the
045     * configurations can be accessed either by name (if one was provided when the
046     * configuration was added) or by index. For the whole set of managed
047     * configurations a logical node structure is constructed. For this purpose a
048     * <code>{@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}</code>
049     * object can be set. This makes it possible to specify different algorithms for
050     * the combination process.
051     * </p>
052     * <p>
053     * The big advantage of this class is that it creates a truly hierarchical
054     * structure of all the properties stored in the contained configurations - even
055     * if some of them are no hierarchical configurations per se. So all enhanced
056     * features provided by a hierarchical configuration (e.g. choosing an
057     * expression engine) are applicable.
058     * </p>
059     * <p>
060     * The class works by registering itself as an event listener at all added
061     * configurations. So it gets notified whenever one of these configurations is
062     * changed and can invalidate its internal node structure. The next time a
063     * property is accessed the node structure will be re-constructed using the
064     * current state of the managed configurations. Note that, depending on the used
065     * <code>NodeCombiner</code>, this may be a complex operation.
066     * </p>
067     * <p>
068     * Because of the way a <code>CombinedConfiguration</code> is working it has
069     * more or less view character: it provides a logic view on the configurations
070     * it contains. In this constellation not all methods defined for hierarchical
071     * configurations - especially methods that update the stored properties - can
072     * be implemented in a consistent manner. Using such methods (like
073     * <code>addProperty()</code>, or <code>clearProperty()</code> on a
074     * <code>CombinedConfiguration</code> is not strictly forbidden, however,
075     * depending on the current <code>{@link NodeCombiner}</code> and the involved
076     * properties, the results may be different than expected. Some examples may
077     * illustrate this:
078     * </p>
079     * <p>
080     * <ul>
081     * <li>Imagine a <code>CombinedConfiguration</code> <em>cc</em> containing
082     * two child configurations with the following content:
083     * <dl>
084     * <dt>user.properties</dt>
085     * <dd>
086     *
087     * <pre>
088     * gui.background = blue
089     * gui.position = (10, 10, 400, 200)
090     * </pre>
091     *
092     * </dd>
093     * <dt>default.properties</dt>
094     * <dd>
095     *
096     * <pre>
097     * gui.background = black
098     * gui.foreground = white
099     * 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    }