1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.configuration;
18
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.Iterator;
22 import java.util.List;
23
24 import org.apache.commons.configuration.tree.ConfigurationNode;
25
26 /***
27 * <p>
28 * A specialized hierarchical configuration class that wraps a single node of
29 * its parent configuration.
30 * </p>
31 * <p>
32 * Configurations of this type are initialized with a parent configuration and a
33 * configuration node of this configuration. This node becomes the root node of
34 * the subnode configuration. All property accessor methods are evaluated
35 * relative to this root node. A good use case for a
36 * <code>SubnodeConfiguration</code> is when multiple properties from a
37 * specific sub tree of the whole configuration need to be accessed. Then a
38 * <code>SubnodeConfiguration</code> can be created with the parent node of
39 * the affected sub tree as root node. This allows for simpler property keys and
40 * is also more efficient.
41 * </p>
42 * <p>
43 * A subnode configuration and its parent configuration operate on the same
44 * hierarchy of configuration nodes. So if modifications are performed at the
45 * subnode configuration, these changes are immideately visible in the parent
46 * configuration. Analogously will updates of the parent configuration affect
47 * the subnode configuration if the sub tree spanned by the subnode
48 * configuration's root node is involved.
49 * </p>
50 * <p>
51 * There are however changes at the parent configuration, which cause the
52 * subnode configuration to become detached. An example for such a change is a
53 * reload operation of a file-based configuration, which replaces all nodes of
54 * the parent configuration. The subnode configuration per default still
55 * references the old nodes. Another example are list structures: a subnode
56 * configuration can be created to point on the <em>i</em>th element of the
57 * list. Now list elements can be added or removed, so that the list elements'
58 * indices change. In such a scenario the subnode configuration would always
59 * point to the same list element, regardless of its current index.
60 * </p>
61 * <p>
62 * To solve these problems and make a subnode configuration aware of
63 * such structural changes of its parent, it is possible to associate a
64 * subnode configuration with a configuration key. This can be done by calling
65 * the <code>setSubnodeKey()</code> method. If here a key is set, the subnode
66 * configuration will evaluate it on each access, thus ensuring that it is
67 * always in sync with its parent. In this mode the subnode configuration really
68 * behaves like a live-view on its parent. The price for this is a decreased
69 * performance because now an additional evaluation has to be performed on each
70 * property access. So this mode should only be used if necessary; if for
71 * instance a subnode configuration is only used for a temporary convenient
72 * access to a complex configuration, there is no need to make it aware for
73 * structural changes of its parent. If a subnode configuration is created
74 * using the <code>{@link HierarchicalConfiguration#configurationAt(String, boolean)
75 * configurationAt()}</code> method of <code>HierarchicalConfiguration</code>
76 * (which should be the preferred way), with an additional boolean parameter it
77 * can be specified whether the resulting subnode configuration should be
78 * aware of structural changes or not. Then the configuration key will be
79 * automatically set.
80 * </p>
81 * <p>
82 * <em>Note:</em> At the moment support for creating a subnode configuration
83 * that is aware of structural changes of its parent from another subnode
84 * configuration (a "sub subnode configuration") is limited. This only works if
85 * <ol><li>the subnode configuration that serves as the parent for the new
86 * subnode configuration is itself associated with a configuration key and</li>
87 * <li>the key passed in to create the new subnode configuration is not too
88 * complex (if configuration keys are used that contain indices, a corresponding
89 * key that is valid from the parent configuration's point of view cannot be
90 * constructed).</li></ol>
91 * </p>
92 * <p>
93 * When a subnode configuration is created, it inherits the settings of its
94 * parent configuration, e.g. some flags like the
95 * <code>throwExceptionOnMissing</code> flag or the settings for handling list
96 * delimiters) or the expression engine. If these settings are changed later in
97 * either the subnode or the parent configuration, the changes are not visible
98 * for each other. So you could create a subnode configuration, change its
99 * 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
210 setSubnodeKey(null);
211 }
212 else
213 {
214 ConfigurationNode currentRoot = (ConfigurationNode) nodes
215 .get(0);
216 if (currentRoot != super.getRootNode())
217 {
218
219
220 setRootNode(currentRoot);
221 }
222 return currentRoot;
223 }
224 }
225 catch (Exception ex)
226 {
227
228
229
230 setSubnodeKey(null);
231 }
232 }
233
234 return super.getRootNode();
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
276
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
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 }