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