Coverage Report - org.apache.commons.configuration.CombinedConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
CombinedConfiguration
99%
81/82
100%
13/13
1,846
CombinedConfiguration$ConfigData
100%
28/28
100%
4/4
1,846
 
 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 truely 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 add 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. Node that, depending on the used
 64  
  * <code>NodeCombiner</code>, this may be a complex operation.
 65  
  * </p>
 66  
  * <p>
 67  
  * It is not strictly forbidden to manipulate a
 68  
  * <code>CombinedConfiguration</code> directly, but the results may be
 69  
  * unpredictable. For instance some node combiners use special view nodes for
 70  
  * linking parts of the original configurations' data together. If new
 71  
  * properties are added to such a special node, they do not belong to any of the
 72  
  * managed configurations and thus hang in the air. It is also possible that
 73  
  * direct updates on a <code>CombinedConfiguration</code> are incompatible
 74  
  * with the used node combiner (e.g. if the
 75  
  * <code>{@link org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}</code>
 76  
  * is used and properties are removed the resulting node structure may be
 77  
  * incorrect because some properties that were hidden by the removed properties
 78  
  * are not visible). So it is recommended to perform updates only on the managed
 79  
  * configurations.
 80  
  * </p>
 81  
  * <p>
 82  
  * Whenever the node structure of a <code>CombinedConfiguration</code> becomes
 83  
  * invalid (either because one of the contained configurations was modified or
 84  
  * because the <code>invalidate()</code> method was directly called) an event
 85  
  * is generated. So this can be detected by interested event listeners. This
 86  
  * also makes it possible to add a combined configuration into another one.
 87  
  * </p>
 88  
  * <p>
 89  
  * Implementation note: Adding and removing configurations to and from a
 90  
  * combined configuration is not thread-safe. If a combined configuration is
 91  
  * manipulated by multiple threads, the developer has to take care about
 92  
  * properly synchronization.
 93  
  * </p>
 94  
  *
 95  
  * @author <a
 96  
  * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons
 97  
  * Configuration team</a>
 98  
  * @since 1.3
 99  
  * @version $Id: CombinedConfiguration.java 439648 2006-09-02 20:42:10Z oheger $
 100  
  */
 101  11
 public class CombinedConfiguration extends HierarchicalConfiguration implements
 102  
         ConfigurationListener, Cloneable
 103  
 {
 104  
     /**
 105  
      * Constant for the invalidate event that is fired when the internal node
 106  
      * structure becomes invalid.
 107  
      */
 108  
     public static final int EVENT_COMBINED_INVALIDATE = 40;
 109  
 
 110  
     /**
 111  
      * The serial version ID.
 112  
      */
 113  
     private static final long serialVersionUID = 8338574525528692307L;
 114  
 
 115  
     /** Constant for the expression engine for parsing the at path. */
 116  6
     private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine();
 117  
 
 118  
     /** Constant for the default node combiner. */
 119  3
     private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
 120  
 
 121  
     /** Stores the combiner. */
 122  
     private NodeCombiner nodeCombiner;
 123  
 
 124  
     /** Stores the combined root node. */
 125  
     private ConfigurationNode combinedRoot;
 126  
 
 127  
     /** Stores a list with the contained configurations. */
 128  
     private List configurations;
 129  
 
 130  
     /** Stores a map with the named configurations. */
 131  
     private Map namedConfigurations;
 132  
 
 133  
     /**
 134  
      * Creates a new instance of <code>CombinedConfiguration</code> and
 135  
      * initializes the combiner to be used.
 136  
      *
 137  
      * @param comb the node combiner (can be <b>null</b>, then a union combiner
 138  
      * is used as default)
 139  
      */
 140  
     public CombinedConfiguration(NodeCombiner comb)
 141  45
     {
 142  45
         setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
 143  45
         clear();
 144  45
     }
 145  
 
 146  
     /**
 147  
      * Creates a new instance of <code>CombinedConfiguration</code> that uses
 148  
      * a union combiner.
 149  
      *
 150  
      * @see org.apache.commons.configuration.tree.UnionCombiner
 151  
      */
 152  
     public CombinedConfiguration()
 153  
     {
 154  39
         this(null);
 155  39
     }
 156  
 
 157  
     /**
 158  
      * Returns the node combiner that is used for creating the combined node
 159  
      * structure.
 160  
      *
 161  
      * @return the node combiner
 162  
      */
 163  
     public NodeCombiner getNodeCombiner()
 164  
     {
 165  361
         return nodeCombiner;
 166  
     }
 167  
 
 168  
     /**
 169  
      * Sets the node combiner. This object will be used when the combined node
 170  
      * structure is to be constructed. It must not be <b>null</b>, otherwise an
 171  
      * <code>IllegalArgumentException</code> exception is thrown. Changing the
 172  
      * node combiner causes an invalidation of this combined configuration, so
 173  
      * that the new combiner immediately takes effect.
 174  
      *
 175  
      * @param nodeCombiner the node combiner
 176  
      */
 177  
     public void setNodeCombiner(NodeCombiner nodeCombiner)
 178  
     {
 179  64
         if (nodeCombiner == null)
 180  
         {
 181  1
             throw new IllegalArgumentException(
 182  
                     "Node combiner must not be null!");
 183  
         }
 184  63
         this.nodeCombiner = nodeCombiner;
 185  63
         invalidate();
 186  63
     }
 187  
 
 188  
     /**
 189  
      * Adds a new configuration to this combined configuration. It is possible
 190  
      * (but not mandatory) to give the new configuration a name. This name must
 191  
      * be unique, otherwise a <code>ConfigurationRuntimeException</code> will
 192  
      * be thrown. With the optional <code>at</code> argument you can specify
 193  
      * where in the resulting node structure the content of the added
 194  
      * configuration should appear. This is a string that uses dots as property
 195  
      * delimiters (independent on the current expression engine). For instance
 196  
      * if you pass in the string <code>&quot;database.tables&quot;</code>,
 197  
      * all properties of the added configuration will occur in this branch.
 198  
      *
 199  
      * @param config the configuration to add (must not be <b>null</b>)
 200  
      * @param name the name of this configuration (can be <b>null</b>)
 201  
      * @param at the position of this configuration in the combined tree (can be
 202  
      * <b>null</b>)
 203  
      */
 204  
     public void addConfiguration(AbstractConfiguration config, String name,
 205  
             String at)
 206  
     {
 207  80
         if (config == null)
 208  
         {
 209  1
             throw new IllegalArgumentException(
 210  
                     "Added configuration must not be null!");
 211  
         }
 212  79
         if (name != null && namedConfigurations.containsKey(name))
 213  
         {
 214  1
             throw new ConfigurationRuntimeException(
 215  
                     "A configuration with the name '"
 216  
                             + name
 217  
                             + "' already exists in this combined configuration!");
 218  
         }
 219  
 
 220  78
         ConfigData cd = new ConfigData(config, name, at);
 221  78
         configurations.add(cd);
 222  78
         if (name != null)
 223  
         {
 224  32
             namedConfigurations.put(name, config);
 225  
         }
 226  
 
 227  78
         config.addConfigurationListener(this);
 228  78
         invalidate();
 229  78
     }
 230  
 
 231  
     /**
 232  
      * Adds a new configuration to this combined configuration with an optional
 233  
      * name. The new configuration's properties will be added under the root of
 234  
      * the combined node structure.
 235  
      *
 236  
      * @param config the configuration to add (must not be <b>null</b>)
 237  
      * @param name the name of this configuration (can be <b>null</b>)
 238  
      */
 239  
     public void addConfiguration(AbstractConfiguration config, String name)
 240  
     {
 241  13
         addConfiguration(config, name, null);
 242  13
     }
 243  
 
 244  
     /**
 245  
      * Adds a new configuration to this combined configuration. The new
 246  
      * configuration is not given a name. Its properties will be added under the
 247  
      * root of the combined node structure.
 248  
      *
 249  
      * @param config the configuration to add (must not be <b>null</b>)
 250  
      */
 251  
     public void addConfiguration(AbstractConfiguration config)
 252  
     {
 253  8
         addConfiguration(config, null, null);
 254  7
     }
 255  
 
 256  
     /**
 257  
      * Returns the number of configurations that are contained in this combined
 258  
      * configuration.
 259  
      *
 260  
      * @return the number of contained configurations
 261  
      */
 262  
     public int getNumberOfConfigurations()
 263  
     {
 264  65
         return configurations.size();
 265  
     }
 266  
 
 267  
     /**
 268  
      * Returns the configuration at the specified index. The contained
 269  
      * configurations are numbered in the order they were added to this combined
 270  
      * configuration. The index of the first configuration is 0.
 271  
      *
 272  
      * @param index the index
 273  
      * @return the configuration at this index
 274  
      */
 275  
     public Configuration getConfiguration(int index)
 276  
     {
 277  37
         ConfigData cd = (ConfigData) configurations.get(index);
 278  37
         return cd.getConfiguration();
 279  
     }
 280  
 
 281  
     /**
 282  
      * Returns the configuration with the given name. This can be <b>null</b>
 283  
      * if no such configuration exists.
 284  
      *
 285  
      * @param name the name of the configuration
 286  
      * @return the configuration with this name
 287  
      */
 288  
     public Configuration getConfiguration(String name)
 289  
     {
 290  13
         return (Configuration) namedConfigurations.get(name);
 291  
     }
 292  
 
 293  
     /**
 294  
      * Removes the specified configuration from this combined configuration.
 295  
      *
 296  
      * @param config the configuration to be removed
 297  
      * @return a flag whether this configuration was found and could be removed
 298  
      */
 299  
     public boolean removeConfiguration(Configuration config)
 300  
     {
 301  5
         for (int index = 0; index < getNumberOfConfigurations(); index++)
 302  
         {
 303  4
             if (((ConfigData) configurations.get(index)).getConfiguration() == config)
 304  
             {
 305  4
                 removeConfigurationAt(index);
 306  4
                 return true;
 307  
             }
 308  
         }
 309  
 
 310  1
         return false;
 311  
     }
 312  
 
 313  
     /**
 314  
      * Removes the configuration at the specified index.
 315  
      *
 316  
      * @param index the index
 317  
      * @return the removed configuration
 318  
      */
 319  
     public Configuration removeConfigurationAt(int index)
 320  
     {
 321  6
         ConfigData cd = (ConfigData) configurations.remove(index);
 322  6
         if (cd.getName() != null)
 323  
         {
 324  4
             namedConfigurations.remove(cd.getName());
 325  
         }
 326  6
         cd.getConfiguration().removeConfigurationListener(this);
 327  6
         invalidate();
 328  6
         return cd.getConfiguration();
 329  
     }
 330  
 
 331  
     /**
 332  
      * Removes the configuration with the specified name.
 333  
      *
 334  
      * @param name the name of the configuration to be removed
 335  
      * @return the removed configuration (<b>null</b> if this configuration
 336  
      * was not found)
 337  
      */
 338  
     public Configuration removeConfiguration(String name)
 339  
     {
 340  3
         Configuration conf = getConfiguration(name);
 341  3
         if (conf != null)
 342  
         {
 343  2
             removeConfiguration(conf);
 344  
         }
 345  3
         return conf;
 346  
     }
 347  
 
 348  
     /**
 349  
      * Returns a set with the names of all configurations contained in this
 350  
      * combined configuration. Of course here are only these configurations
 351  
      * listed, for which a name was specified when they were added.
 352  
      *
 353  
      * @return a set with the names of the contained configurations (never
 354  
      * <b>null</b>)
 355  
      */
 356  
     public Set getConfigurationNames()
 357  
     {
 358  14
         return namedConfigurations.keySet();
 359  
     }
 360  
 
 361  
     /**
 362  
      * Invalidates this combined configuration. This means that the next time a
 363  
      * property is accessed the combined node structure must be re-constructed.
 364  
      * Invalidation of a combined configuration also means that an event of type
 365  
      * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other
 366  
      * events most times appear twice (once before and once after an update),
 367  
      * this event is only fired once (after update).
 368  
      */
 369  
     public void invalidate()
 370  
     {
 371  211
         synchronized (getNodeCombiner()) // use combiner as lock
 372  
         {
 373  211
             combinedRoot = null;
 374  211
         }
 375  211
         fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
 376  211
     }
 377  
 
 378  
     /**
 379  
      * Event listener call back for configuration update events. This method is
 380  
      * called whenever one of the contained configurations was modified. It
 381  
      * invalidates this combined configuration.
 382  
      *
 383  
      * @param event the update event
 384  
      */
 385  
     public void configurationChanged(ConfigurationEvent event)
 386  
     {
 387  16
         invalidate();
 388  16
     }
 389  
 
 390  
     /**
 391  
      * Returns the configuration root node of this combined configuration. This
 392  
      * method will construct a combined node structure using the current node
 393  
      * combiner if necessary.
 394  
      *
 395  
      * @return the combined root node
 396  
      */
 397  
     public ConfigurationNode getRootNode()
 398  
     {
 399  97
         synchronized (getNodeCombiner())
 400  
         {
 401  97
             if (combinedRoot == null)
 402  
             {
 403  36
                 combinedRoot = constructCombinedNode();
 404  
             }
 405  97
             return combinedRoot;
 406  0
         }
 407  
     }
 408  
 
 409  
     /**
 410  
      * Clears this configuration. All contained configurations will be removed.
 411  
      */
 412  
     public void clear()
 413  
     {
 414  48
         fireEvent(EVENT_CLEAR, null, null, true);
 415  48
         configurations = new ArrayList();
 416  48
         namedConfigurations = new HashMap();
 417  48
         fireEvent(EVENT_CLEAR, null, null, false);
 418  48
         invalidate();
 419  48
     }
 420  
 
 421  
     /**
 422  
      * Returns a copy of this object. This implementation performs a deep clone,
 423  
      * i.e. all contained configurations will be cloned, too. For this to work,
 424  
      * all contained configurations must be cloneable. Registered event
 425  
      * listeners won't be cloned. The clone will use the same node combiner than
 426  
      * the original.
 427  
      *
 428  
      * @return the copied object
 429  
      */
 430  
     public Object clone()
 431  
     {
 432  2
         CombinedConfiguration copy = (CombinedConfiguration) super.clone();
 433  2
         copy.clear();
 434  8
         for (Iterator it = configurations.iterator(); it.hasNext();)
 435  
         {
 436  4
             ConfigData cd = (ConfigData) it.next();
 437  4
             copy.addConfiguration((AbstractConfiguration) ConfigurationUtils
 438  
                     .cloneConfiguration(cd.getConfiguration()), cd.getName(),
 439  
                     cd.getAt());
 440  
         }
 441  
 
 442  2
         copy.setRootNode(new DefaultConfigurationNode());
 443  2
         return copy;
 444  
     }
 445  
 
 446  
     /**
 447  
      * Creates the root node of this combined configuration.
 448  
      *
 449  
      * @return the combined root node
 450  
      */
 451  
     private ConfigurationNode constructCombinedNode()
 452  
     {
 453  36
         if (getNumberOfConfigurations() < 1)
 454  
         {
 455  7
             return new ViewNode();
 456  
         }
 457  
 
 458  
         else
 459  
         {
 460  29
             Iterator it = configurations.iterator();
 461  29
             ConfigurationNode node = ((ConfigData) it.next())
 462  
                     .getTransformedRoot();
 463  99
             while (it.hasNext())
 464  
             {
 465  41
                 node = getNodeCombiner().combine(node,
 466  
                         ((ConfigData) it.next()).getTransformedRoot());
 467  
             }
 468  29
             return node;
 469  
         }
 470  
     }
 471  
 
 472  
     /**
 473  
      * An internal helper class for storing information about contained
 474  
      * configurations.
 475  
      */
 476  
     static class ConfigData
 477  
     {
 478  
         /** Stores a reference to the configuration. */
 479  
         private AbstractConfiguration configuration;
 480  
 
 481  
         /** Stores the name under which the configuration is stored. */
 482  
         private String name;
 483  
 
 484  
         /** Stores the at information as path of nodes. */
 485  
         private Collection atPath;
 486  
 
 487  
         /** Stores the at string.*/
 488  
         private String at;
 489  
 
 490  
         /**
 491  
          * Creates a new instance of <code>ConfigData</code> and initializes
 492  
          * it.
 493  
          *
 494  
          * @param config the configuration
 495  
          * @param n the name
 496  
          * @param at the at position
 497  
          */
 498  
         public ConfigData(AbstractConfiguration config, String n, String at)
 499  78
         {
 500  78
             configuration = config;
 501  78
             name = n;
 502  78
             atPath = parseAt(at);
 503  78
             this.at = at;
 504  78
         }
 505  
 
 506  
         /**
 507  
          * Returns the stored configuration.
 508  
          *
 509  
          * @return the configuration
 510  
          */
 511  
         public AbstractConfiguration getConfiguration()
 512  
         {
 513  127
             return configuration;
 514  
         }
 515  
 
 516  
         /**
 517  
          * Returns the configuration's name.
 518  
          *
 519  
          * @return the name
 520  
          */
 521  
         public String getName()
 522  
         {
 523  14
             return name;
 524  
         }
 525  
 
 526  
         /**
 527  
          * Returns the at position of this configuration.
 528  
          *
 529  
          * @return the at position
 530  
          */
 531  
         public String getAt()
 532  
         {
 533  4
             return at;
 534  
         }
 535  
 
 536  
         /**
 537  
          * Returns the transformed root node of the stored configuration. The
 538  
          * term &quot;transformed&quot; means that an eventually defined at path
 539  
          * has been applied.
 540  
          *
 541  
          * @return the transformed root node
 542  
          */
 543  
         public ConfigurationNode getTransformedRoot()
 544  
         {
 545  70
             ViewNode result = new ViewNode();
 546  70
             ViewNode atParent = result;
 547  
 
 548  70
             if (atPath != null)
 549  
             {
 550  
                 // Build the complete path
 551  32
                 for (Iterator it = atPath.iterator(); it.hasNext();)
 552  
                 {
 553  12
                     ViewNode node = new ViewNode();
 554  12
                     node.setName((String) it.next());
 555  12
                     atParent.addChild(node);
 556  12
                     atParent = node;
 557  
                 }
 558  
             }
 559  
 
 560  
             // Copy data of the root node to the new path
 561  70
             HierarchicalConfiguration hc = ConfigurationUtils
 562  
                     .convertToHierarchical(getConfiguration());
 563  70
             atParent.appendChildren(hc.getRootNode());
 564  70
             atParent.appendAttributes(hc.getRootNode());
 565  
 
 566  70
             return result;
 567  
         }
 568  
 
 569  
         /**
 570  
          * Splits the at path into its components.
 571  
          *
 572  
          * @param at the at string
 573  
          * @return a collection with the names of the single components
 574  
          */
 575  
         private Collection parseAt(String at)
 576  
         {
 577  78
             if (at == null)
 578  
             {
 579  67
                 return null;
 580  
             }
 581  
 
 582  11
             Collection result = new ArrayList();
 583  11
             DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
 584  
                     AT_ENGINE, at).iterator();
 585  35
             while (it.hasNext())
 586  
             {
 587  13
                 result.add(it.nextKey());
 588  
             }
 589  11
             return result;
 590  
         }
 591  
     }
 592  
 }