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

Classes in this File | Line Coverage | Branch Coverage | Complexity
CombinedConfiguration |
97% | 116/119
100% | 22/22
| 2.125;2,125
CombinedConfiguration$ConfigData |
100% | 32/32
100% | 4/4
| 2.125;2,125

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