Coverage Report - org.apache.commons.configuration.SubnodeConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
SubnodeConfiguration
100%
54/54
100%
18/18
2,3
 
 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.Collections;
 21  
 import java.util.List;
 22  
 
 23  
 import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
 24  
 import org.apache.commons.configuration.reloading.Reloadable;
 25  
 import org.apache.commons.configuration.tree.ConfigurationNode;
 26  
 
 27  
 /**
 28  
  * <p>
 29  
  * A specialized hierarchical configuration class that wraps a single node of
 30  
  * its parent configuration.
 31  
  * </p>
 32  
  * <p>
 33  
  * Configurations of this type are initialized with a parent configuration and a
 34  
  * configuration node of this configuration. This node becomes the root node of
 35  
  * the subnode configuration. All property accessor methods are evaluated
 36  
  * relative to this root node. A good use case for a
 37  
  * {@code SubnodeConfiguration} is when multiple properties from a
 38  
  * specific sub tree of the whole configuration need to be accessed. Then a
 39  
  * {@code SubnodeConfiguration} can be created with the parent node of
 40  
  * the affected sub tree as root node. This allows for simpler property keys and
 41  
  * is also more efficient.
 42  
  * </p>
 43  
  * <p>
 44  
  * A subnode configuration and its parent configuration operate on the same
 45  
  * hierarchy of configuration nodes. So if modifications are performed at the
 46  
  * subnode configuration, these changes are immediately visible in the parent
 47  
  * configuration. Analogously will updates of the parent configuration affect
 48  
  * the subnode configuration if the sub tree spanned by the subnode
 49  
  * configuration's root node is involved.
 50  
  * </p>
 51  
  * <p>
 52  
  * There are however changes at the parent configuration, which cause the
 53  
  * subnode configuration to become detached. An example for such a change is a
 54  
  * reload operation of a file-based configuration, which replaces all nodes of
 55  
  * the parent configuration. The subnode configuration per default still
 56  
  * references the old nodes. Another example are list structures: a subnode
 57  
  * configuration can be created to point on the <em>i</em>th element of the
 58  
  * list. Now list elements can be added or removed, so that the list elements'
 59  
  * indices change. In such a scenario the subnode configuration would always
 60  
  * point to the same list element, regardless of its current index.
 61  
  * </p>
 62  
  * <p>
 63  
  * To solve these problems and make a subnode configuration aware of
 64  
  * such structural changes of its parent, it is possible to associate a
 65  
  * subnode configuration with a configuration key. This can be done by calling
 66  
  * the {@code setSubnodeKey()} method. If here a key is set, the subnode
 67  
  * configuration will evaluate it on each access, thus ensuring that it is
 68  
  * always in sync with its parent. In this mode the subnode configuration really
 69  
  * behaves like a live-view on its parent. The price for this is a decreased
 70  
  * performance because now an additional evaluation has to be performed on each
 71  
  * property access. So this mode should only be used if necessary; if for
 72  
  * instance a subnode configuration is only used for a temporary convenient
 73  
  * access to a complex configuration, there is no need to make it aware for
 74  
  * structural changes of its parent. If a subnode configuration is created
 75  
  * using the {@link HierarchicalConfiguration#configurationAt(String, boolean)
 76  
  * configurationAt()} method of {@code HierarchicalConfiguration}
 77  
  * (which should be the preferred way), with an additional boolean parameter it
 78  
  * can be specified whether the resulting subnode configuration should be
 79  
  * aware of structural changes or not. Then the configuration key will be
 80  
  * automatically set.
 81  
  * </p>
 82  
  * <p>
 83  
  * <em>Note:</em> At the moment support for creating a subnode configuration
 84  
  * that is aware of structural changes of its parent from another subnode
 85  
  * configuration (a "sub subnode configuration") is limited. This only works if
 86  
  * <ol><li>the subnode configuration that serves as the parent for the new
 87  
  * subnode configuration is itself associated with a configuration key and</li>
 88  
  * <li>the key passed in to create the new subnode configuration is not too
 89  
  * complex (if configuration keys are used that contain indices, a corresponding
 90  
  * key that is valid from the parent configuration's point of view cannot be
 91  
  * constructed).</li></ol>
 92  
  * </p>
 93  
  * <p>
 94  
  * When a subnode configuration is created, it inherits the settings of its
 95  
  * parent configuration, e.g. some flags like the
 96  
  * {@code throwExceptionOnMissing} flag or the settings for handling list
 97  
  * delimiters) or the expression engine. If these settings are changed later in
 98  
  * either the subnode or the parent configuration, the changes are not visible
 99  
  * for each other. So you could create a subnode configuration, change its
 100  
  * expression engine without affecting the parent configuration.
 101  
  * </p>
 102  
  * <p>
 103  
  * From its purpose this class is quite similar to
 104  
  * {@link SubsetConfiguration}. The difference is that a subset
 105  
  * configuration of a hierarchical configuration may combine multiple
 106  
  * configuration nodes from different sub trees of the configuration, while all
 107  
  * nodes in a subnode configuration belong to the same sub tree. If an
 108  
  * application can live with this limitation, it is recommended to use this
 109  
  * class instead of {@code SubsetConfiguration} because creating a subset
 110  
  * configuration is more expensive than creating a subnode configuration.
 111  
  * </p>
 112  
  *
 113  
  * @since 1.3
 114  
  * @author <a
 115  
  * href="http://commons.apache.org/configuration/team-list.html">Commons
 116  
  * Configuration team</a>
 117  
  * @version $Id: SubnodeConfiguration.java 1210178 2011-12-04 18:58:51Z oheger $
 118  
  */
 119  
 public class SubnodeConfiguration extends HierarchicalReloadableConfiguration
 120  
 {
 121  
     /**
 122  
      * The serial version UID.
 123  
      */
 124  
     private static final long serialVersionUID = 3105734147019386480L;
 125  
 
 126  
     /** Stores the parent configuration. */
 127  
     private HierarchicalConfiguration parent;
 128  
 
 129  
     /** Stores the key that was used to construct this configuration.*/
 130  
     private String subnodeKey;
 131  
 
 132  
     /**
 133  
      * Creates a new instance of {@code SubnodeConfiguration} and
 134  
      * initializes it with the parent configuration and the new root node.
 135  
      *
 136  
      * @param parent the parent configuration
 137  
      * @param root the root node of this subnode configuration
 138  
      */
 139  
     public SubnodeConfiguration(HierarchicalConfiguration parent, ConfigurationNode root)
 140  
     {
 141  3707
         super(parent instanceof Reloadable ? ((Reloadable) parent).getReloadLock() : null);
 142  3707
         if (parent == null)
 143  
         {
 144  1
             throw new IllegalArgumentException(
 145  
                     "Parent configuration must not be null!");
 146  
         }
 147  3706
         if (root == null)
 148  
         {
 149  1
             throw new IllegalArgumentException("Root node must not be null!");
 150  
         }
 151  
 
 152  3705
         setRootNode(root);
 153  3705
         this.parent = parent;
 154  3705
         initFromParent(parent);
 155  3705
     }
 156  
 
 157  
     /**
 158  
      * Returns the parent configuration of this subnode configuration.
 159  
      *
 160  
      * @return the parent configuration
 161  
      */
 162  
     public HierarchicalConfiguration getParent()
 163  
     {
 164  2084
         return parent;
 165  
     }
 166  
 
 167  
     /**
 168  
      * Returns the key that was used to construct this configuration. If here a
 169  
      * non-<b>null</b> value is returned, the subnode configuration will
 170  
      * always check its parent for structural changes and reconstruct itself if
 171  
      * necessary.
 172  
      *
 173  
      * @return the key for selecting this configuration's root node
 174  
      * @since 1.5
 175  
      */
 176  
     public String getSubnodeKey()
 177  
     {
 178  3148
         return subnodeKey;
 179  
     }
 180  
 
 181  
     /**
 182  
      * Sets the key to the root node of this subnode configuration. If here a
 183  
      * key is set, the subnode configuration will behave like a live-view on its
 184  
      * parent for this key. See the class comment for more details.
 185  
      *
 186  
      * @param subnodeKey the key used to construct this configuration
 187  
      * @since 1.5
 188  
      */
 189  
     public void setSubnodeKey(String subnodeKey)
 190  
     {
 191  12
         this.subnodeKey = subnodeKey;
 192  12
     }
 193  
 
 194  
     /**
 195  
      * Returns the root node for this configuration. If a subnode key is set,
 196  
      * this implementation re-evaluates this key to find out if this subnode
 197  
      * configuration needs to be reconstructed. This ensures that the subnode
 198  
      * configuration is always synchronized with its parent configuration.
 199  
      *
 200  
      * @return the root node of this configuration
 201  
      * @since 1.5
 202  
      * @see #setSubnodeKey(String)
 203  
      */
 204  
     @Override
 205  
     public ConfigurationNode getRootNode()
 206  
     {
 207  3120
         if (getSubnodeKey() != null)
 208  
         {
 209  
             try
 210  
             {
 211  20
                 List<ConfigurationNode> nodes = getParent().fetchNodeList(getSubnodeKey());
 212  19
                 if (nodes.size() != 1)
 213  
                 {
 214  
                     // key is invalid, so detach this subnode configuration
 215  1
                     setSubnodeKey(null);
 216  
                 }
 217  
                 else
 218  
                 {
 219  18
                     ConfigurationNode currentRoot = nodes.get(0);
 220  18
                     if (currentRoot != super.getRootNode())
 221  
                     {
 222  
                         // the root node was changed due to a change of the
 223  
                         // parent
 224  8
                         fireEvent(EVENT_SUBNODE_CHANGED, null, null, true);
 225  8
                         setRootNode(currentRoot);
 226  8
                         fireEvent(EVENT_SUBNODE_CHANGED, null, null, false);
 227  
                     }
 228  18
                     return currentRoot;
 229  
                 }
 230  
             }
 231  1
             catch (Exception ex)
 232  
             {
 233  
                 // Evaluation of the key caused an exception. Probably the
 234  
                 // expression engine has changed on the parent. Detach this
 235  
                 // configuration, there is not much we can do about this.
 236  1
                 setSubnodeKey(null);
 237  1
             }
 238  
         }
 239  
 
 240  3102
         return super.getRootNode(); // use stored root node
 241  
     }
 242  
 
 243  
     /**
 244  
      * Returns a hierarchical configuration object for the given sub node.
 245  
      * This implementation will ensure that the returned
 246  
      * {@code SubnodeConfiguration} object will have the same parent than
 247  
      * this object.
 248  
      *
 249  
      * @param node the sub node, for which the configuration is to be created
 250  
      * @return a hierarchical configuration for this sub node
 251  
      */
 252  
     @Override
 253  
     protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node)
 254  
     {
 255  560
         SubnodeConfiguration result = new SubnodeConfiguration(getParent(), node);
 256  560
         getParent().registerSubnodeConfiguration(result);
 257  560
         return result;
 258  
     }
 259  
 
 260  
     /**
 261  
      * Returns a hierarchical configuration object for the given sub node that
 262  
      * is aware of structural changes of its parent. Works like the method with
 263  
      * the same name, but also sets the subnode key for the new subnode
 264  
      * configuration, so it can check whether the parent has been changed. This
 265  
      * only works if this subnode configuration has itself a valid subnode key.
 266  
      * So if a subnode configuration that should be aware of structural changes
 267  
      * is created from an already existing subnode configuration, this subnode
 268  
      * configuration must also be aware of such changes.
 269  
      *
 270  
      * @param node the sub node, for which the configuration is to be created
 271  
      * @param subnodeKey the construction key
 272  
      * @return a hierarchical configuration for this sub node
 273  
      * @since 1.5
 274  
      */
 275  
     @Override
 276  
     protected SubnodeConfiguration createSubnodeConfiguration(
 277  
             ConfigurationNode node, String subnodeKey)
 278  
     {
 279  2
         SubnodeConfiguration result = createSubnodeConfiguration(node);
 280  
 
 281  2
         if (getSubnodeKey() != null)
 282  
         {
 283  
             // construct the correct subnode key
 284  
             // determine path to root node
 285  1
             List<ConfigurationNode> lstPathToRoot = new ArrayList<ConfigurationNode>();
 286  1
             ConfigurationNode top = super.getRootNode();
 287  1
             ConfigurationNode nd = node;
 288  2
             while (nd != top)
 289  
             {
 290  1
                 lstPathToRoot.add(nd);
 291  1
                 nd = nd.getParentNode();
 292  
             }
 293  
 
 294  
             // construct the keys for the nodes on this path
 295  1
             Collections.reverse(lstPathToRoot);
 296  1
             String key = getSubnodeKey();
 297  1
             for (ConfigurationNode pathNode : lstPathToRoot)
 298  
             {
 299  1
                 key = getParent().getExpressionEngine().nodeKey(pathNode, key);
 300  1
             }
 301  1
             result.setSubnodeKey(key);
 302  
         }
 303  
 
 304  2
         return result;
 305  
     }
 306  
 
 307  
     /**
 308  
      * Creates a new node. This task is delegated to the parent.
 309  
      *
 310  
      * @param name the node's name
 311  
      * @return the new node
 312  
      */
 313  
     @Override
 314  
     protected Node createNode(String name)
 315  
     {
 316  10
         return getParent().createNode(name);
 317  
     }
 318  
 
 319  
     /**
 320  
      * Initializes this subnode configuration from the given parent
 321  
      * configuration. This method is called by the constructor. It will copy
 322  
      * many settings from the parent.
 323  
      *
 324  
      * @param parentConfig the parent configuration
 325  
      */
 326  
     protected void initFromParent(HierarchicalConfiguration parentConfig)
 327  
     {
 328  3705
         setExpressionEngine(parentConfig.getExpressionEngine());
 329  3705
         setListDelimiter(parentConfig.getListDelimiter());
 330  3705
         setDelimiterParsingDisabled(parentConfig.isDelimiterParsingDisabled());
 331  3705
         setThrowExceptionOnMissing(parentConfig.isThrowExceptionOnMissing());
 332  3705
     }
 333  
 
 334  
     /**
 335  
      * Creates a ConfigurationInterpolator with a chain to the parent's
 336  
      * interpolator.
 337  
      *
 338  
      * @return the new interpolator
 339  
      */
 340  
     @Override
 341  
     protected ConfigurationInterpolator createInterpolator()
 342  
     {
 343  604
         ConfigurationInterpolator interpolator = super.createInterpolator();
 344  604
         interpolator.setParentInterpolator(getParent().getInterpolator());
 345  604
         return interpolator;
 346  
     }
 347  
 }