##### Coverage Report - org.apache.commons.configuration.CombinedConfiguration

Classes in this File | Line Coverage | Branch Coverage | Complexity
CombinedConfiguration |
99% | 114/115
100% | 23/23
| 2.057142857142857;2,057
CombinedConfiguration$ConfigData |
100% | 30/30
100% | 4/4
| 2.057142857142857;2,057

1 |  |
     /*
2 |  |
      * Licensed to the Apache Software Foundation (ASF) under one or more
3 |  |
      * contributor license agreements.  See the NOTICE file distributed with
4 |  |
      * this work for additional information regarding copyright ownership.
5 |  |
      * The ASF licenses this file to You under the Apache License, Version 2.0
6 |  |
      * (the "License"); you may not use this file except in compliance with
7 |  |
      * the License.  You may obtain a copy of the License at
8 |  |
      *
9 |  |
      *     http://www.apache.org/licenses/LICENSE-2.0
10 |  |
      *
11 |  |
      * Unless required by applicable law or agreed to in writing, software
12 |  |
      * distributed under the License is distributed on an "AS IS" BASIS,
13 |  |
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 |  |
      * See the License for the specific language governing permissions and
15 |  |
      * limitations under the License.
16 |  |
      */
17 |  |
     package org.apache.commons.configuration;
18 |  |
19 |  |
     import java.util.ArrayList;
20 |  |
     import java.util.Collection;
21 |  |
     import java.util.HashMap;
22 |  |
     import java.util.Iterator;
23 |  |
     import java.util.List;
24 |  |
     import java.util.Map;
25 |  |
     import java.util.Set;
26 |  |
27 |  |
     import org.apache.commons.configuration.event.ConfigurationEvent;
28 |  |
     import org.apache.commons.configuration.event.ConfigurationListener;
29 |  |
     import org.apache.commons.configuration.tree.ConfigurationNode;
30 |  |
     import org.apache.commons.configuration.tree.DefaultConfigurationKey;
31 |  |
     import org.apache.commons.configuration.tree.DefaultConfigurationNode;
32 |  |
     import org.apache.commons.configuration.tree.DefaultExpressionEngine;
33 |  |
     import org.apache.commons.configuration.tree.ExpressionEngine;
34 |  |
     import org.apache.commons.configuration.tree.NodeCombiner;
35 |  |
     import org.apache.commons.configuration.tree.UnionCombiner;
36 |  |
     import org.apache.commons.configuration.tree.ViewNode;
37 |  |
38 |  |
     /**
39 |  |
      * <p>
40 |  |
      * A hierarchical composite configuration class.
41 |  |
      * </p>
42 |  |
      * <p>
43 |  |
      * This class maintains a list of configuration objects, which can be added
44 |  |
      * using the divers <code>addConfiguration()</code> methods. After that the
45 |  |
      * configurations can be accessed either by name (if one was provided when the
46 |  |
      * configuration was added) or by index. For the whole set of managed
47 |  |
      * configurations a logical node structure is constructed. For this purpose a
48 |  |
      * <code>{@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}</code>
49 |  |
      * object can be set. This makes it possible to specify different algorithms for
50 |  |
      * the combination process.
51 |  |
      * </p>
52 |  |
      * <p>
53 |  |
      * The big advantage of this class is that it creates a truly hierarchical
54 |  |
      * structure of all the properties stored in the contained configurations - even
55 |  |
      * if some of them are no hierarchical configurations per se. So all enhanced
56 |  |
      * features provided by a hierarchical configuration (e.g. choosing an
57 |  |
      * expression engine) are applicable.
58 |  |
      * </p>
59 |  |
      * <p>
60 |  |
      * The class works by registering itself as an event listener at all added
61 |  |
      * configurations. So it gets notified whenever one of these configurations is
62 |  |
      * changed and can invalidate its internal node structure. The next time a
63 |  |
      * property is accessed the node structure will be re-constructed using the
64 |  |
      * current state of the managed configurations. Note that, depending on the used
65 |  |
      * <code>NodeCombiner</code>, this may be a complex operation.
66 |  |
      * </p>
67 |  |
      * <p>
68 |  |
      * Because of the way a <code>CombinedConfiguration</code> is working it has
69 |  |
      * more or less view character: it provides a logic view on the configurations
70 |  |
      * it contains. In this constellation not all methods defined for hierarchical
71 |  |
      * configurations - especially methods that update the stored properties - can
72 |  |
      * be implemented in a consistent manner. Using such methods (like
73 |  |
      * <code>addProperty()</code>, or <code>clearProperty()</code> on a
74 |  |
      * <code>CombinedConfiguration</code> is not strictly forbidden, however,
75 |  |
      * depending on the current <code>{@link NodeCombiner}</code> and the involved
76 |  |
      * properties, the results may be different than expected. Some examples may
77 |  |
      * illustrate this:
78 |  |
      * </p>
79 |  |
      * <p>
80 |  |
      * <ul>
81 |  |
      * <li>Imagine a <code>CombinedConfiguration</code> <em>cc</em> containing
82 |  |
      * two child configurations with the following content:
83 |  |
      * <dl>
84 |  |
      * <dt>user.properties</dt>
85 |  |
      * <dd>
86 |  |
      *
87 |  |
      * <pre>
88 |  |
      * gui.background = blue
89 |  |
      * gui.position = (10, 10, 400, 200)
90 |  |
      * </pre>
91 |  |
      *
92 |  |
      * </dd>
93 |  |
      * <dt>default.properties</dt>
94 |  |
      * <dd>
95 |  |
      *
96 |  |
      * <pre>
97 |  |
      * gui.background = black
98 |  |
      * gui.foreground = white
99 |  |
      * home.dir = /data
100 |  |
      * </pre>
101 |  |
      *
102 |  |
      * </dd>
103 |  |
      * </dl>
104 |  |
      * As a <code>NodeCombiner</code> a
105 |  |
      * <code>{@link org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}</code>
106 |  |
      * is used. This combiner will ensure that defined user settings take precedence
107 |  |
      * over the default values. If the resulting <code>CombinedConfiguration</code>
108 |  |
      * is queried for the background color, <code>blue</code> will be returned
109 |  |
      * because this value is defined in <code>user.properties</code>. Now
110 |  |
      * consider what happens if the key <code>gui.background</code> is removed
111 |  |
      * from the <code>CombinedConfiguration</code>:
112 |  |
      *
113 |  |
      * <pre>cc.clearProperty("gui.background");</pre>
114 |  |
      *
115 |  |
      * Will a <code>cc.containsKey("gui.background")</code> now return <b>false</b>?
116 |  |
      * No, it won't! The <code>clearProperty()</code> operation is executed on the
117 |  |
      * node set of the combined configuration, which was constructed from the nodes
118 |  |
      * of the two child configurations. It causes the value of the
119 |  |
      * <em>background</em> node to be cleared, which is also part of the first
120 |  |
      * child configuration. This modification of one of its child configurations
121 |  |
      * causes the <code>CombinedConfiguration</code> to be re-constructed. This
122 |  |
      * time the <code>OverrideCombiner</code> cannot find a
123 |  |
      * <code>gui.background</code> property in the first child configuration, but
124 |  |
      * it finds one in the second, and adds it to the resulting combined
125 |  |
      * configuration. So the property is still present (with a different value now).</li>
126 |  |
      * <li><code>addProperty()</code> can also be problematic: Most node
127 |  |
      * combiners use special view nodes for linking parts of the original
128 |  |
      * configurations' data together. If new properties are added to such a special
129 |  |
      * node, they do not belong to any of the managed configurations and thus hang
130 |  |
      * in the air. Using the same configurations as in the last example, the
131 |  |
      * statement
132 |  |
      *
133 |  |
      * <pre>
134 |  |
      * addProperty("database.user", "scott");
135 |  |
      * </pre>
136 |  |
      *
137 |  |
      * would cause such a hanging property. If now one of the child configurations
138 |  |
      * is changed and the <code>CombinedConfiguration</code> is re-constructed,
139 |  |
      * this property will disappear! (Add operations are not problematic if they
140 |  |
      * result in a child configuration being updated. For instance an
141 |  |
      * <code>addProperty("home.url", "localhost");</code> will alter the second
142 |  |
      * child configuration - because the prefix <em>home</em> is here already
143 |  |
      * present; when the <code>CombinedConfiguration</code> is re-constructed,
144 |  |
      * this change is taken into account.)</li>
145 |  |
      * </ul>
146 |  |
      * Because of such problems it is recommended to perform updates only on the
147 |  |
      * managed child configurations.
148 |  |
      * </p>
149 |  |
      * <p>
150 |  |
      * Whenever the node structure of a <code>CombinedConfiguration</code> becomes
151 |  |
      * invalid (either because one of the contained configurations was modified or
152 |  |
      * because the <code>invalidate()</code> method was directly called) an event
153 |  |
      * is generated. So this can be detected by interested event listeners. This
154 |  |
      * also makes it possible to add a combined configuration into another one.
155 |  |
      * </p>
156 |  |
      * <p>
157 |  |
      * Implementation note: Adding and removing configurations to and from a
158 |  |
      * combined configuration is not thread-safe. If a combined configuration is
159 |  |
      * manipulated by multiple threads, the developer has to take care about
160 |  |
      * properly synchronization.
161 |  |
      * </p>
162 |  |
      *
163 |  |
      * @author <a
164 |  |
      * href="http://commons.apache.org/configuration/team-list.html">Commons
165 |  |
      * Configuration team</a>
166 |  |
      * @since 1.3
167 |  |
      * @version $Id: CombinedConfiguration.java 712401 2008-11-08 15:29:56Z oheger $
168 |  |
      */
169 | 12 |
     public class CombinedConfiguration extends HierarchicalConfiguration implements
170 |  |
             ConfigurationListener, Cloneable
171 |  |
     {
172 |  |
         /**
173 |  |
          * Constant for the invalidate event that is fired when the internal node
174 |  |
          * structure becomes invalid.
175 |  |
          */
176 |  |
         public static final int EVENT_COMBINED_INVALIDATE = 40;
177 |  |
178 |  |
         /**
179 |  |
          * The serial version ID.
180 |  |
          */
181 |  |
         private static final long serialVersionUID = 8338574525528692307L;
182 |  |
183 |  |
         /** Constant for the expression engine for parsing the at path. */
184 | 4 |
         private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine();
185 |  |
186 |  |
         /** Constant for the default node combiner. */
187 | 4 |
         private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
188 |  |
189 |  |
         /** Constant for the name of the property used for the reload check.*/
190 |  |
         private static final String PROP_RELOAD_CHECK = "CombinedConfigurationReloadCheck";
191 |  |
192 |  |
         /** Stores the combiner. */
193 |  |
         private NodeCombiner nodeCombiner;
194 |  |
195 |  |
         /** Stores the combined root node. */
196 |  |
         private volatile ConfigurationNode combinedRoot;
197 |  |
198 |  |
         /** Stores a list with the contained configurations. */
199 |  |
         private List configurations;
200 |  |
201 |  |
         /** Stores a map with the named configurations. */
202 |  |
         private Map namedConfigurations;
203 |  |
204 |  |
         /**
205 |  |
          * An expression engine used for converting child configurations to
206 |  |
          * hierarchical ones.
207 |  |
          */
208 |  |
         private ExpressionEngine conversionExpressionEngine;
209 |  |
210 |  |
         /** A flag whether an enhanced reload check is to be performed.*/
211 |  |
         private boolean forceReloadCheck;
212 |  |
213 |  |
         /**
214 |  |
          * Creates a new instance of <code>CombinedConfiguration</code> and
215 |  |
          * initializes the combiner to be used.
216 |  |
          *
217 |  |
          * @param comb the node combiner (can be <b>null</b>, then a union combiner
218 |  |
          * is used as default)
219 |  |
          */
220 |  |
         public CombinedConfiguration(NodeCombiner comb)
221 | 78 |
         {
222 | 78 |
             setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
223 | 78 |
             clear();
224 | 78 |
         }
225 |  |
226 |  |
         /**
227 |  |
          * Creates a new instance of <code>CombinedConfiguration</code> that uses
228 |  |
          * a union combiner.
229 |  |
          *
230 |  |
          * @see org.apache.commons.configuration.tree.UnionCombiner
231 |  |
          */
232 |  |
         public CombinedConfiguration()
233 |  |
         {
234 | 64 |
             this(null);
235 | 64 |
         }
236 |  |
237 |  |
         /**
238 |  |
          * Returns the node combiner that is used for creating the combined node
239 |  |
          * structure.
240 |  |
          *
241 |  |
          * @return the node combiner
242 |  |
          */
243 |  |
         public NodeCombiner getNodeCombiner()
244 |  |
         {
245 | 86 |
             return nodeCombiner;
246 |  |
         }
247 |  |
248 |  |
         /**
249 |  |
          * Sets the node combiner. This object will be used when the combined node
250 |  |
          * structure is to be constructed. It must not be <b>null</b>, otherwise an
251 |  |
          * <code>IllegalArgumentException</code> exception is thrown. Changing the
252 |  |
          * node combiner causes an invalidation of this combined configuration, so
253 |  |
          * that the new combiner immediately takes effect.
254 |  |
          *
255 |  |
          * @param nodeCombiner the node combiner
256 |  |
          */
257 |  |
         public void setNodeCombiner(NodeCombiner nodeCombiner)
258 |  |
         {
259 | 106 |
             if (nodeCombiner == null)
260 |  |
             {
261 | 1 |
                 throw new IllegalArgumentException(
262 |  |
                         "Node combiner must not be null!");
263 |  |
             }
264 | 105 |
             this.nodeCombiner = nodeCombiner;
265 | 105 |
             invalidate();
266 | 105 |
         }
267 |  |
268 |  |
         /**
269 |  |
          * Returns a flag whether an enhanced reload check must be performed.
270 |  |
          *
271 |  |
          * @return the force reload check flag
272 |  |
          * @since 1.4
273 |  |
          */
274 |  |
         public boolean isForceReloadCheck()
275 |  |
         {
276 | 1105 |
             return forceReloadCheck;
277 |  |
         }
278 |  |
279 |  |
         /**
280 |  |
          * Sets the force reload check flag. If this flag is set, each property
281 |  |
          * access on this configuration will cause a reload check on the contained
282 |  |
          * configurations. This is a workaround for a problem with some reload
283 |  |
          * implementations that only check if a reload is required when they are
284 |  |
          * triggered. Per default this mode is disabled. If the force reload check
285 |  |
          * flag is set to <b>true</b>, accessing properties will be less
286 |  |
          * performant, but reloads on contained configurations will be detected.
287 |  |
          *
288 |  |
          * @param forceReloadCheck the value of the flag
289 |  |
          * @since 1.4
290 |  |
          */
291 |  |
         public void setForceReloadCheck(boolean forceReloadCheck)
292 |  |
         {
293 | 7 |
             this.forceReloadCheck = forceReloadCheck;
294 | 7 |
         }
295 |  |
296 |  |
         /**
297 |  |
          * Returns the <code>ExpressionEngine</code> for converting flat child
298 |  |
          * configurations to hierarchical ones.
299 |  |
          *
300 |  |
          * @return the conversion expression engine
301 |  |
          * @since 1.6
302 |  |
          */
303 |  |
         public ExpressionEngine getConversionExpressionEngine()
304 |  |
         {
305 | 138 |
             return conversionExpressionEngine;
306 |  |
         }
307 |  |
308 |  |
         /**
309 |  |
          * Sets the <code>ExpressionEngine</code> for converting flat child
310 |  |
          * configurations to hierarchical ones. When constructing the root node for
311 |  |
          * this combined configuration the properties of all child configurations
312 |  |
          * must be combined to a single hierarchical node structure. In this
313 |  |
          * process, non hierarchical configurations are converted to hierarchical
314 |  |
          * ones first. This can be problematic if a child configuration contains
315 |  |
          * keys that are no compatible with the default expression engine used by
316 |  |
          * hierarchical configurations. Therefore it is possible to specify a
317 |  |
          * specific expression engine to be used for this purpose.
318 |  |
          *
319 |  |
          * @param conversionExpressionEngine the conversion expression engine
320 |  |
          * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine)
321 |  |
          * @since 1.6
322 |  |
          */
323 |  |
         public void setConversionExpressionEngine(
324 |  |
                 ExpressionEngine conversionExpressionEngine)
325 |  |
         {
326 | 1 |
             this.conversionExpressionEngine = conversionExpressionEngine;
327 | 1 |
         }
328 |  |
329 |  |
         /**
330 |  |
          * Adds a new configuration to this combined configuration. It is possible
331 |  |
          * (but not mandatory) to give the new configuration a name. This name must
332 |  |
          * be unique, otherwise a <code>ConfigurationRuntimeException</code> will
333 |  |
          * be thrown. With the optional <code>at</code> argument you can specify
334 |  |
          * where in the resulting node structure the content of the added
335 |  |
          * configuration should appear. This is a string that uses dots as property
336 |  |
          * delimiters (independent on the current expression engine). For instance
337 |  |
          * if you pass in the string <code>&quot;database.tables&quot;</code>,
338 |  |
          * all properties of the added configuration will occur in this branch.
339 |  |
          *
340 |  |
          * @param config the configuration to add (must not be <b>null</b>)
341 |  |
          * @param name the name of this configuration (can be <b>null</b>)
342 |  |
          * @param at the position of this configuration in the combined tree (can be
343 |  |
          * <b>null</b>)
344 |  |
          */
345 |  |
         public void addConfiguration(AbstractConfiguration config, String name,
346 |  |
                 String at)
347 |  |
         {
348 | 135 |
             if (config == null)
349 |  |
             {
350 | 1 |
                 throw new IllegalArgumentException(
351 |  |
                         "Added configuration must not be null!");
352 |  |
             }
353 | 134 |
             if (name != null && namedConfigurations.containsKey(name))
354 |  |
             {
355 | 1 |
                 throw new ConfigurationRuntimeException(
356 |  |
                         "A configuration with the name '"
357 |  |
                                 + name
358 |  |
                                 + "' already exists in this combined configuration!");
359 |  |
             }
360 |  |
361 | 133 |
             ConfigData cd = new ConfigData(config, name, at);
362 | 133 |
             configurations.add(cd);
363 | 133 |
             if (name != null)
364 |  |
             {
365 | 76 |
                 namedConfigurations.put(name, config);
366 |  |
             }
367 |  |
368 | 133 |
             config.addConfigurationListener(this);
369 | 133 |
             invalidate();
370 | 133 |
         }
371 |  |
372 |  |
         /**
373 |  |
          * Adds a new configuration to this combined configuration with an optional
374 |  |
          * name. The new configuration's properties will be added under the root of
375 |  |
          * the combined node structure.
376 |  |
          *
377 |  |
          * @param config the configuration to add (must not be <b>null</b>)
378 |  |
          * @param name the name of this configuration (can be <b>null</b>)
379 |  |
          */
380 |  |
         public void addConfiguration(AbstractConfiguration config, String name)
381 |  |
         {
382 | 31 |
             addConfiguration(config, name, null);
383 | 31 |
         }
384 |  |
385 |  |
         /**
386 |  |
          * Adds a new configuration to this combined configuration. The new
387 |  |
          * configuration is not given a name. Its properties will be added under the
388 |  |
          * root of the combined node structure.
389 |  |
          *
390 |  |
          * @param config the configuration to add (must not be <b>null</b>)
391 |  |
          */
392 |  |
         public void addConfiguration(AbstractConfiguration config)
393 |  |
         {
394 | 13 |
             addConfiguration(config, null, null);
395 | 12 |
         }
396 |  |
397 |  |
         /**
398 |  |
          * Returns the number of configurations that are contained in this combined
399 |  |
          * configuration.
400 |  |
          *
401 |  |
          * @return the number of contained configurations
402 |  |
          */
403 |  |
         public int getNumberOfConfigurations()
404 |  |
         {
405 | 114 |
             return configurations.size();
406 |  |
         }
407 |  |
408 |  |
         /**
409 |  |
          * Returns the configuration at the specified index. The contained
410 |  |
          * configurations are numbered in the order they were added to this combined
411 |  |
          * configuration. The index of the first configuration is 0.
412 |  |
          *
413 |  |
          * @param index the index
414 |  |
          * @return the configuration at this index
415 |  |
          */
416 |  |
         public Configuration getConfiguration(int index)
417 |  |
         {
418 | 37 |
             ConfigData cd = (ConfigData) configurations.get(index);
419 | 37 |
             return cd.getConfiguration();
420 |  |
         }
421 |  |
422 |  |
         /**
423 |  |
          * Returns the configuration with the given name. This can be <b>null</b>
424 |  |
          * if no such configuration exists.
425 |  |
          *
426 |  |
          * @param name the name of the configuration
427 |  |
          * @return the configuration with this name
428 |  |
          */
429 |  |
         public Configuration getConfiguration(String name)
430 |  |
         {
431 | 22 |
             return (Configuration) namedConfigurations.get(name);
432 |  |
         }
433 |  |
434 |  |
         /**
435 |  |
          * Removes the specified configuration from this combined configuration.
436 |  |
          *
437 |  |
          * @param config the configuration to be removed
438 |  |
          * @return a flag whether this configuration was found and could be removed
439 |  |
          */
440 |  |
         public boolean removeConfiguration(Configuration config)
441 |  |
         {
442 | 5 |
             for (int index = 0; index < getNumberOfConfigurations(); index++)
443 |  |
             {
444 | 4 |
                 if (((ConfigData) configurations.get(index)).getConfiguration() == config)
445 |  |
                 {
446 | 4 |
                     removeConfigurationAt(index);
447 | 4 |
                     return true;
448 |  |
                 }
449 |  |
             }
450 |  |
451 | 1 |
             return false;
452 |  |
         }
453 |  |
454 |  |
         /**
455 |  |
          * Removes the configuration at the specified index.
456 |  |
          *
457 |  |
          * @param index the index
458 |  |
          * @return the removed configuration
459 |  |
          */
460 |  |
         public Configuration removeConfigurationAt(int index)
461 |  |
         {
462 | 6 |
             ConfigData cd = (ConfigData) configurations.remove(index);
463 | 6 |
             if (cd.getName() != null)
464 |  |
             {
465 | 4 |
                 namedConfigurations.remove(cd.getName());
466 |  |
             }
467 | 6 |
             cd.getConfiguration().removeConfigurationListener(this);
468 | 6 |
             invalidate();
469 | 6 |
             return cd.getConfiguration();
470 |  |
         }
471 |  |
472 |  |
         /**
473 |  |
          * Removes the configuration with the specified name.
474 |  |
          *
475 |  |
          * @param name the name of the configuration to be removed
476 |  |
          * @return the removed configuration (<b>null</b> if this configuration
477 |  |
          * was not found)
478 |  |
          */
479 |  |
         public Configuration removeConfiguration(String name)
480 |  |
         {
481 | 3 |
             Configuration conf = getConfiguration(name);
482 | 3 |
             if (conf != null)
483 |  |
             {
484 | 2 |
                 removeConfiguration(conf);
485 |  |
             }
486 | 3 |
             return conf;
487 |  |
         }
488 |  |
489 |  |
         /**
490 |  |
          * Returns a set with the names of all configurations contained in this
491 |  |
          * combined configuration. Of course here are only these configurations
492 |  |
          * listed, for which a name was specified when they were added.
493 |  |
          *
494 |  |
          * @return a set with the names of the contained configurations (never
495 |  |
          * <b>null</b>)
496 |  |
          */
497 |  |
         public Set getConfigurationNames()
498 |  |
         {
499 | 14 |
             return namedConfigurations.keySet();
500 |  |
         }
501 |  |
502 |  |
         /**
503 |  |
          * Invalidates this combined configuration. This means that the next time a
504 |  |
          * property is accessed the combined node structure must be re-constructed.
505 |  |
          * Invalidation of a combined configuration also means that an event of type
506 |  |
          * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other
507 |  |
          * events most times appear twice (once before and once after an update),
508 |  |
          * this event is only fired once (after update).
509 |  |
          */
510 |  |
         public void invalidate()
511 |  |
         {
512 | 2129 |
             combinedRoot = null;
513 | 2129 |
             fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
514 | 2129 |
         }
515 |  |
516 |  |
         /**
517 |  |
          * Event listener call back for configuration update events. This method is
518 |  |
          * called whenever one of the contained configurations was modified. It
519 |  |
          * invalidates this combined configuration.
520 |  |
          *
521 |  |
          * @param event the update event
522 |  |
          */
523 |  |
         public void configurationChanged(ConfigurationEvent event)
524 |  |
         {
525 | 3588 |
             if (!event.isBeforeUpdate())
526 |  |
             {
527 | 1805 |
                 invalidate();
528 |  |
             }
529 | 3588 |
         }
530 |  |
531 |  |
         /**
532 |  |
          * Returns the configuration root node of this combined configuration. This
533 |  |
          * method will construct a combined node structure using the current node
534 |  |
          * combiner if necessary.
535 |  |
          *
536 |  |
          * @return the combined root node
537 |  |
          */
538 |  |
         public ConfigurationNode getRootNode()
539 |  |
         {
540 | 1143 |
             if (combinedRoot == null)
541 |  |
             {
542 | 82 |
                 combinedRoot = constructCombinedNode();
543 |  |
             }
544 | 1143 |
             return combinedRoot;
545 |  |
         }
546 |  |
547 |  |
         /**
548 |  |
          * Clears this configuration. All contained configurations will be removed.
549 |  |
          */
550 |  |
         public void clear()
551 |  |
         {
552 | 80 |
             fireEvent(EVENT_CLEAR, null, null, true);
553 | 80 |
             configurations = new ArrayList();
554 | 80 |
             namedConfigurations = new HashMap();
555 | 80 |
             fireEvent(EVENT_CLEAR, null, null, false);
556 | 80 |
             invalidate();
557 | 80 |
         }
558 |  |
559 |  |
         /**
560 |  |
          * Returns a copy of this object. This implementation performs a deep clone,
561 |  |
          * i.e. all contained configurations will be cloned, too. For this to work,
562 |  |
          * all contained configurations must be cloneable. Registered event
563 |  |
          * listeners won't be cloned. The clone will use the same node combiner than
564 |  |
          * the original.
565 |  |
          *
566 |  |
          * @return the copied object
567 |  |
          */
568 |  |
         public Object clone()
569 |  |
         {
570 | 2 |
             CombinedConfiguration copy = (CombinedConfiguration) super.clone();
571 | 2 |
             copy.clear();
572 | 2 |
             for (Iterator it = configurations.iterator(); it.hasNext();)
573 |  |
             {
574 | 4 |
                 ConfigData cd = (ConfigData) it.next();
575 | 4 |
                 copy.addConfiguration((AbstractConfiguration) ConfigurationUtils
576 |  |
                         .cloneConfiguration(cd.getConfiguration()), cd.getName(),
577 |  |
                         cd.getAt());
578 |  |
             }
579 |  |
580 | 2 |
             copy.setRootNode(new DefaultConfigurationNode());
581 | 2 |
             return copy;
582 |  |
         }
583 |  |
584 |  |
         /**
585 |  |
          * Returns the configuration source, in which the specified key is defined.
586 |  |
          * This method will determine the configuration node that is identified by
587 |  |
          * the given key. The following constellations are possible:
588 |  |
          * <ul>
589 |  |
          * <li>If no node object is found for this key, <b>null</b> is returned.</li>
590 |  |
          * <li>If the key maps to multiple nodes belonging to different
591 |  |
          * configuration sources, a <code>IllegalArgumentException</code> is
592 |  |
          * thrown (in this case no unique source can be determined).</li>
593 |  |
          * <li>If exactly one node is found for the key, the (child) configuration
594 |  |
          * object, to which the node belongs is determined and returned.</li>
595 |  |
          * <li>For keys that have been added directly to this combined
596 |  |
          * configuration and that do not belong to the namespaces defined by
597 |  |
          * existing child configurations this configuration will be returned.</li>
598 |  |
          * </ul>
599 |  |
          *
600 |  |
          * @param key the key of a configuration property
601 |  |
          * @return the configuration, to which this property belongs or <b>null</b>
602 |  |
          * if the key cannot be resolved
603 |  |
          * @throws IllegalArgumentException if the key maps to multiple properties
604 |  |
          * and the source cannot be determined, or if the key is <b>null</b>
605 |  |
          * @since 1.5
606 |  |
          */
607 |  |
         public Configuration getSource(String key)
608 |  |
         {
609 | 7 |
             if (key == null)
610 |  |
             {
611 | 1 |
                 throw new IllegalArgumentException("Key must not be null!");
612 |  |
             }
613 |  |
614 | 6 |
             List nodes = fetchNodeList(key);
615 | 6 |
             if (nodes.isEmpty())
616 |  |
             {
617 | 1 |
                 return null;
618 |  |
             }
619 |  |
620 | 5 |
             Iterator it = nodes.iterator();
621 | 5 |
             Configuration source = findSourceConfiguration((ConfigurationNode) it
622 |  |
                     .next());
623 | 9 |
             while (it.hasNext())
624 |  |
             {
625 | 5 |
                 Configuration src = findSourceConfiguration((ConfigurationNode) it
626 |  |
                         .next());
627 | 5 |
                 if (src != source)
628 |  |
                 {
629 | 1 |
                     throw new IllegalArgumentException("The key " + key
630 |  |
                             + " is defined by multiple sources!");
631 |  |
                 }
632 |  |
             }
633 |  |
634 | 4 |
             return source;
635 |  |
         }
636 |  |
637 |  |
         /**
638 |  |
          * Evaluates the passed in property key and returns a list with the matching
639 |  |
          * configuration nodes. This implementation also evaluates the
640 |  |
          * <em>force reload check</em> flag. If it is set,
641 |  |
          * <code>performReloadCheck()</code> is invoked.
642 |  |
          *
643 |  |
          * @param key the property key
644 |  |
          * @return a list with the matching configuration nodes
645 |  |
          */
646 |  |
         protected List fetchNodeList(String key)
647 |  |
         {
648 | 1100 |
             if (isForceReloadCheck())
649 |  |
             {
650 | 5 |
                 performReloadCheck();
651 |  |
             }
652 |  |
653 | 1100 |
             return super.fetchNodeList(key);
654 |  |
         }
655 |  |
656 |  |
         /**
657 |  |
          * Triggers the contained configurations to perform a reload check if
658 |  |
          * necessary. This method is called when a property of this combined
659 |  |
          * configuration is accessed and the <code>forceReloadCheck</code> property
660 |  |
          * is set to <b>true</b>.
661 |  |
          *
662 |  |
          * @see #setForceReloadCheck(boolean)
663 |  |
          * @since 1.6
664 |  |
          */
665 |  |
         protected void performReloadCheck()
666 |  |
         {
667 | 5 |
             for (Iterator it = configurations.iterator(); it.hasNext();)
668 |  |
             {
669 |  |
                 try
670 |  |
                 {
671 |  |
                     // simply retrieve a property; this is enough for
672 |  |
                     // triggering a reload
673 | 8 |
                     ((ConfigData) it.next()).getConfiguration().getProperty(
674 |  |
                             PROP_RELOAD_CHECK);
675 |  |
                 }
676 | 0 |
                 catch (Exception ex)
677 |  |
                 {
678 |  |
                     // ignore all exceptions, e.g. missing property exceptions
679 |  |
                     ;
680 | 8 |
                 }
681 |  |
             }
682 | 5 |
         }
683 |  |
684 |  |
         /**
685 |  |
          * Creates the root node of this combined configuration.
686 |  |
          *
687 |  |
          * @return the combined root node
688 |  |
          */
689 |  |
         private ConfigurationNode constructCombinedNode()
690 |  |
         {
691 | 82 |
             if (getNumberOfConfigurations() < 1)
692 |  |
             {
693 | 10 |
                 return new ViewNode();
694 |  |
             }
695 |  |
696 |  |
             else
697 |  |
             {
698 | 72 |
                 Iterator it = configurations.iterator();
699 | 72 |
                 ConfigurationNode node = ((ConfigData) it.next())
700 |  |
                         .getTransformedRoot();
701 | 138 |
                 while (it.hasNext())
702 |  |
                 {
703 | 66 |
                     node = getNodeCombiner().combine(node,
704 |  |
                             ((ConfigData) it.next()).getTransformedRoot());
705 |  |
                 }
706 | 72 |
                 return node;
707 |  |
             }
708 |  |
         }
709 |  |
710 |  |
         /**
711 |  |
          * Determines the configuration that owns the specified node.
712 |  |
          *
713 |  |
          * @param node the node
714 |  |
          * @return the owning configuration
715 |  |
          */
716 |  |
         private Configuration findSourceConfiguration(ConfigurationNode node)
717 |  |
         {
718 | 10 |
             ConfigurationNode root = null;
719 | 10 |
             ConfigurationNode current = node;
720 |  |
721 |  |
             // find the root node in this hierarchy
722 | 40 |
             while (current != null)
723 |  |
             {
724 | 30 |
                 root = current;
725 | 30 |
                 current = current.getParentNode();
726 |  |
             }
727 |  |
728 |  |
             // Check with the root nodes of the child configurations
729 | 10 |
             for (Iterator it = configurations.iterator(); it.hasNext();)
730 |  |
             {
731 | 13 |
                 ConfigData cd = (ConfigData) it.next();
732 | 13 |
                 if (root == cd.getRootNode())
733 |  |
                 {
734 | 9 |
                     return cd.getConfiguration();
735 |  |
                 }
736 |  |
             }
737 |  |
738 | 1 |
             return this;
739 |  |
         }
740 |  |
741 |  |
         /**
742 |  |
          * An internal helper class for storing information about contained
743 |  |
          * configurations.
744 |  |
          */
745 |  |
         class ConfigData
746 |  |
         {
747 |  |
             /** Stores a reference to the configuration. */
748 |  |
             private AbstractConfiguration configuration;
749 |  |
750 |  |
             /** Stores the name under which the configuration is stored. */
751 |  |
             private String name;
752 |  |
753 |  |
             /** Stores the at information as path of nodes. */
754 |  |
             private Collection atPath;
755 |  |
756 |  |
             /** Stores the at string.*/
757 |  |
             private String at;
758 |  |
759 |  |
             /** Stores the root node for this child configuration.*/
760 |  |
             private ConfigurationNode rootNode;
761 |  |
762 |  |
             /**
763 |  |
              * Creates a new instance of <code>ConfigData</code> and initializes
764 |  |
              * it.
765 |  |
              *
766 |  |
              * @param config the configuration
767 |  |
              * @param n the name
768 |  |
              * @param at the at position
769 |  |
              */
770 |  |
             public ConfigData(AbstractConfiguration config, String n, String at)
771 | 133 |
             {
772 | 133 |
                 configuration = config;
773 | 133 |
                 name = n;
774 | 133 |
                 atPath = parseAt(at);
775 | 133 |
                 this.at = at;
776 | 133 |
             }
777 |  |
778 |  |
             /**
779 |  |
              * Returns the stored configuration.
780 |  |
              *
781 |  |
              * @return the configuration
782 |  |
              */
783 |  |
             public AbstractConfiguration getConfiguration()
784 |  |
             {
785 | 212 |
                 return configuration;
786 |  |
             }
787 |  |
788 |  |
             /**
789 |  |
              * Returns the configuration's name.
790 |  |
              *
791 |  |
              * @return the name
792 |  |
              */
793 |  |
             public String getName()
794 |  |
             {
795 | 14 |
                 return name;
796 |  |
             }
797 |  |
798 |  |
             /**
799 |  |
              * Returns the at position of this configuration.
800 |  |
              *
801 |  |
              * @return the at position
802 |  |
              */
803 |  |
             public String getAt()
804 |  |
             {
805 | 4 |
                 return at;
806 |  |
             }
807 |  |
808 |  |
             /**
809 |  |
              * Returns the root node for this child configuration.
810 |  |
              *
811 |  |
              * @return the root node of this child configuration
812 |  |
              * @since 1.5
813 |  |
              */
814 |  |
             public ConfigurationNode getRootNode()
815 |  |
             {
816 | 13 |
                 return rootNode;
817 |  |
             }
818 |  |
819 |  |
             /**
820 |  |
              * Returns the transformed root node of the stored configuration. The
821 |  |
              * term &quot;transformed&quot; means that an eventually defined at path
822 |  |
              * has been applied.
823 |  |
              *
824 |  |
              * @return the transformed root node
825 |  |
              */
826 |  |
             public ConfigurationNode getTransformedRoot()
827 |  |
             {
828 | 138 |
                 ViewNode result = new ViewNode();
829 | 138 |
                 ViewNode atParent = result;
830 |  |
831 | 138 |
                 if (atPath != null)
832 |  |
                 {
833 |  |
                     // Build the complete path
834 | 12 |
                     for (Iterator it = atPath.iterator(); it.hasNext();)
835 |  |
                     {
836 | 14 |
                         ViewNode node = new ViewNode();
837 | 14 |
                         node.setName((String) it.next());
838 | 14 |
                         atParent.addChild(node);
839 | 14 |
                         atParent = node;
840 |  |
                     }
841 |  |
                 }
842 |  |
843 |  |
                 // Copy data of the root node to the new path
844 | 138 |
                 HierarchicalConfiguration hc = ConfigurationUtils
845 |  |
                         .convertToHierarchical(getConfiguration(),
846 |  |
                                 getConversionExpressionEngine());
847 | 138 |
                 atParent.appendChildren(hc.getRootNode());
848 | 138 |
                 atParent.appendAttributes(hc.getRootNode());
849 | 138 |
                 rootNode = hc.getRootNode();
850 |  |
851 | 138 |
                 return result;
852 |  |
             }
853 |  |
854 |  |
             /**
855 |  |
              * Splits the at path into its components.
856 |  |
              *
857 |  |
              * @param at the at string
858 |  |
              * @return a collection with the names of the single components
859 |  |
              */
860 |  |
             private Collection parseAt(String at)
861 |  |
             {
862 | 133 |
                 if (at == null)
863 |  |
                 {
864 | 121 |
                     return null;
865 |  |
                 }
866 |  |
867 | 12 |
                 Collection result = new ArrayList();
868 | 12 |
                 DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
869 |  |
                         AT_ENGINE, at).iterator();
870 | 26 |
                 while (it.hasNext())
871 |  |
                 {
872 | 14 |
                     result.add(it.nextKey());
873 |  |
                 }
874 | 12 |
                 return result;
875 |  |
             }
876 |  |
         }
877 |  |
     }