    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.configuration;
    018
    019    import java.util.ArrayList;
    020    import java.util.Collections;
    021    import java.util.Iterator;
    022    import java.util.List;
    023
    024    import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
    025    import org.apache.commons.configuration.tree.ConfigurationNode;
    026    import org.apache.commons.configuration.reloading.Reloadable;
    027
    028    /**
    029     * <p>
    030     * A specialized hierarchical configuration class that wraps a single node of
    031     * its parent configuration.
    032     * </p>
    033     * <p>
    034     * Configurations of this type are initialized with a parent configuration and a
    035     * configuration node of this configuration. This node becomes the root node of
    036     * the subnode configuration. All property accessor methods are evaluated
    037     * relative to this root node. A good use case for a
    038     * <code>SubnodeConfiguration</code> is when multiple properties from a
    039     * specific sub tree of the whole configuration need to be accessed. Then a
    040     * <code>SubnodeConfiguration</code> can be created with the parent node of
    041     * the affected sub tree as root node. This allows for simpler property keys and
    042     * is also more efficient.
    043     * </p>
    044     * <p>
    045     * A subnode configuration and its parent configuration operate on the same
    046     * hierarchy of configuration nodes. So if modifications are performed at the
    047     * subnode configuration, these changes are immideately visible in the parent
    048     * configuration. Analogously will updates of the parent configuration affect
    049     * the subnode configuration if the sub tree spanned by the subnode
    050     * configuration's root node is involved.
    051     * </p>
    052     * <p>
    053     * There are however changes at the parent configuration, which cause the
    054     * subnode configuration to become detached. An example for such a change is a
    055     * reload operation of a file-based configuration, which replaces all nodes of
    056     * the parent configuration. The subnode configuration per default still
    057     * references the old nodes. Another example are list structures: a subnode
    058     * configuration can be created to point on the <em>i</em>th element of the
    059     * list. Now list elements can be added or removed, so that the list elements'
    060     * indices change. In such a scenario the subnode configuration would always
    061     * point to the same list element, regardless of its current index.
    062     * </p>
    063     * <p>
    064     * To solve these problems and make a subnode configuration aware of
    065     * such structural changes of its parent, it is possible to associate a
    066     * subnode configuration with a configuration key. This can be done by calling
    067     * the <code>setSubnodeKey()</code> method. If here a key is set, the subnode
    068     * configuration will evaluate it on each access, thus ensuring that it is
    069     * always in sync with its parent. In this mode the subnode configuration really
    070     * behaves like a live-view on its parent. The price for this is a decreased
    071     * performance because now an additional evaluation has to be performed on each
    072     * property access. So this mode should only be used if necessary; if for
    073     * instance a subnode configuration is only used for a temporary convenient
    074     * access to a complex configuration, there is no need to make it aware for
    075     * structural changes of its parent. If a subnode configuration is created
    076     * using the <code>{@link HierarchicalConfiguration#configurationAt(String, boolean)
    077     * configurationAt()}</code> method of <code>HierarchicalConfiguration</code>
    078     * (which should be the preferred way), with an additional boolean parameter it
    079     * can be specified whether the resulting subnode configuration should be
    080     * aware of structural changes or not. Then the configuration key will be
    081     * automatically set.
    082     * </p>
    083     * <p>
    084     * <em>Note:</em> At the moment support for creating a subnode configuration
    085     * that is aware of structural changes of its parent from another subnode
    086     * configuration (a "sub subnode configuration") is limited. This only works if
    087     * <ol><li>the subnode configuration that serves as the parent for the new
    088     * subnode configuration is itself associated with a configuration key and</li>
    089     * <li>the key passed in to create the new subnode configuration is not too
    090     * complex (if configuration keys are used that contain indices, a corresponding
    091     * key that is valid from the parent configuration's point of view cannot be
    092     * constructed).</li></ol>
    093     * </p>
    094     * <p>
    095     * When a subnode configuration is created, it inherits the settings of its
    096     * parent configuration, e.g. some flags like the
    097     * <code>throwExceptionOnMissing</code> flag or the settings for handling list
    098     * delimiters) or the expression engine. If these settings are changed later in
    099     * either the subnode or the parent configuration, the changes are not visible
    100     * for each other. So you could create a subnode configuration, change its
    101     * expression engine without affecting the parent configuration.
    102     * </p>
    103     * <p>
    104     * From its purpose this class is quite similar to
    105     * <code>{@link SubsetConfiguration}</code>. The difference is that a subset
    106     * configuration of a hierarchical configuration may combine multiple
    107     * configuration nodes from different sub trees of the configuration, while all
    108     * nodes in a subnode configuration belong to the same sub tree. If an
    109     * application can live with this limitation, it is recommended to use this
    110     * class instead of <code>SubsetConfiguration</code> because creating a subset
    111     * configuration is more expensive than creating a subnode configuration.
    112     * </p>
    113     *
    114     * @since 1.3
    115     * @author Oliver Heger
    116     * @version $Id: SubnodeConfiguration.java 823891 2009-10-10 17:17:44Z rgoers $
    117     */
    118    public class SubnodeConfiguration extends HierarchicalReloadableConfiguration
    119    {
    120        /**
    121         * The serial version UID.
    122         */
    123        private static final long serialVersionUID = 3105734147019386480L;
    124
    125        /** Stores the parent configuration. */
    126        private HierarchicalConfiguration parent;
    127
    128        /** Stores the key that was used to construct this configuration.*/
    129        private String subnodeKey;
    130
    131        /**
    132         * Creates a new instance of <code>SubnodeConfiguration</code> and
    133         * initializes it with the parent configuration and the new root node.
    134         *
    135         * @param parent the parent configuration
    136         * @param root the root node of this subnode configuration
    137         */
    138        public SubnodeConfiguration(HierarchicalConfiguration parent, ConfigurationNode root)
    139        {
    140            super(parent instanceof Reloadable ? ((Reloadable) parent).getReloadLock() : null);
    141            if (parent == null)
    142            {
    143                throw new IllegalArgumentException(
    144                        "Parent configuration must not be null!");
    145            }
    146            if (root == null)
    147            {
    148                throw new IllegalArgumentException("Root node must not be null!");
    149            }
    150
    151            setRootNode(root);
    152            this.parent = parent;
    153            initFromParent(parent);
    154        }
    155
    156        /**
    157         * Returns the parent configuration of this subnode configuration.
    158         *
    159         * @return the parent configuration
    160         */
    161        public HierarchicalConfiguration getParent()
    162        {
    163            return parent;
    164        }
    165
    166        /**
    167         * Returns the key that was used to construct this configuration. If here a
    168         * non-<b>null</b> value is returned, the subnode configuration will
    169         * always check its parent for structural changes and reconstruct itself if
    170         * necessary.
    171         *
    172         * @return the key for selecting this configuration's root node
    173         * @since 1.5
    174         */
    175        public String getSubnodeKey()
    176        {
    177            return subnodeKey;
    178        }
    179
    180        /**
    181         * Sets the key to the root node of this subnode configuration. If here a
    182         * key is set, the subnode configuration will behave like a live-view on its
    183         * parent for this key. See the class comment for more details.
    184         *
    185         * @param subnodeKey the key used to construct this configuration
    186         * @since 1.5
    187         */
    188        public void setSubnodeKey(String subnodeKey)
    189        {
    190            this.subnodeKey = subnodeKey;
    191        }
    192
    193        /**
    194         * Returns the root node for this configuration. If a subnode key is set,
    195         * this implementation re-evaluates this key to find out if this subnode
    196         * configuration needs to be reconstructed. This ensures that the subnode
    197         * configuration is always synchronized with its parent configuration.
    198         *
    199         * @return the root node of this configuration
    200         * @since 1.5
    201         * @see #setSubnodeKey(String)
    202         */
    203        public ConfigurationNode getRootNode()
    204        {
    205            if (getSubnodeKey() != null)
    206            {
    207                try
    208                {
    209                    List nodes = getParent().fetchNodeList(getSubnodeKey());
    210                    if (nodes.size() != 1)
    211                    {
    212                        // key is invalid, so detach this subnode configuration
    213                        setSubnodeKey(null);
    214                    }
    215                    else
    216                    {
    217                        ConfigurationNode currentRoot = (ConfigurationNode) nodes
    218                                .get(0);
    219                        if (currentRoot != super.getRootNode())
    220                        {
    221                            // the root node was changed due to a change of the
    222                            // parent
    223                            fireEvent(EVENT_SUBNODE_CHANGED, null, null, true);
    224                            setRootNode(currentRoot);
    225                            fireEvent(EVENT_SUBNODE_CHANGED, null, null, false);
    226                        }
    227                        return currentRoot;
    228                    }
    229                }
    230                catch (Exception ex)
    231                {
    232                    // Evaluation of the key caused an exception. Probably the
    233                    // expression engine has changed on the parent. Detach this
    234                    // configuration, there is not much we can do about this.
    235                    setSubnodeKey(null);
    236                }
    237            }
    238
    239            return super.getRootNode(); // use stored root node
    240        }
    241
    242        /**
    243         * Returns a hierarchical configuration object for the given sub node.
    244         * This implementation will ensure that the returned
    245         * <code>SubnodeConfiguration</code> object will have the same parent than
    246         * this object.
    247         *
    248         * @param node the sub node, for which the configuration is to be created
    249         * @return a hierarchical configuration for this sub node
    250         */
    251        protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node)
    252        {
    253            SubnodeConfiguration result = new SubnodeConfiguration(getParent(), node);
    254            getParent().registerSubnodeConfiguration(result);
    255            return result;
    256        }
    257
    258        /**
    259         * Returns a hierarchical configuration object for the given sub node that
    260         * is aware of structural changes of its parent. Works like the method with
    261         * the same name, but also sets the subnode key for the new subnode
    262         * configuration, so it can check whether the parent has been changed. This
    263         * only works if this subnode configuration has itself a valid subnode key.
    264         * So if a subnode configuration that should be aware of structural changes
    265         * is created from an already existing subnode configuration, this subnode
    266         * configuration must also be aware of such changes.
    267         *
    268         * @param node the sub node, for which the configuration is to be created
    269         * @param subnodeKey the construction key
    270         * @return a hierarchical configuration for this sub node
    271         * @since 1.5
    272         */
    273        protected SubnodeConfiguration createSubnodeConfiguration(
    274                ConfigurationNode node, String subnodeKey)
    275        {
    276            SubnodeConfiguration result = createSubnodeConfiguration(node);
    277
    278            if (getSubnodeKey() != null)
    279            {
    280                // construct the correct subnode key
    281                // determine path to root node
    282                List lstPathToRoot = new ArrayList();
    283                ConfigurationNode top = super.getRootNode();
    284                ConfigurationNode nd = node;
    285                while (nd != top)
    286                {
    287                    lstPathToRoot.add(nd);
    288                    nd = nd.getParentNode();
    289                }
    290
    291                // construct the keys for the nodes on this path
    292                Collections.reverse(lstPathToRoot);
    293                String key = getSubnodeKey();
    294                for (Iterator it = lstPathToRoot.iterator(); it.hasNext();)
    295                {
    296                    key = getParent().getExpressionEngine().nodeKey(
    297                            (ConfigurationNode) it.next(), key);
    298                }
    299                result.setSubnodeKey(key);
    300            }
    301
    302            return result;
    303        }
    304
    305        /**
    306         * Creates a new node. This task is delegated to the parent.
    307         *
    308         * @param name the node's name
    309         * @return the new node
    310         */
    311        protected Node createNode(String name)
    312        {
    313            return getParent().createNode(name);
    314        }
    315
    316        /**
    317         * Initializes this subnode configuration from the given parent
    318         * configuration. This method is called by the constructor. It will copy
    319         * many settings from the parent.
    320         *
    321         * @param parentConfig the parent configuration
    322         */
    323        protected void initFromParent(HierarchicalConfiguration parentConfig)
    324        {
    325            setExpressionEngine(parentConfig.getExpressionEngine());
    326            setListDelimiter(parentConfig.getListDelimiter());
    327            setDelimiterParsingDisabled(parentConfig.isDelimiterParsingDisabled());
    328            setThrowExceptionOnMissing(parentConfig.isThrowExceptionOnMissing());
    329        }
    330
    331        /**
    332         * Creates a ConfigurationInterpolator with a chain to the parent's
    333         * interpolator.
    334         *
    335         * @return the new interpolator
    336         */
    337        protected ConfigurationInterpolator createInterpolator()
    338        {
    339            ConfigurationInterpolator interpolator = super.createInterpolator();
    340            interpolator.setParentInterpolator(getParent().getInterpolator());
    341            return interpolator;
    342        }
    343    }