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 }