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