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