Coverage Report - org.apache.commons.configuration.CombinedConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
CombinedConfiguration
98%
89/91
100%
15/15
1,862
CombinedConfiguration$ConfigData
100%
28/28
100%
4/4
1,862
 
 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 truely 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 add 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. Node that, depending on the used
 64  
  * <code>NodeCombiner</code>, this may be a complex operation.
 65  
  * </p>
 66  
  * <p>
 67  
  * It is not strictly forbidden to manipulate a
 68  
  * <code>CombinedConfiguration</code> directly, but the results may be
 69  
  * unpredictable. For instance some node combiners use special view nodes for
 70  
  * linking parts of the original configurations' data together. If new
 71  
  * properties are added to such a special node, they do not belong to any of the
 72  
  * managed configurations and thus hang in the air. It is also possible that
 73  
  * direct updates on a <code>CombinedConfiguration</code> are incompatible
 74  
  * with the used node combiner (e.g. if the
 75  
  * <code>{@link org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}</code>
 76  
  * is used and properties are removed the resulting node structure may be
 77  
  * incorrect because some properties that were hidden by the removed properties
 78  
  * are not visible). So it is recommended to perform updates only on the managed
 79  
  * configurations.
 80  
  * </p>
 81  
  * <p>
 82  
  * Whenever the node structure of a <code>CombinedConfiguration</code> becomes
 83  
  * invalid (either because one of the contained configurations was modified or
 84  
  * because the <code>invalidate()</code> method was directly called) an event
 85  
  * is generated. So this can be detected by interested event listeners. This
 86  
  * also makes it possible to add a combined configuration into another one.
 87  
  * </p>
 88  
  * <p>
 89  
  * Implementation note: Adding and removing configurations to and from a
 90  
  * combined configuration is not thread-safe. If a combined configuration is
 91  
  * manipulated by multiple threads, the developer has to take care about
 92  
  * properly synchronization.
 93  
  * </p>
 94  
  *
 95  
  * @author <a
 96  
  * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons
 97  
  * Configuration team</a>
 98  
  * @since 1.3
 99  
  * @version $Id: CombinedConfiguration.java 484692 2006-12-08 18:30:15Z oheger $
 100  
  */
 101  11
 public class CombinedConfiguration extends HierarchicalConfiguration implements
 102  
         ConfigurationListener, Cloneable
 103  
 {
 104  
     /**
 105  
      * Constant for the invalidate event that is fired when the internal node
 106  
      * structure becomes invalid.
 107  
      */
 108  
     public static final int EVENT_COMBINED_INVALIDATE = 40;
 109  
 
 110  
     /**
 111  
      * The serial version ID.
 112  
      */
 113  
     private static final long serialVersionUID = 8338574525528692307L;
 114  
 
 115  
     /** Constant for the expression engine for parsing the at path. */
 116  6
     private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine();
 117  
 
 118  
     /** Constant for the default node combiner. */
 119  3
     private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
 120  
 
 121  
     /** Constant for the name of the property used for the reload check.*/
 122  
     private static final String PROP_RELOAD_CHECK = "CombinedConfigurationReloadCheck";
 123  
 
 124  
     /** Stores the combiner. */
 125  
     private NodeCombiner nodeCombiner;
 126  
 
 127  
     /** Stores the combined root node. */
 128  
     private ConfigurationNode combinedRoot;
 129  
 
 130  
     /** Stores a list with the contained configurations. */
 131  
     private List configurations;
 132  
 
 133  
     /** Stores a map with the named configurations. */
 134  
     private Map namedConfigurations;
 135  
 
 136  
     /** A flag whether an enhanced reload check is to be performed.*/
 137  
     private boolean forceReloadCheck;
 138  
 
 139  
     /**
 140  
      * Creates a new instance of <code>CombinedConfiguration</code> and
 141  
      * initializes the combiner to be used.
 142  
      *
 143  
      * @param comb the node combiner (can be <b>null</b>, then a union combiner
 144  
      * is used as default)
 145  
      */
 146  
     public CombinedConfiguration(NodeCombiner comb)
 147  50
     {
 148  50
         setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
 149  50
         clear();
 150  50
     }
 151  
 
 152  
     /**
 153  
      * Creates a new instance of <code>CombinedConfiguration</code> that uses
 154  
      * a union combiner.
 155  
      *
 156  
      * @see org.apache.commons.configuration.tree.UnionCombiner
 157  
      */
 158  
     public CombinedConfiguration()
 159  
     {
 160  44
         this(null);
 161  44
     }
 162  
 
 163  
     /**
 164  
      * Returns the node combiner that is used for creating the combined node
 165  
      * structure.
 166  
      *
 167  
      * @return the node combiner
 168  
      */
 169  
     public NodeCombiner getNodeCombiner()
 170  
     {
 171  415
         return nodeCombiner;
 172  
     }
 173  
 
 174  
     /**
 175  
      * Sets the node combiner. This object will be used when the combined node
 176  
      * structure is to be constructed. It must not be <b>null</b>, otherwise an
 177  
      * <code>IllegalArgumentException</code> exception is thrown. Changing the
 178  
      * node combiner causes an invalidation of this combined configuration, so
 179  
      * that the new combiner immediately takes effect.
 180  
      *
 181  
      * @param nodeCombiner the node combiner
 182  
      */
 183  
     public void setNodeCombiner(NodeCombiner nodeCombiner)
 184  
     {
 185  72
         if (nodeCombiner == null)
 186  
         {
 187  1
             throw new IllegalArgumentException(
 188  
                     "Node combiner must not be null!");
 189  
         }
 190  71
         this.nodeCombiner = nodeCombiner;
 191  71
         invalidate();
 192  71
     }
 193  
 
 194  
     /**
 195  
      * Returns a flag whether an enhanced reload check must be performed.
 196  
      *
 197  
      * @return the force reload check flag
 198  
      * @since 1.4
 199  
      */
 200  
     public boolean isForceReloadCheck()
 201  
     {
 202  76
         return forceReloadCheck;
 203  
     }
 204  
 
 205  
     /**
 206  
      * Sets the force reload check flag. If this flag is set, each property
 207  
      * access on this configuration will cause a reload check on the contained
 208  
      * configurations. This is a workaround for a problem with some reload
 209  
      * implementations that only check if a reload is required when they are
 210  
      * triggered. Per default this mode is disabled. If the force reload check
 211  
      * flag is set to <b>true</b>, accessing properties will be less
 212  
      * performant, but reloads on contained configurations will be detected.
 213  
      *
 214  
      * @param forceReloadCheck the value of the flag
 215  
      * @since 1.4
 216  
      */
 217  
     public void setForceReloadCheck(boolean forceReloadCheck)
 218  
     {
 219  2
         this.forceReloadCheck = forceReloadCheck;
 220  2
     }
 221  
 
 222  
     /**
 223  
      * Adds a new configuration to this combined configuration. It is possible
 224  
      * (but not mandatory) to give the new configuration a name. This name must
 225  
      * be unique, otherwise a <code>ConfigurationRuntimeException</code> will
 226  
      * be thrown. With the optional <code>at</code> argument you can specify
 227  
      * where in the resulting node structure the content of the added
 228  
      * configuration should appear. This is a string that uses dots as property
 229  
      * delimiters (independent on the current expression engine). For instance
 230  
      * if you pass in the string <code>&quot;database.tables&quot;</code>,
 231  
      * all properties of the added configuration will occur in this branch.
 232  
      *
 233  
      * @param config the configuration to add (must not be <b>null</b>)
 234  
      * @param name the name of this configuration (can be <b>null</b>)
 235  
      * @param at the position of this configuration in the combined tree (can be
 236  
      * <b>null</b>)
 237  
      */
 238  
     public void addConfiguration(AbstractConfiguration config, String name,
 239  
             String at)
 240  
     {
 241  84
         if (config == null)
 242  
         {
 243  1
             throw new IllegalArgumentException(
 244  
                     "Added configuration must not be null!");
 245  
         }
 246  83
         if (name != null && namedConfigurations.containsKey(name))
 247  
         {
 248  1
             throw new ConfigurationRuntimeException(
 249  
                     "A configuration with the name '"
 250  
                             + name
 251  
                             + "' already exists in this combined configuration!");
 252  
         }
 253  
 
 254  82
         ConfigData cd = new ConfigData(config, name, at);
 255  82
         configurations.add(cd);
 256  82
         if (name != null)
 257  
         {
 258  34
             namedConfigurations.put(name, config);
 259  
         }
 260  
 
 261  82
         config.addConfigurationListener(this);
 262  82
         invalidate();
 263  82
     }
 264  
 
 265  
     /**
 266  
      * Adds a new configuration to this combined configuration with an optional
 267  
      * name. The new configuration's properties will be added under the root of
 268  
      * the combined node structure.
 269  
      *
 270  
      * @param config the configuration to add (must not be <b>null</b>)
 271  
      * @param name the name of this configuration (can be <b>null</b>)
 272  
      */
 273  
     public void addConfiguration(AbstractConfiguration config, String name)
 274  
     {
 275  13
         addConfiguration(config, name, null);
 276  13
     }
 277  
 
 278  
     /**
 279  
      * Adds a new configuration to this combined configuration. The new
 280  
      * configuration is not given a name. Its properties will be added under the
 281  
      * root of the combined node structure.
 282  
      *
 283  
      * @param config the configuration to add (must not be <b>null</b>)
 284  
      */
 285  
     public void addConfiguration(AbstractConfiguration config)
 286  
     {
 287  10
         addConfiguration(config, null, null);
 288  9
     }
 289  
 
 290  
     /**
 291  
      * Returns the number of configurations that are contained in this combined
 292  
      * configuration.
 293  
      *
 294  
      * @return the number of contained configurations
 295  
      */
 296  
     public int getNumberOfConfigurations()
 297  
     {
 298  71
         return configurations.size();
 299  
     }
 300  
 
 301  
     /**
 302  
      * Returns the configuration at the specified index. The contained
 303  
      * configurations are numbered in the order they were added to this combined
 304  
      * configuration. The index of the first configuration is 0.
 305  
      *
 306  
      * @param index the index
 307  
      * @return the configuration at this index
 308  
      */
 309  
     public Configuration getConfiguration(int index)
 310  
     {
 311  37
         ConfigData cd = (ConfigData) configurations.get(index);
 312  37
         return cd.getConfiguration();
 313  
     }
 314  
 
 315  
     /**
 316  
      * Returns the configuration with the given name. This can be <b>null</b>
 317  
      * if no such configuration exists.
 318  
      *
 319  
      * @param name the name of the configuration
 320  
      * @return the configuration with this name
 321  
      */
 322  
     public Configuration getConfiguration(String name)
 323  
     {
 324  15
         return (Configuration) namedConfigurations.get(name);
 325  
     }
 326  
 
 327  
     /**
 328  
      * Removes the specified configuration from this combined configuration.
 329  
      *
 330  
      * @param config the configuration to be removed
 331  
      * @return a flag whether this configuration was found and could be removed
 332  
      */
 333  
     public boolean removeConfiguration(Configuration config)
 334  
     {
 335  5
         for (int index = 0; index < getNumberOfConfigurations(); index++)
 336  
         {
 337  4
             if (((ConfigData) configurations.get(index)).getConfiguration() == config)
 338  
             {
 339  4
                 removeConfigurationAt(index);
 340  4
                 return true;
 341  
             }
 342  
         }
 343  
 
 344  1
         return false;
 345  
     }
 346  
 
 347  
     /**
 348  
      * Removes the configuration at the specified index.
 349  
      *
 350  
      * @param index the index
 351  
      * @return the removed configuration
 352  
      */
 353  
     public Configuration removeConfigurationAt(int index)
 354  
     {
 355  6
         ConfigData cd = (ConfigData) configurations.remove(index);
 356  6
         if (cd.getName() != null)
 357  
         {
 358  4
             namedConfigurations.remove(cd.getName());
 359  
         }
 360  6
         cd.getConfiguration().removeConfigurationListener(this);
 361  6
         invalidate();
 362  6
         return cd.getConfiguration();
 363  
     }
 364  
 
 365  
     /**
 366  
      * Removes the configuration with the specified name.
 367  
      *
 368  
      * @param name the name of the configuration to be removed
 369  
      * @return the removed configuration (<b>null</b> if this configuration
 370  
      * was not found)
 371  
      */
 372  
     public Configuration removeConfiguration(String name)
 373  
     {
 374  3
         Configuration conf = getConfiguration(name);
 375  3
         if (conf != null)
 376  
         {
 377  2
             removeConfiguration(conf);
 378  
         }
 379  3
         return conf;
 380  
     }
 381  
 
 382  
     /**
 383  
      * Returns a set with the names of all configurations contained in this
 384  
      * combined configuration. Of course here are only these configurations
 385  
      * listed, for which a name was specified when they were added.
 386  
      *
 387  
      * @return a set with the names of the contained configurations (never
 388  
      * <b>null</b>)
 389  
      */
 390  
     public Set getConfigurationNames()
 391  
     {
 392  14
         return namedConfigurations.keySet();
 393  
     }
 394  
 
 395  
     /**
 396  
      * Invalidates this combined configuration. This means that the next time a
 397  
      * property is accessed the combined node structure must be re-constructed.
 398  
      * Invalidation of a combined configuration also means that an event of type
 399  
      * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other
 400  
      * events most times appear twice (once before and once after an update),
 401  
      * this event is only fired once (after update).
 402  
      */
 403  
     public void invalidate()
 404  
     {
 405  258
         synchronized (getNodeCombiner()) // use combiner as lock
 406  
         {
 407  258
             combinedRoot = null;
 408  258
         }
 409  258
         fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
 410  258
     }
 411  
 
 412  
     /**
 413  
      * Event listener call back for configuration update events. This method is
 414  
      * called whenever one of the contained configurations was modified. It
 415  
      * invalidates this combined configuration.
 416  
      *
 417  
      * @param event the update event
 418  
      */
 419  
     public void configurationChanged(ConfigurationEvent event)
 420  
     {
 421  46
         invalidate();
 422  46
     }
 423  
 
 424  
     /**
 425  
      * Returns the configuration root node of this combined configuration. This
 426  
      * method will construct a combined node structure using the current node
 427  
      * combiner if necessary.
 428  
      *
 429  
      * @return the combined root node
 430  
      */
 431  
     public ConfigurationNode getRootNode()
 432  
     {
 433  101
         synchronized (getNodeCombiner())
 434  
         {
 435  101
             if (combinedRoot == null)
 436  
             {
 437  39
                 combinedRoot = constructCombinedNode();
 438  
             }
 439  101
             return combinedRoot;
 440  0
         }
 441  
     }
 442  
 
 443  
     /**
 444  
      * Clears this configuration. All contained configurations will be removed.
 445  
      */
 446  
     public void clear()
 447  
     {
 448  53
         fireEvent(EVENT_CLEAR, null, null, true);
 449  53
         configurations = new ArrayList();
 450  53
         namedConfigurations = new HashMap();
 451  53
         fireEvent(EVENT_CLEAR, null, null, false);
 452  53
         invalidate();
 453  53
     }
 454  
 
 455  
     /**
 456  
      * Returns a copy of this object. This implementation performs a deep clone,
 457  
      * i.e. all contained configurations will be cloned, too. For this to work,
 458  
      * all contained configurations must be cloneable. Registered event
 459  
      * listeners won't be cloned. The clone will use the same node combiner than
 460  
      * the original.
 461  
      *
 462  
      * @return the copied object
 463  
      */
 464  
     public Object clone()
 465  
     {
 466  2
         CombinedConfiguration copy = (CombinedConfiguration) super.clone();
 467  2
         copy.clear();
 468  8
         for (Iterator it = configurations.iterator(); it.hasNext();)
 469  
         {
 470  4
             ConfigData cd = (ConfigData) it.next();
 471  4
             copy.addConfiguration((AbstractConfiguration) ConfigurationUtils
 472  
                     .cloneConfiguration(cd.getConfiguration()), cd.getName(),
 473  
                     cd.getAt());
 474  
         }
 475  
 
 476  2
         copy.setRootNode(new DefaultConfigurationNode());
 477  2
         return copy;
 478  
     }
 479  
 
 480  
     /**
 481  
      * Returns the value of the specified property. This implementation
 482  
      * evaluates the <em>force reload check</em> flag. If it is set, all
 483  
      * contained configurations will be triggered before the value of the
 484  
      * requested property is retrieved.
 485  
      *
 486  
      * @param key the key of the desired property
 487  
      * @return the value of this property
 488  
      * @since 1.4
 489  
      */
 490  
     public Object getProperty(String key)
 491  
     {
 492  75
         if (isForceReloadCheck())
 493  
         {
 494  12
             for (Iterator it = configurations.iterator(); it.hasNext();)
 495  
             {
 496  
                 try
 497  
                 {
 498  
                     // simply retrieve a property; this is enough for
 499  
                     // triggering a reload
 500  6
                     ((ConfigData) it.next()).getConfiguration().getProperty(
 501  
                             PROP_RELOAD_CHECK);
 502  6
                 }
 503  
                 catch (Exception ex)
 504  
                 {
 505  
                     // ignore all exceptions, e.g. missing property exceptions
 506  0
                     ;
 507  
                 }
 508  
             }
 509  
         }
 510  
 
 511  75
         return super.getProperty(key);
 512  
     }
 513  
 
 514  
     /**
 515  
      * Creates the root node of this combined configuration.
 516  
      *
 517  
      * @return the combined root node
 518  
      */
 519  
     private ConfigurationNode constructCombinedNode()
 520  
     {
 521  39
         if (getNumberOfConfigurations() < 1)
 522  
         {
 523  7
             return new ViewNode();
 524  
         }
 525  
 
 526  
         else
 527  
         {
 528  32
             Iterator it = configurations.iterator();
 529  32
             ConfigurationNode node = ((ConfigData) it.next())
 530  
                     .getTransformedRoot();
 531  108
             while (it.hasNext())
 532  
             {
 533  44
                 node = getNodeCombiner().combine(node,
 534  
                         ((ConfigData) it.next()).getTransformedRoot());
 535  
             }
 536  32
             return node;
 537  
         }
 538  
     }
 539  
 
 540  
     /**
 541  
      * An internal helper class for storing information about contained
 542  
      * configurations.
 543  
      */
 544  
     static class ConfigData
 545  
     {
 546  
         /** Stores a reference to the configuration. */
 547  
         private AbstractConfiguration configuration;
 548  
 
 549  
         /** Stores the name under which the configuration is stored. */
 550  
         private String name;
 551  
 
 552  
         /** Stores the at information as path of nodes. */
 553  
         private Collection atPath;
 554  
 
 555  
         /** Stores the at string.*/
 556  
         private String at;
 557  
 
 558  
         /**
 559  
          * Creates a new instance of <code>ConfigData</code> and initializes
 560  
          * it.
 561  
          *
 562  
          * @param config the configuration
 563  
          * @param n the name
 564  
          * @param at the at position
 565  
          */
 566  
         public ConfigData(AbstractConfiguration config, String n, String at)
 567  82
         {
 568  82
             configuration = config;
 569  82
             name = n;
 570  82
             atPath = parseAt(at);
 571  82
             this.at = at;
 572  82
         }
 573  
 
 574  
         /**
 575  
          * Returns the stored configuration.
 576  
          *
 577  
          * @return the configuration
 578  
          */
 579  
         public AbstractConfiguration getConfiguration()
 580  
         {
 581  139
             return configuration;
 582  
         }
 583  
 
 584  
         /**
 585  
          * Returns the configuration's name.
 586  
          *
 587  
          * @return the name
 588  
          */
 589  
         public String getName()
 590  
         {
 591  14
             return name;
 592  
         }
 593  
 
 594  
         /**
 595  
          * Returns the at position of this configuration.
 596  
          *
 597  
          * @return the at position
 598  
          */
 599  
         public String getAt()
 600  
         {
 601  4
             return at;
 602  
         }
 603  
 
 604  
         /**
 605  
          * Returns the transformed root node of the stored configuration. The
 606  
          * term &quot;transformed&quot; means that an eventually defined at path
 607  
          * has been applied.
 608  
          *
 609  
          * @return the transformed root node
 610  
          */
 611  
         public ConfigurationNode getTransformedRoot()
 612  
         {
 613  76
             ViewNode result = new ViewNode();
 614  76
             ViewNode atParent = result;
 615  
 
 616  76
             if (atPath != null)
 617  
             {
 618  
                 // Build the complete path
 619  32
                 for (Iterator it = atPath.iterator(); it.hasNext();)
 620  
                 {
 621  12
                     ViewNode node = new ViewNode();
 622  12
                     node.setName((String) it.next());
 623  12
                     atParent.addChild(node);
 624  12
                     atParent = node;
 625  
                 }
 626  
             }
 627  
 
 628  
             // Copy data of the root node to the new path
 629  76
             HierarchicalConfiguration hc = ConfigurationUtils
 630  
                     .convertToHierarchical(getConfiguration());
 631  76
             atParent.appendChildren(hc.getRootNode());
 632  76
             atParent.appendAttributes(hc.getRootNode());
 633  
 
 634  76
             return result;
 635  
         }
 636  
 
 637  
         /**
 638  
          * Splits the at path into its components.
 639  
          *
 640  
          * @param at the at string
 641  
          * @return a collection with the names of the single components
 642  
          */
 643  
         private Collection parseAt(String at)
 644  
         {
 645  82
             if (at == null)
 646  
             {
 647  71
                 return null;
 648  
             }
 649  
 
 650  11
             Collection result = new ArrayList();
 651  11
             DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
 652  
                     AT_ENGINE, at).iterator();
 653  35
             while (it.hasNext())
 654  
             {
 655  13
                 result.add(it.nextKey());
 656  
             }
 657  11
             return result;
 658  
         }
 659  
     }
 660  
 }