View Javadoc

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 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     private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine();
184 
185     /*** Constant for the default node combiner. */
186     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     {
215         setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
216         clear();
217     }
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         this(null);
228     }
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         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         if (nodeCombiner == null)
253         {
254             throw new IllegalArgumentException(
255                     "Node combiner must not be null!");
256         }
257         this.nodeCombiner = nodeCombiner;
258         invalidate();
259     }
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         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         this.forceReloadCheck = forceReloadCheck;
287     }
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         if (config == null)
309         {
310             throw new IllegalArgumentException(
311                     "Added configuration must not be null!");
312         }
313         if (name != null && namedConfigurations.containsKey(name))
314         {
315             throw new ConfigurationRuntimeException(
316                     "A configuration with the name '"
317                             + name
318                             + "' already exists in this combined configuration!");
319         }
320 
321         ConfigData cd = new ConfigData(config, name, at);
322         configurations.add(cd);
323         if (name != null)
324         {
325             namedConfigurations.put(name, config);
326         }
327 
328         config.addConfigurationListener(this);
329         invalidate();
330     }
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         addConfiguration(config, name, null);
343     }
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         addConfiguration(config, null, null);
355     }
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         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         ConfigData cd = (ConfigData) configurations.get(index);
379         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         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         for (int index = 0; index < getNumberOfConfigurations(); index++)
403         {
404             if (((ConfigData) configurations.get(index)).getConfiguration() == config)
405             {
406                 removeConfigurationAt(index);
407                 return true;
408             }
409         }
410 
411         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         ConfigData cd = (ConfigData) configurations.remove(index);
423         if (cd.getName() != null)
424         {
425             namedConfigurations.remove(cd.getName());
426         }
427         cd.getConfiguration().removeConfigurationListener(this);
428         invalidate();
429         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         Configuration conf = getConfiguration(name);
442         if (conf != null)
443         {
444             removeConfiguration(conf);
445         }
446         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         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         synchronized (getNodeCombiner()) // use combiner as lock
473         {
474             combinedRoot = null;
475         }
476         fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
477     }
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         invalidate();
489     }
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         synchronized (getNodeCombiner())
501         {
502             if (combinedRoot == null)
503             {
504                 combinedRoot = constructCombinedNode();
505             }
506             return combinedRoot;
507         }
508     }
509 
510     /***
511      * Clears this configuration. All contained configurations will be removed.
512      */
513     public void clear()
514     {
515         fireEvent(EVENT_CLEAR, null, null, true);
516         configurations = new ArrayList();
517         namedConfigurations = new HashMap();
518         fireEvent(EVENT_CLEAR, null, null, false);
519         invalidate();
520     }
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         CombinedConfiguration copy = (CombinedConfiguration) super.clone();
534         copy.clear();
535         for (Iterator it = configurations.iterator(); it.hasNext();)
536         {
537             ConfigData cd = (ConfigData) it.next();
538             copy.addConfiguration((AbstractConfiguration) ConfigurationUtils
539                     .cloneConfiguration(cd.getConfiguration()), cd.getName(),
540                     cd.getAt());
541         }
542 
543         copy.setRootNode(new DefaultConfigurationNode());
544         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         if (isForceReloadCheck())
560         {
561             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                     ((ConfigData) it.next()).getConfiguration().getProperty(
568                             PROP_RELOAD_CHECK);
569                 }
570                 catch (Exception ex)
571                 {
572                     // ignore all exceptions, e.g. missing property exceptions
573                     ;
574                 }
575             }
576         }
577 
578         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         if (key == null)
607         {
608             throw new IllegalArgumentException("Key must not be null!");
609         }
610 
611         List nodes = fetchNodeList(key);
612         if (nodes.isEmpty())
613         {
614             return null;
615         }
616 
617         Iterator it = nodes.iterator();
618         Configuration source = findSourceConfiguration((ConfigurationNode) it
619                 .next());
620         while (it.hasNext())
621         {
622             Configuration src = findSourceConfiguration((ConfigurationNode) it
623                     .next());
624             if (src != source)
625             {
626                 throw new IllegalArgumentException("The key " + key
627                         + " is defined by multiple sources!");
628             }
629         }
630 
631         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         if (getNumberOfConfigurations() < 1)
642         {
643             return new ViewNode();
644         }
645 
646         else
647         {
648             Iterator it = configurations.iterator();
649             ConfigurationNode node = ((ConfigData) it.next())
650                     .getTransformedRoot();
651             while (it.hasNext())
652             {
653                 node = getNodeCombiner().combine(node,
654                         ((ConfigData) it.next()).getTransformedRoot());
655             }
656             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         ConfigurationNode root = null;
669         ConfigurationNode current = node;
670 
671         // find the root node in this hierarchy
672         while (current != null)
673         {
674             root = current;
675             current = current.getParentNode();
676         }
677 
678         // Check with the root nodes of the child configurations
679         for (Iterator it = configurations.iterator(); it.hasNext();)
680         {
681             ConfigData cd = (ConfigData) it.next();
682             if (root == cd.getRootNode())
683             {
684                 return cd.getConfiguration();
685             }
686         }
687 
688         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         {
722             configuration = config;
723             name = n;
724             atPath = parseAt(at);
725             this.at = at;
726         }
727 
728         /***
729          * Returns the stored configuration.
730          *
731          * @return the configuration
732          */
733         public AbstractConfiguration getConfiguration()
734         {
735             return configuration;
736         }
737 
738         /***
739          * Returns the configuration's name.
740          *
741          * @return the name
742          */
743         public String getName()
744         {
745             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             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             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             ViewNode result = new ViewNode();
779             ViewNode atParent = result;
780 
781             if (atPath != null)
782             {
783                 // Build the complete path
784                 for (Iterator it = atPath.iterator(); it.hasNext();)
785                 {
786                     ViewNode node = new ViewNode();
787                     node.setName((String) it.next());
788                     atParent.addChild(node);
789                     atParent = node;
790                 }
791             }
792 
793             // Copy data of the root node to the new path
794             HierarchicalConfiguration hc = ConfigurationUtils
795                     .convertToHierarchical(getConfiguration());
796             atParent.appendChildren(hc.getRootNode());
797             atParent.appendAttributes(hc.getRootNode());
798             rootNode = hc.getRootNode();
799 
800             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             if (at == null)
812             {
813                 return null;
814             }
815 
816             Collection result = new ArrayList();
817             DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
818                     AT_ENGINE, at).iterator();
819             while (it.hasNext())
820             {
821                 result.add(it.nextKey());
822             }
823             return result;
824         }
825     }
826 }