Coverage Report - org.apache.commons.configuration.CombinedConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
CombinedConfiguration
99%
114/115
100%
23/23
2,057
CombinedConfiguration$ConfigData
100%
30/30
100%
4/4
2,057
 
 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  12
 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  4
     private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine();
 185  
 
 186  
     /** Constant for the default node combiner. */
 187  4
     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  78
     {
 222  78
         setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
 223  78
         clear();
 224  78
     }
 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  64
         this(null);
 235  64
     }
 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  86
         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  106
         if (nodeCombiner == null)
 260  
         {
 261  1
             throw new IllegalArgumentException(
 262  
                     "Node combiner must not be null!");
 263  
         }
 264  105
         this.nodeCombiner = nodeCombiner;
 265  105
         invalidate();
 266  105
     }
 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  1105
         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  7
         this.forceReloadCheck = forceReloadCheck;
 294  7
     }
 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  138
         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  1
         this.conversionExpressionEngine = conversionExpressionEngine;
 327  1
     }
 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  135
         if (config == null)
 349  
         {
 350  1
             throw new IllegalArgumentException(
 351  
                     "Added configuration must not be null!");
 352  
         }
 353  134
         if (name != null && namedConfigurations.containsKey(name))
 354  
         {
 355  1
             throw new ConfigurationRuntimeException(
 356  
                     "A configuration with the name '"
 357  
                             + name
 358  
                             + "' already exists in this combined configuration!");
 359  
         }
 360  
 
 361  133
         ConfigData cd = new ConfigData(config, name, at);
 362  133
         configurations.add(cd);
 363  133
         if (name != null)
 364  
         {
 365  76
             namedConfigurations.put(name, config);
 366  
         }
 367  
 
 368  133
         config.addConfigurationListener(this);
 369  133
         invalidate();
 370  133
     }
 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  31
         addConfiguration(config, name, null);
 383  31
     }
 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  13
         addConfiguration(config, null, null);
 395  12
     }
 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  114
         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  37
         ConfigData cd = (ConfigData) configurations.get(index);
 419  37
         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  22
         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  5
         for (int index = 0; index < getNumberOfConfigurations(); index++)
 443  
         {
 444  4
             if (((ConfigData) configurations.get(index)).getConfiguration() == config)
 445  
             {
 446  4
                 removeConfigurationAt(index);
 447  4
                 return true;
 448  
             }
 449  
         }
 450  
 
 451  1
         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  6
         ConfigData cd = (ConfigData) configurations.remove(index);
 463  6
         if (cd.getName() != null)
 464  
         {
 465  4
             namedConfigurations.remove(cd.getName());
 466  
         }
 467  6
         cd.getConfiguration().removeConfigurationListener(this);
 468  6
         invalidate();
 469  6
         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  3
         Configuration conf = getConfiguration(name);
 482  3
         if (conf != null)
 483  
         {
 484  2
             removeConfiguration(conf);
 485  
         }
 486  3
         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  14
         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  2129
         combinedRoot = null;
 513  2129
         fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
 514  2129
     }
 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  3588
         if (!event.isBeforeUpdate())
 526  
         {
 527  1805
             invalidate();
 528  
         }
 529  3588
     }
 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  1143
         if (combinedRoot == null)
 541  
         {
 542  82
             combinedRoot = constructCombinedNode();
 543  
         }
 544  1143
         return combinedRoot;
 545  
     }
 546  
 
 547  
     /**
 548  
      * Clears this configuration. All contained configurations will be removed.
 549  
      */
 550  
     public void clear()
 551  
     {
 552  80
         fireEvent(EVENT_CLEAR, null, null, true);
 553  80
         configurations = new ArrayList();
 554  80
         namedConfigurations = new HashMap();
 555  80
         fireEvent(EVENT_CLEAR, null, null, false);
 556  80
         invalidate();
 557  80
     }
 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  2
         CombinedConfiguration copy = (CombinedConfiguration) super.clone();
 571  2
         copy.clear();
 572  2
         for (Iterator it = configurations.iterator(); it.hasNext();)
 573  
         {
 574  4
             ConfigData cd = (ConfigData) it.next();
 575  4
             copy.addConfiguration((AbstractConfiguration) ConfigurationUtils
 576  
                     .cloneConfiguration(cd.getConfiguration()), cd.getName(),
 577  
                     cd.getAt());
 578  
         }
 579  
 
 580  2
         copy.setRootNode(new DefaultConfigurationNode());
 581  2
         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  7
         if (key == null)
 610  
         {
 611  1
             throw new IllegalArgumentException("Key must not be null!");
 612  
         }
 613  
 
 614  6
         List nodes = fetchNodeList(key);
 615  6
         if (nodes.isEmpty())
 616  
         {
 617  1
             return null;
 618  
         }
 619  
 
 620  5
         Iterator it = nodes.iterator();
 621  5
         Configuration source = findSourceConfiguration((ConfigurationNode) it
 622  
                 .next());
 623  9
         while (it.hasNext())
 624  
         {
 625  5
             Configuration src = findSourceConfiguration((ConfigurationNode) it
 626  
                     .next());
 627  5
             if (src != source)
 628  
             {
 629  1
                 throw new IllegalArgumentException("The key " + key
 630  
                         + " is defined by multiple sources!");
 631  
             }
 632  
         }
 633  
 
 634  4
         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  1100
         if (isForceReloadCheck())
 649  
         {
 650  5
             performReloadCheck();
 651  
         }
 652  
 
 653  1100
         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  5
         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  8
                 ((ConfigData) it.next()).getConfiguration().getProperty(
 674  
                         PROP_RELOAD_CHECK);
 675  
             }
 676  0
             catch (Exception ex)
 677  
             {
 678  
                 // ignore all exceptions, e.g. missing property exceptions
 679  
                 ;
 680  8
             }
 681  
         }
 682  5
     }
 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  82
         if (getNumberOfConfigurations() < 1)
 692  
         {
 693  10
             return new ViewNode();
 694  
         }
 695  
 
 696  
         else
 697  
         {
 698  72
             Iterator it = configurations.iterator();
 699  72
             ConfigurationNode node = ((ConfigData) it.next())
 700  
                     .getTransformedRoot();
 701  138
             while (it.hasNext())
 702  
             {
 703  66
                 node = getNodeCombiner().combine(node,
 704  
                         ((ConfigData) it.next()).getTransformedRoot());
 705  
             }
 706  72
             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  10
         ConfigurationNode root = null;
 719  10
         ConfigurationNode current = node;
 720  
 
 721  
         // find the root node in this hierarchy
 722  40
         while (current != null)
 723  
         {
 724  30
             root = current;
 725  30
             current = current.getParentNode();
 726  
         }
 727  
 
 728  
         // Check with the root nodes of the child configurations
 729  10
         for (Iterator it = configurations.iterator(); it.hasNext();)
 730  
         {
 731  13
             ConfigData cd = (ConfigData) it.next();
 732  13
             if (root == cd.getRootNode())
 733  
             {
 734  9
                 return cd.getConfiguration();
 735  
             }
 736  
         }
 737  
 
 738  1
         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  133
         {
 772  133
             configuration = config;
 773  133
             name = n;
 774  133
             atPath = parseAt(at);
 775  133
             this.at = at;
 776  133
         }
 777  
 
 778  
         /**
 779  
          * Returns the stored configuration.
 780  
          *
 781  
          * @return the configuration
 782  
          */
 783  
         public AbstractConfiguration getConfiguration()
 784  
         {
 785  212
             return configuration;
 786  
         }
 787  
 
 788  
         /**
 789  
          * Returns the configuration's name.
 790  
          *
 791  
          * @return the name
 792  
          */
 793  
         public String getName()
 794  
         {
 795  14
             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  4
             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  13
             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  138
             ViewNode result = new ViewNode();
 829  138
             ViewNode atParent = result;
 830  
 
 831  138
             if (atPath != null)
 832  
             {
 833  
                 // Build the complete path
 834  12
                 for (Iterator it = atPath.iterator(); it.hasNext();)
 835  
                 {
 836  14
                     ViewNode node = new ViewNode();
 837  14
                     node.setName((String) it.next());
 838  14
                     atParent.addChild(node);
 839  14
                     atParent = node;
 840  
                 }
 841  
             }
 842  
 
 843  
             // Copy data of the root node to the new path
 844  138
             HierarchicalConfiguration hc = ConfigurationUtils
 845  
                     .convertToHierarchical(getConfiguration(),
 846  
                             getConversionExpressionEngine());
 847  138
             atParent.appendChildren(hc.getRootNode());
 848  138
             atParent.appendAttributes(hc.getRootNode());
 849  138
             rootNode = hc.getRootNode();
 850  
 
 851  138
             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  133
             if (at == null)
 863  
             {
 864  121
                 return null;
 865  
             }
 866  
 
 867  12
             Collection result = new ArrayList();
 868  12
             DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
 869  
                     AT_ENGINE, at).iterator();
 870  26
             while (it.hasNext())
 871  
             {
 872  14
                 result.add(it.nextKey());
 873  
             }
 874  12
             return result;
 875  
         }
 876  
     }
 877  
 }