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 }