    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.List;
    022
    023    import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
    024    import org.apache.commons.configuration.reloading.Reloadable;
    025    import org.apache.commons.configuration.tree.ConfigurationNode;
    026
    027    /**
    028     * <p>
    029     * A specialized hierarchical configuration class that wraps a single node of
    030     * its parent configuration.
    031     * </p>
    032     * <p>
    033     * Configurations of this type are initialized with a parent configuration and a
    034     * configuration node of this configuration. This node becomes the root node of
    035     * the subnode configuration. All property accessor methods are evaluated
    036     * relative to this root node. A good use case for a
    037     * {@code SubnodeConfiguration} is when multiple properties from a
    038     * specific sub tree of the whole configuration need to be accessed. Then a
    039     * {@code SubnodeConfiguration} can be created with the parent node of
    040     * the affected sub tree as root node. This allows for simpler property keys and
    041     * is also more efficient.
    042     * </p>
    043     * <p>
    044     * A subnode configuration and its parent configuration operate on the same
    045     * hierarchy of configuration nodes. So if modifications are performed at the
    046     * subnode configuration, these changes are immediately visible in the parent
    047     * configuration. Analogously will updates of the parent configuration affect
    048     * the subnode configuration if the sub tree spanned by the subnode
    049     * configuration's root node is involved.
    050     * </p>
    051     * <p>
    052     * There are however changes at the parent configuration, which cause the
    053     * subnode configuration to become detached. An example for such a change is a
    054     * reload operation of a file-based configuration, which replaces all nodes of
    055     * the parent configuration. The subnode configuration per default still
    056     * references the old nodes. Another example are list structures: a subnode
    057     * configuration can be created to point on the <em>i</em>th element of the
    058     * list. Now list elements can be added or removed, so that the list elements'
    059     * indices change. In such a scenario the subnode configuration would always
    060     * point to the same list element, regardless of its current index.
    061     * </p>
    062     * <p>
    063     * To solve these problems and make a subnode configuration aware of
    064     * such structural changes of its parent, it is possible to associate a
    065     * subnode configuration with a configuration key. This can be done by calling
    066     * the {@code setSubnodeKey()} method. If here a key is set, the subnode
    067     * configuration will evaluate it on each access, thus ensuring that it is
    068     * always in sync with its parent. In this mode the subnode configuration really
    069     * behaves like a live-view on its parent. The price for this is a decreased
    070     * performance because now an additional evaluation has to be performed on each
    071     * property access. So this mode should only be used if necessary; if for
    072     * instance a subnode configuration is only used for a temporary convenient
    073     * access to a complex configuration, there is no need to make it aware for
    074     * structural changes of its parent. If a subnode configuration is created
    075     * using the {@link HierarchicalConfiguration#configurationAt(String, boolean)
    076     * configurationAt()} method of {@code HierarchicalConfiguration}
    077     * (which should be the preferred way), with an additional boolean parameter it
    078     * can be specified whether the resulting subnode configuration should be
    079     * aware of structural changes or not. Then the configuration key will be
    080     * automatically set.
    081     * </p>
    082     * <p>
    083     * <em>Note:</em> At the moment support for creating a subnode configuration
    084     * that is aware of structural changes of its parent from another subnode
    085     * configuration (a "sub subnode configuration") is limited. This only works if
    086     * <ol><li>the subnode configuration that serves as the parent for the new
    087     * subnode configuration is itself associated with a configuration key and</li>
    088     * <li>the key passed in to create the new subnode configuration is not too
    089     * complex (if configuration keys are used that contain indices, a corresponding
    090     * key that is valid from the parent configuration's point of view cannot be
    091     * constructed).</li></ol>
    092     * </p>
    093     * <p>
    094     * When a subnode configuration is created, it inherits the settings of its
    095     * parent configuration, e.g. some flags like the
    096     * {@code throwExceptionOnMissing} flag or the settings for handling list
    097     * delimiters) or the expression engine. If these settings are changed later in
    098     * either the subnode or the parent configuration, the changes are not visible
    099     * 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            super(parent instanceof Reloadable ? ((Reloadable) parent).getReloadLock() : null);
    142            if (parent == null)
    143            {
    144                throw new IllegalArgumentException(
    145                        "Parent configuration must not be null!");
    146            }
    147            if (root == null)
    148            {
    149                throw new IllegalArgumentException("Root node must not be null!");
    150            }
    151
    152            setRootNode(root);
    153            this.parent = parent;
    154            initFromParent(parent);
    155        }
    156
    157        /**
    158         * Returns the parent configuration of this subnode configuration.
    159         *
    160         * @return the parent configuration
    161         */
    162        public HierarchicalConfiguration getParent()
    163        {
    164            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            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            this.subnodeKey = subnodeKey;
    192        }
    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            if (getSubnodeKey() != null)
    208            {
    209                try
    210                {
    211                    List<ConfigurationNode> nodes = getParent().fetchNodeList(getSubnodeKey());
    212                    if (nodes.size() != 1)
    213                    {
    214                        // key is invalid, so detach this subnode configuration
    215                        setSubnodeKey(null);
    216                    }
    217                    else
    218                    {
    219                        ConfigurationNode currentRoot = nodes.get(0);
    220                        if (currentRoot != super.getRootNode())
    221                        {
    222                            // the root node was changed due to a change of the
    223                            // parent
    224                            fireEvent(EVENT_SUBNODE_CHANGED, null, null, true);
    225                            setRootNode(currentRoot);
    226                            fireEvent(EVENT_SUBNODE_CHANGED, null, null, false);
    227                        }
    228                        return currentRoot;
    229                    }
    230                }
    231                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                    setSubnodeKey(null);
    237                }
    238            }
    239
    240            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            SubnodeConfiguration result = new SubnodeConfiguration(getParent(), node);
    256            getParent().registerSubnodeConfiguration(result);
    257            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            SubnodeConfiguration result = createSubnodeConfiguration(node);
    280
    281            if (getSubnodeKey() != null)
    282            {
    283                // construct the correct subnode key
    284                // determine path to root node
    285                List<ConfigurationNode> lstPathToRoot = new ArrayList<ConfigurationNode>();
    286                ConfigurationNode top = super.getRootNode();
    287                ConfigurationNode nd = node;
    288                while (nd != top)
    289                {
    290                    lstPathToRoot.add(nd);
    291                    nd = nd.getParentNode();
    292                }
    293
    294                // construct the keys for the nodes on this path
    295                Collections.reverse(lstPathToRoot);
    296                String key = getSubnodeKey();
    297                for (ConfigurationNode pathNode : lstPathToRoot)
    298                {
    299                    key = getParent().getExpressionEngine().nodeKey(pathNode, key);
    300                }
    301                result.setSubnodeKey(key);
    302            }
    303
    304            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            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            setExpressionEngine(parentConfig.getExpressionEngine());
    329            setListDelimiter(parentConfig.getListDelimiter());
    330            setDelimiterParsingDisabled(parentConfig.isDelimiterParsingDisabled());
    331            setThrowExceptionOnMissing(parentConfig.isThrowExceptionOnMissing());
    332        }
    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            ConfigurationInterpolator interpolator = super.createInterpolator();
    344            interpolator.setParentInterpolator(getParent().getInterpolator());
    345            return interpolator;
    346        }
    347    }