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