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 */
017package org.apache.commons.configuration;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022
023import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
024import org.apache.commons.configuration.reloading.Reloadable;
025import 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 */
119public 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}