Coverage report

  %line %branch
org.apache.commons.configuration.HierarchicalConfiguration$CloneVisitor
100% 
100% 

 1  
 /*
 2  
  * Copyright 2001-2004 The Apache Software Foundation.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License")
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 
 17  
 package org.apache.commons.configuration;
 18  
 
 19  
 import java.io.Serializable;
 20  
 import java.util.ArrayList;
 21  
 import java.util.Collection;
 22  
 import java.util.HashSet;
 23  
 import java.util.Iterator;
 24  
 import java.util.LinkedList;
 25  
 import java.util.List;
 26  
 import java.util.Set;
 27  
 import java.util.Stack;
 28  
 
 29  
 import org.apache.commons.collections.map.LinkedMap;
 30  
 import org.apache.commons.lang.StringUtils;
 31  
 
 32  
 /**
 33  
  * <p>A specialized configuration class that extends its base class by the
 34  
  * ability of keeping more structure in the stored properties.</p>
 35  
  * <p>There are some sources of configuration data that cannot be stored
 36  
  * very well in a <code>BaseConfiguration</code> object because then their
 37  
  * structure is lost. This is especially true for XML documents. This class
 38  
  * can deal with such structured configuration sources by storing the
 39  
  * properties in a tree-like organization.</p>
 40  
  * <p>The internal used storage form allows for a more sophisticated access to
 41  
  * single properties. As an example consider the following XML document:</p>
 42  
  * <p><pre>
 43  
  * &lt;database&gt;
 44  
  *   &lt;tables&gt;
 45  
  *     &lt;table&gt;
 46  
  *       &lt;name&gt;users&lt;/name&gt;
 47  
  *       &lt;fields&gt;
 48  
  *         &lt;field&gt;
 49  
  *           &lt;name&gt;lid&lt;/name&gt;
 50  
  *           &lt;type&gt;long&lt;/name&gt;
 51  
  *         &lt;/field&gt;
 52  
  *         &lt;field&gt;
 53  
  *           &lt;name&gt;usrName&lt;/name&gt;
 54  
  *           &lt;type&gt;java.lang.String&lt;/type&gt;
 55  
  *         &lt;/field&gt;
 56  
  *        ...
 57  
  *       &lt;/fields&gt;
 58  
  *     &lt;/table&gt;
 59  
  *     &lt;table&gt;
 60  
  *       &lt;name&gt;documents&lt;/name&gt;
 61  
  *       &lt;fields&gt;
 62  
  *         &lt;field&gt;
 63  
  *           &lt;name&gt;docid&lt;/name&gt;
 64  
  *           &lt;type&gt;long&lt;/type&gt;
 65  
  *         &lt;/field&gt;
 66  
  *         ...
 67  
  *       &lt;/fields&gt;
 68  
  *     &lt;/table&gt;
 69  
  *     ...
 70  
  *   &lt;/tables&gt;
 71  
  * &lt;/database&gt;
 72  
  * </pre></p>
 73  
  * <p>If this document is parsed and stored in a
 74  
  * <code>HierarchicalConfiguration</code> object (which can be done by one of
 75  
  * the sub classes), there are enhanced possibilities of accessing properties.
 76  
  * The keys for querying information can contain indices that select a certain
 77  
  * element if there are multiple hits.</p>
 78  
  * <p>For instance the key <code>tables.table(0).name</code> can be used to
 79  
  * find out the name of the first table. In opposite
 80  
  * <code>tables.table.name</code> would return a collection with the names of
 81  
  * all available tables. Similarily the key
 82  
  * <code>tables.table(1).fields.field.name</code> returns a collection with the
 83  
  * names of all fields of the second table. If another index is added after the
 84  
  * <code>field</code> element, a single field can be accessed:
 85  
  * <code>tables.table(1).fields.field(0).name</code>.</p>
 86  
  * <p>There is a <code>getMaxIndex()</code> method that returns the maximum
 87  
  * allowed index that can be added to a given property key. This method can be
 88  
  * used to iterate over all values defined for a certain property.</p>
 89  
  *
 90  
  * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
 91  
  * @version $Id: HierarchicalConfiguration.java,v 1.11 2004/10/11 09:27:20 henning Exp $
 92  
  */
 93  
 public class HierarchicalConfiguration extends AbstractConfiguration
 94  
 {
 95  
     /** Constant for a new dummy key.*/
 96  
     private static final String NEW_KEY = "newKey";
 97  
 
 98  
     /** Stores the root node of this configuration.*/
 99  
     private Node root = new Node();
 100  
 
 101  
     /**
 102  
      * Creates a new instance of <code>HierarchicalConfiguration</code>.
 103  
      */
 104  
     public HierarchicalConfiguration()
 105  
     {
 106  
         super();
 107  
     }
 108  
 
 109  
     /**
 110  
      * Returns the root node of this hierarchical configuration.
 111  
      *
 112  
      * @return the root node
 113  
      */
 114  
     public Node getRoot()
 115  
     {
 116  
         return root;
 117  
     }
 118  
 
 119  
     /**
 120  
      * Sets the root node of this hierarchical configuration.
 121  
      *
 122  
      * @param node the root node
 123  
      */
 124  
     public void setRoot(Node node)
 125  
     {
 126  
         if (node == null)
 127  
         {
 128  
             throw new IllegalArgumentException("Root node must not be null!");
 129  
         }
 130  
         root = node;
 131  
     }
 132  
 
 133  
     /**
 134  
      * Fetches the specified property. Performs a recursive lookup in the
 135  
      * tree with the configuration properties.
 136  
      *
 137  
      * @param key the key to be looked up
 138  
      * @return the found value
 139  
      */
 140  
     protected Object getPropertyDirect(String key)
 141  
     {
 142  
         List nodes = fetchNodeList(key);
 143  
 
 144  
         if (nodes.size() == 0)
 145  
         {
 146  
             return null;
 147  
         }
 148  
         else
 149  
         {
 150  
             List list = new ArrayList();
 151  
             for (Iterator it = nodes.iterator(); it.hasNext();)
 152  
             {
 153  
                 Node node = (Node) it.next();
 154  
                 if (node.getValue() != null)
 155  
                 {
 156  
                     list.add(node.getValue());
 157  
                 }
 158  
             }
 159  
 
 160  
             if (list.size() < 1)
 161  
             {
 162  
                 return null;
 163  
             }
 164  
             else
 165  
             {
 166  
                 return (list.size() == 1) ? list.get(0) : list;
 167  
             }
 168  
         }
 169  
     }
 170  
 
 171  
     /**
 172  
      * <p>Adds the property with the specified key.</p>
 173  
      * <p>To be able to deal with the structure supported by this configuration
 174  
      * implementation the passed in key is of importance, especially the
 175  
      * indices it might contain. The following example should clearify this:
 176  
      * Suppose the actual configuration contains the following elements:</p>
 177  
      * <p><pre>
 178  
      * tables
 179  
      *    +-- table
 180  
      *            +-- name = user
 181  
      *            +-- fields
 182  
      *                    +-- field
 183  
      *                            +-- name = uid
 184  
      *                    +-- field
 185  
      *                            +-- name = firstName
 186  
      *                    ...
 187  
      *    +-- table
 188  
      *            +-- name = documents
 189  
      *            +-- fields
 190  
      *                   ...
 191  
      * </pre></p>
 192  
      * <p>In this example a database structure is defined, e.g. all fields of
 193  
      * the first table could be accessed using the key
 194  
      * <code>tables.table(0).fields.field.name</code>. If now properties are
 195  
      * to be added, it must be exactly specified at which position in the
 196  
      * hierarchy the new property is to be inserted. So to add a new field name
 197  
      * to a table it is not enough to say just</p>
 198  
      * <p><pre>
 199  
      * config.addProperty("tables.table.fields.field.name", "newField");
 200  
      * </pre></p>
 201  
      * <p>The statement given above contains some ambiguity. For instance
 202  
      * it is not clear, to which table the new field should be added. If this
 203  
      * method finds such an ambiguity, it is resolved by following the last
 204  
      * valid path. Here this would be the last table. The same is true for the
 205  
      * <code>field</code>; because there are multiple fields and no explicit
 206  
      * index is provided, a new <code>name</code> property would be
 207  
      * added to the last field - which is propably not what was desired.</p>
 208  
      * <p>To make things clear explicit indices should be provided whenever
 209  
      * possible. In the example above the exact table could be specified by
 210  
      * providing an index for the <code>table</code> element as in
 211  
      * <code>tables.table(1).fields</code>. By specifying an index it can also
 212  
      * be expressed that at a given position in the configuration tree a new
 213  
      * branch should be added. In the example above we did not want to add
 214  
      * an additional <code>name</code> element to the last field of the table,
 215  
      * but we want a complete new <code>field</code> element. This can be
 216  
      * achieved by specifying an invalid index (like -1) after the element
 217  
      * where a new branch should be created. Given this our example would run:
 218  
      * </p><p><pre>
 219  
      * config.addProperty("tables.table(1).fields.field(-1).name", "newField");
 220  
      * </pre></p>
 221  
      * <p>With this notation it is possible to add new branches everywhere.
 222  
      * We could for instance create a new <code>table</code> element by
 223  
      * specifying</p>
 224  
      * <p><pre>
 225  
      * config.addProperty("tables.table(-1).fields.field.name", "newField2");
 226  
      * </pre></p>
 227  
      * <p>(Note that because after the <code>table</code> element a new
 228  
      * branch is created indices in following elements are not relevant; the
 229  
      * branch is new so there cannot be any ambiguities.)</p>
 230  
      *
 231  
      * @param key the key of the new property
 232  
      * @param obj the value of the new property
 233  
      */
 234  
     protected void addPropertyDirect(String key, Object obj)
 235  
     {
 236  
         ConfigurationKey.KeyIterator it = new ConfigurationKey(key).iterator();
 237  
         Node parent = fetchAddNode(it, getRoot());
 238  
 
 239  
         Node child = new Node(it.currentKey(true));
 240  
         child.setValue(obj);
 241  
         parent.addChild(child);
 242  
     }
 243  
 
 244  
     /**
 245  
      * Adds a collection of nodes at the specified position of the
 246  
      * configuration tree. This method works similar to
 247  
      * <code>addProperty()</code>, but instead of a single property a whole
 248  
      * collection of nodes can be added - and thus complete configuration
 249  
      * sub trees. E.g. with this method it is possible to add parts of
 250  
      * another <code>HierarchicalConfiguration</code> object to this object.
 251  
      *
 252  
      * @param key the key where the nodes are to be added; can be <b>null</b>,
 253  
      * then they are added to the root node
 254  
      * @param nodes a collection with the <code>Node</code> objects to be
 255  
      * added
 256  
      */
 257  
     public void addNodes(String key, Collection nodes)
 258  
     {
 259  
         if (nodes == null || nodes.isEmpty())
 260  
         {
 261  
             return;
 262  
         }
 263  
 
 264  
         Node parent;
 265  
         if (StringUtils.isEmpty(key))
 266  
         {
 267  
             parent = getRoot();
 268  
         }
 269  
         else
 270  
         {
 271  
             ConfigurationKey.KeyIterator kit =
 272  
                 new ConfigurationKey(key).iterator();
 273  
             parent = fetchAddNode(kit, getRoot());
 274  
 
 275  
             // fetchAddNode() does not really fetch the last component,
 276  
             // but one before. So we must perform an additional step.
 277  
             ConfigurationKey keyNew =
 278  
                 new ConfigurationKey(kit.currentKey(true));
 279  
             keyNew.append(NEW_KEY);
 280  
             parent = fetchAddNode(keyNew.iterator(), parent);
 281  
         }
 282  
 
 283  
         for (Iterator it = nodes.iterator(); it.hasNext();)
 284  
         {
 285  
             parent.addChild((Node) it.next());
 286  
         }
 287  
     }
 288  
 
 289  
     /**
 290  
      * Checks if this configuration is empty. Empty means that there are
 291  
      * no keys with any values, though there can be some (empty) nodes.
 292  
      *
 293  
      * @return a flag if this configuration is empty
 294  
      */
 295  
     public boolean isEmpty()
 296  
     {
 297  
         return !nodeDefined(getRoot());
 298  
     }
 299  
 
 300  
     /**
 301  
      * Creates a new <code>Configuration</code> object containing all keys
 302  
      * that start with the specified prefix. This implementation will return
 303  
      * a <code>HierarchicalConfiguration</code> object so that the structure
 304  
      * of the keys will be saved.
 305  
      * @param prefix the prefix of the keys for the subset
 306  
      * @return a new configuration object representing the selected subset
 307  
      */
 308  
     public Configuration subset(String prefix)
 309  
     {
 310  
         Collection nodes = fetchNodeList(prefix);
 311  
         if (nodes.isEmpty())
 312  
         {
 313  
             return new HierarchicalConfiguration();
 314  
         }
 315  
 
 316  
         HierarchicalConfiguration result = new HierarchicalConfiguration();
 317  
         CloneVisitor visitor = new CloneVisitor();
 318  
 
 319  
         for (Iterator it = nodes.iterator(); it.hasNext();)
 320  
         {
 321  
             Node nd = (Node) it.next();
 322  
             nd.visit(visitor, null);
 323  
 
 324  
             List children = visitor.getClone().getChildren();
 325  
             if (children.size() > 0)
 326  
             {
 327  
                 for (int i = 0; i < children.size(); i++)
 328  
                 {
 329  
                     result.getRoot().addChild((Node) children.get(i));
 330  
                 }
 331  
             }
 332  
         }
 333  
 
 334  
         return (result.isEmpty()) ? new HierarchicalConfiguration() : result;
 335  
     }
 336  
 
 337  
     /**
 338  
      * Checks if the specified key is contained in this configuration.
 339  
      * Note that for this configuration the term &quot;contained&quot; means
 340  
      * that the key has an associated value. If there is a node for this key
 341  
      * that has no value but children (either defined or undefined), this
 342  
      * method will still return <b>false</b>.
 343  
      *
 344  
      * @param key the key to be chekced
 345  
      * @return a flag if this key is contained in this configuration
 346  
      */
 347  
     public boolean containsKey(String key)
 348  
     {
 349  
         return getPropertyDirect(key) != null;
 350  
     }
 351  
 
 352  
     /**
 353  
      * Removes all values of the property with the given name.
 354  
      *
 355  
      * @param key the key of the property to be removed
 356  
      */
 357  
     public void clearProperty(String key)
 358  
     {
 359  
         List nodes = fetchNodeList(key);
 360  
 
 361  
         for (Iterator it = nodes.iterator(); it.hasNext();)
 362  
         {
 363  
             removeNode((Node) it.next());
 364  
         }
 365  
     }
 366  
 
 367  
     /**
 368  
      * <p>Returns an iterator with all keys defined in this configuration.</p>
 369  
      * <p>Note that the keys returned by this method will not contain
 370  
      * any indices. This means that some structure will be lost.</p>
 371  
      *
 372  
      * @return an iterator with the defined keys in this configuration
 373  
      */
 374  
     public Iterator getKeys()
 375  
     {
 376  
         DefinedKeysVisitor visitor = new DefinedKeysVisitor();
 377  
         getRoot().visit(visitor, new ConfigurationKey());
 378  
         return visitor.getKeyList().iterator();
 379  
     }
 380  
 
 381  
     /**
 382  
      * Returns the maximum defined index for the given key. This is
 383  
      * useful if there are multiple values for this key. They can then be
 384  
      * addressed separately by specifying indices from 0 to the return value
 385  
      * of this method.
 386  
      *
 387  
      * @param key the key to be checked
 388  
      * @return the maximum defined index for this key
 389  
      */
 390  
     public int getMaxIndex(String key)
 391  
     {
 392  
         return fetchNodeList(key).size() - 1;
 393  
     }
 394  
 
 395  
     /**
 396  
      * Helper method for fetching a list of all nodes that are addressed by
 397  
      * the specified key.
 398  
      *
 399  
      * @param key the key
 400  
      * @return a list with all affected nodes (never <b>null</b>)
 401  
      */
 402  
     protected List fetchNodeList(String key)
 403  
     {
 404  
         List nodes = new LinkedList();
 405  
         findPropertyNodes(
 406  
             new ConfigurationKey(key).iterator(),
 407  
             getRoot(),
 408  
             nodes);
 409  
         return nodes;
 410  
     }
 411  
 
 412  
     /**
 413  
      * Recursive helper method for fetching a property. This method
 414  
      * processes all facets of a configuration key, traverses the tree of
 415  
      * properties and fetches the the nodes of all matching properties.
 416  
      *
 417  
      * @param keyPart the configuration key iterator
 418  
      * @param node the actual node
 419  
      * @param data here the found nodes are stored
 420  
      */
 421  
     protected void findPropertyNodes(
 422  
         ConfigurationKey.KeyIterator keyPart,
 423  
         Node node,
 424  
         Collection data)
 425  
     {
 426  
         if (!keyPart.hasNext())
 427  
         {
 428  
             data.add(node);
 429  
         }
 430  
         else
 431  
         {
 432  
             String key = keyPart.nextKey(true);
 433  
             List children = node.getChildren(key);
 434  
             if (keyPart.hasIndex())
 435  
             {
 436  
                 if (keyPart.getIndex() < children.size()
 437  
                     && keyPart.getIndex() >= 0)
 438  
                 {
 439  
                     findPropertyNodes(
 440  
                         (ConfigurationKey.KeyIterator) keyPart.clone(),
 441  
                         (Node) children.get(keyPart.getIndex()),
 442  
                         data);
 443  
                 }
 444  
             }
 445  
             else
 446  
             {
 447  
                 for (Iterator it = children.iterator(); it.hasNext();)
 448  
                 {
 449  
                     findPropertyNodes(
 450  
                         (ConfigurationKey.KeyIterator) keyPart.clone(),
 451  
                         (Node) it.next(),
 452  
                         data);
 453  
                 }
 454  
             }
 455  
         }
 456  
     }
 457  
 
 458  
     /**
 459  
      * Checks if the specified node is defined.
 460  
      *
 461  
      * @param node the node to be checked
 462  
      * @return a flag if this node is defined
 463  
      */
 464  
     protected boolean nodeDefined(Node node)
 465  
     {
 466  
         DefinedVisitor visitor = new DefinedVisitor();
 467  
         node.visit(visitor, null);
 468  
         return visitor.isDefined();
 469  
     }
 470  
 
 471  
     /**
 472  
      * Removes the specified node from this configuration. This method
 473  
      * ensures that parent nodes that become undefined by this operation
 474  
      * are also removed.
 475  
      *
 476  
      * @param node the node to be removed
 477  
      */
 478  
     protected void removeNode(Node node)
 479  
     {
 480  
         Node parent = node.getParent();
 481  
         if (parent != null)
 482  
         {
 483  
             parent.remove(node);
 484  
             if (!nodeDefined(parent))
 485  
             {
 486  
                 removeNode(parent);
 487  
             }
 488  
         }
 489  
     }
 490  
 
 491  
     /**
 492  
      * Returns a reference to the parent node of an add operation.
 493  
      * Nodes for new properties can be added as children of this node.
 494  
      * If the path for the specified key does not exist so far, it is created
 495  
      * now.
 496  
      *
 497  
      * @param keyIt the iterator for the key of the new property
 498  
      * @param startNode the node to start the search with
 499  
      * @return the parent node for the add operation
 500  
      */
 501  
     protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt, Node startNode)
 502  
     {
 503  
         if (!keyIt.hasNext())
 504  
         {
 505  
             throw new IllegalArgumentException("Key must be defined!");
 506  
         }
 507  
 
 508  
         return createAddPath(keyIt, findLastPathNode(keyIt, startNode));
 509  
     }
 510  
 
 511  
     /**
 512  
      * Finds the last existing node for an add operation. This method
 513  
      * traverses the configuration tree along the specified key. The last
 514  
      * existing node on this path is returned.
 515  
      *
 516  
      * @param keyIt the key iterator
 517  
      * @param node the actual node
 518  
      * @return the last existing node on the given path
 519  
      */
 520  
     protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt, Node node)
 521  
     {
 522  
         String keyPart = keyIt.nextKey(true);
 523  
 
 524  
         if (keyIt.hasNext())
 525  
         {
 526  
             List list = node.getChildren(keyPart);
 527  
             int idx = (keyIt.hasIndex()) ? keyIt.getIndex() : list.size() - 1;
 528  
             if (idx < 0 || idx >= list.size())
 529  
             {
 530  
                 return node;
 531  
             }
 532  
             else
 533  
             {
 534  
                 return findLastPathNode(keyIt, (Node) list.get(idx));
 535  
             }
 536  
         }
 537  
 
 538  
         else
 539  
         {
 540  
             return node;
 541  
         }
 542  
     }
 543  
 
 544  
     /**
 545  
      * Creates the missing nodes for adding a new property. This method
 546  
      * ensures that there are corresponding nodes for all components of the
 547  
      * specified configuration key.
 548  
      *
 549  
      * @param keyIt the key iterator
 550  
      * @param root the base node of the path to be created
 551  
      * @return the last node of the path
 552  
      */
 553  
     protected Node createAddPath(ConfigurationKey.KeyIterator keyIt, Node root)
 554  
     {
 555  
         if (keyIt.hasNext())
 556  
         {
 557  
             Node child = new Node(keyIt.currentKey(true));
 558  
             root.addChild(child);
 559  
             keyIt.next();
 560  
             return createAddPath(keyIt, child);
 561  
         }
 562  
         else
 563  
         {
 564  
             return root;
 565  
         }
 566  
     }
 567  
 
 568  
     /**
 569  
      * A data class for storing (hierarchical) property information. A property
 570  
      * can have a value and an arbitrary number of child properties.
 571  
      *
 572  
      */
 573  
     public static class Node implements Serializable, Cloneable
 574  
     {
 575  
         /** Stores a reference to this node's parent.*/
 576  
         private Node parent;
 577  
 
 578  
         /** Stores the name of this node.*/
 579  
         private String name;
 580  
 
 581  
         /** Stores the value of this node.*/
 582  
         private Object value;
 583  
 
 584  
         /** Stores the children of this node.*/
 585  
         private LinkedMap children; // Explict type here or we
 586  
                                     // will get a findbugs error 
 587  
                                     // because Map doesn't imply 
 588  
                                     // Serializable
 589  
 
 590  
         /**
 591  
          * Creates a new instance of <code>Node</code>.
 592  
          */
 593  
         public Node()
 594  
         {
 595  
             this(null);
 596  
         }
 597  
 
 598  
         /**
 599  
          * Creates a new instance of <code>Node</code> and sets the name.
 600  
          *
 601  
          * @param name the node's name
 602  
          */
 603  
         public Node(String name)
 604  
         {
 605  
             setName(name);
 606  
         }
 607  
 
 608  
         /**
 609  
          * Returns the name of this node.
 610  
          *
 611  
          * @return the node name
 612  
          */
 613  
         public String getName()
 614  
         {
 615  
             return name;
 616  
         }
 617  
 
 618  
         /**
 619  
          * Returns the value of this node.
 620  
          *
 621  
          * @return the node value (may be <b>null</b>)
 622  
          */
 623  
         public Object getValue()
 624  
         {
 625  
             return value;
 626  
         }
 627  
 
 628  
         /**
 629  
          * Returns the parent of this node.
 630  
          *
 631  
          * @return this node's parent (can be <b>null</b>)
 632  
          */
 633  
         public Node getParent()
 634  
         {
 635  
             return parent;
 636  
         }
 637  
 
 638  
         /**
 639  
          * Sets the name of this node.
 640  
          *
 641  
          * @param string the node name
 642  
          */
 643  
         public void setName(String string)
 644  
         {
 645  
             name = string;
 646  
         }
 647  
 
 648  
         /**
 649  
          * Sets the value of this node.
 650  
          *
 651  
          * @param object the node value
 652  
          */
 653  
         public void setValue(Object object)
 654  
         {
 655  
             value = object;
 656  
         }
 657  
 
 658  
         /**
 659  
          * Sets the parent of this node.
 660  
          *
 661  
          * @param node the parent node
 662  
          */
 663  
         public void setParent(Node node)
 664  
         {
 665  
             parent = node;
 666  
         }
 667  
 
 668  
         /**
 669  
          * Adds the specified child object to this node. Note that there can
 670  
          * be multiple children with the same name.
 671  
          *
 672  
          * @param child the child to be added
 673  
          */
 674  
         public void addChild(Node child)
 675  
         {
 676  
             if (children == null)
 677  
             {
 678  
                 children = new LinkedMap();
 679  
             }
 680  
 
 681  
             List c = (List) children.get(child.getName());
 682  
             if (c == null)
 683  
             {
 684  
                 c = new ArrayList();
 685  
                 children.put(child.getName(), c);
 686  
             }
 687  
 
 688  
             c.add(child);
 689  
             child.setParent(this);
 690  
         }
 691  
 
 692  
         /**
 693  
          * Returns a list with the child nodes of this node.
 694  
          *
 695  
          * @return a list with the children (can be empty, but never
 696  
          * <b>null</b>)
 697  
          */
 698  
         public List getChildren()
 699  
         {
 700  
             List result = new ArrayList();
 701  
 
 702  
             if (children != null)
 703  
             {
 704  
                 for (Iterator it = children.values().iterator(); it.hasNext();)
 705  
                 {
 706  
                     result.addAll((Collection) it.next());
 707  
                 }
 708  
             }
 709  
 
 710  
             return result;
 711  
         }
 712  
 
 713  
         /**
 714  
          * Returns a list with this node's children with the given name.
 715  
          *
 716  
          * @param name the name of the children
 717  
          * @return a list with all chidren with this name; may be empty, but
 718  
          * never <b>null</b>
 719  
          */
 720  
         public List getChildren(String name)
 721  
         {
 722  
             if (name == null || children == class="keyword">null)
 723  
             {
 724  
                 return getChildren();
 725  
             }
 726  
 
 727  
             List list = new ArrayList();
 728  
             List c = (List) children.get(name);
 729  
             if (c != null)
 730  
             {
 731  
                 list.addAll(c);
 732  
             }
 733  
 
 734  
             return list;
 735  
         }
 736  
 
 737  
         /**
 738  
          * Removes the specified child from this node.
 739  
          *
 740  
          * @param child the child node to be removed
 741  
          * @return a flag if the child could be found
 742  
          */
 743  
         public boolean remove(Node child)
 744  
         {
 745  
             if (children == null)
 746  
             {
 747  
                 return false;
 748  
             }
 749  
 
 750  
             List c = (List) children.get(child.getName());
 751  
             if (c == null)
 752  
             {
 753  
                 return false;
 754  
             }
 755  
 
 756  
             else
 757  
             {
 758  
                 if (c.remove(child))
 759  
                 {
 760  
                     if (c.isEmpty())
 761  
                     {
 762  
                         children.remove(child.getName());
 763  
                     }
 764  
                     return true;
 765  
                 }
 766  
                 else
 767  
                 {
 768  
                     return false;
 769  
                 }
 770  
             }
 771  
         }
 772  
 
 773  
         /**
 774  
          * Removes all children with the given name.
 775  
          *
 776  
          * @param name the name of the children to be removed
 777  
          * @return a flag if children with this name existed
 778  
          */
 779  
         public boolean remove(String name)
 780  
         {
 781  
             if (children == null)
 782  
             {
 783  
                 return false;
 784  
             }
 785  
 
 786  
             return children.remove(name) != null;
 787  
         }
 788  
 
 789  
         /**
 790  
          * Removes all children of this node.
 791  
          */
 792  
         public void removeChildren()
 793  
         {
 794  
             children = null;
 795  
         }
 796  
 
 797  
         /**
 798  
          * A generic method for traversing this node and all of its children.
 799  
          * This method sends the passed in visitor to this node and all of its
 800  
          * children.
 801  
          *
 802  
          * @param visitor the visitor
 803  
          * @param key here a configuration key with the name of the root node
 804  
          * of the iteration can be passed; if this key is not <b>null</b>, the
 805  
          * full pathes to the visited nodes are builded and passed to the
 806  
          * visitor's <code>visit()</code> methods
 807  
          */
 808  
         public void visit(NodeVisitor visitor, ConfigurationKey key)
 809  
         {
 810  
             int length = 0;
 811  
             if (key != null)
 812  
             {
 813  
                 length = key.length();
 814  
                 if (getName() != null)
 815  
                 {
 816  
                     key.append(getName());
 817  
                 }
 818  
             }
 819  
 
 820  
             visitor.visitBeforeChildren(this, key);
 821  
 
 822  
             if (children != null)
 823  
             {
 824  
                 for (Iterator it = children.values().iterator();
 825  
                     it.hasNext() && !visitor.terminate();
 826  
                     )
 827  
                 {
 828  
                     Collection col = (Collection) it.next();
 829  
                     for (Iterator it2 = col.iterator();
 830  
                         it2.hasNext() && !visitor.terminate();
 831  
                         )
 832  
                     {
 833  
                         ((Node) it2.next()).visit(visitor, key);
 834  
                     }
 835  
                 }
 836  
             }
 837  
 
 838  
             if (key != null)
 839  
             {
 840  
                 key.setLength(length);
 841  
             }
 842  
             visitor.visitAfterChildren(this, key);
 843  
         }
 844  
 
 845  
         /**
 846  
          * Creates a copy of this object. This is not a deep copy, the children
 847  
          * are not cloned.
 848  
          *
 849  
          * @return a copy of this object
 850  
          */
 851  
         protected Object clone()
 852  
         {
 853  
             try
 854  
             {
 855  
                 return super.clone();
 856  
             }
 857  
             catch (CloneNotSupportedException cex)
 858  
             {
 859  
                 return null; // should not happen
 860  
             }
 861  
         }
 862  
     }
 863  
 
 864  
     /**
 865  
      * <p>Definition of a visitor class for traversing a node and all of its
 866  
      * children.</p>
 867  
      * <p>This class defines the interface of a visitor for <code>Node</code>
 868  
      * objects and provides a default implementation. The method
 869  
      * <code>visit()</code> of <code>Node</code> implements a generic
 870  
      * iteration algorithm based on the <em>Visitor</em> pattern. By
 871  
      * providing different implementations of visitors it is possible to
 872  
      * collect different data during the iteration process.</p>
 873  
      *
 874  
      */
 875  
     public static class NodeVisitor
 876  
     {
 877  
         /**
 878  
          * Visits the specified node. This method is called during iteration
 879  
          * for each node before its children have been visited.
 880  
          *
 881  
          * @param node the actual node
 882  
          * @param key the key of this node (may be <b>null</b>)
 883  
          */
 884  
         public void visitBeforeChildren(Node node, ConfigurationKey key)
 885  
         {
 886  
         }
 887  
 
 888  
         /**
 889  
          * Visits the specified node after its children have been processed.
 890  
          * This gives a visitor the opportunity of collecting additional data
 891  
          * after the child nodes have been visited.
 892  
          *
 893  
          * @param node the node to be visited
 894  
          * @param key the key of this node (may be <b>null</b>)
 895  
          */
 896  
         public void visitAfterChildren(Node node, ConfigurationKey key)
 897  
         {
 898  
         }
 899  
 
 900  
         /**
 901  
          * Returns a flag that indicates if iteration should be stopped. This
 902  
          * method is called after each visited node. It can be useful for
 903  
          * visitors that search a specific node. If this node is found, the
 904  
          * whole process can be stopped. This base implementation always
 905  
          * returns <b>false</b>.
 906  
          *
 907  
          * @return a flag if iteration should be stopped
 908  
          */
 909  
         public boolean terminate()
 910  
         {
 911  
             return false;
 912  
         }
 913  
     }
 914  
 
 915  
     /**
 916  
      * A specialized visitor that checks if a node is defined.
 917  
      * &quot;Defined&quot; in this terms means that the node or at least one
 918  
      * of its sub nodes is associated with a value.
 919  
      *
 920  
      */
 921  
     static class DefinedVisitor extends NodeVisitor
 922  
     {
 923  
         /** Stores the defined flag.*/
 924  
         private boolean defined;
 925  
 
 926  
         /**
 927  
          * Checks if iteration should be stopped. This can be done if the first
 928  
          * defined node is found.
 929  
          *
 930  
          * @return a flag if iteration should be stopped
 931  
          */
 932  
         public boolean terminate()
 933  
         {
 934  
             return isDefined();
 935  
         }
 936  
 
 937  
         /**
 938  
          * Visits the node. Checks if a value is defined.
 939  
          *
 940  
          * @param node the actual node
 941  
          * @param key the key of this node
 942  
          */
 943  
         public void visitBeforeChildren(Node node, ConfigurationKey key)
 944  
         {
 945  
             defined = node.getValue() != null;
 946  
         }
 947  
 
 948  
         /**
 949  
          * Returns the defined flag.
 950  
          *
 951  
          * @return the defined flag
 952  
          */
 953  
         public boolean isDefined()
 954  
         {
 955  
             return defined;
 956  
         }
 957  
     }
 958  
 
 959  
     /**
 960  
      * A specialized visitor that fills a list with keys that are defined in
 961  
      * a node hierarchy.
 962  
      *
 963  
      */
 964  
     static class DefinedKeysVisitor extends NodeVisitor
 965  
     {
 966  
         /** Stores the list to be filled.*/
 967  
         private Set keyList;
 968  
 
 969  
         /**
 970  
          * Default constructor.
 971  
          */
 972  
         public DefinedKeysVisitor()
 973  
         {
 974  
             keyList = new HashSet();
 975  
         }
 976  
 
 977  
         /**
 978  
          * Returns the list with all defined keys.
 979  
          *
 980  
          * @return the list with the defined keys
 981  
          */
 982  
         public Set getKeyList()
 983  
         {
 984  
             return keyList;
 985  
         }
 986  
 
 987  
         /**
 988  
          * Visits the specified node. If this node has a value, its key is
 989  
          * added to the internal list.
 990  
          *
 991  
          * @param node the node to be visited
 992  
          * @param key the key of this node
 993  
          */
 994  
         public void visitBeforeChildren(Node node, ConfigurationKey key)
 995  
         {
 996  
             if (node.getValue() != null && key != class="keyword">null)
 997  
             {
 998  
                 keyList.add(key.toString());
 999  
             }
 1000  
         }
 1001  
     }
 1002  
 
 1003  
     /**
 1004  
      * A specialized visitor that is able to create a deep copy of a node
 1005  
      * hierarchy.
 1006  
      *
 1007  
      */
 1008  
     static class CloneVisitor extends NodeVisitor
 1009  
     {
 1010  
         /** A stack with the actual object to be copied.*/
 1011  
         private Stack copyStack;
 1012  
 
 1013  
         /** Stores the result of the clone process.*/
 1014  
         private Node result;
 1015  
 
 1016  
         /**
 1017  
          * Creates a new instance of <code>CloneVisitor</code>.
 1018  
          */
 1019  
         public CloneVisitor()
 1020  3
         {
 1021  3
             copyStack = new Stack();
 1022  3
         }
 1023  
 
 1024  
         /**
 1025  
          * Visits the specified node after its children have been processed.
 1026  
          *
 1027  
          * @param node the node
 1028  
          * @param key the key of this node
 1029  
          */
 1030  
         public void visitAfterChildren(Node node, ConfigurationKey key)
 1031  
         {
 1032  43
             copyStack.pop();
 1033  43
             if (copyStack.isEmpty())
 1034  
             {
 1035  21
                 result = node;
 1036  
             }
 1037  43
         }
 1038  
 
 1039  
         /**
 1040  
          * Visits and copies the specified node.
 1041  
          *
 1042  
          * @param node the node
 1043  
          * @param key the key of this node
 1044  
          */
 1045  
         public void visitBeforeChildren(Node node, ConfigurationKey key)
 1046  
         {
 1047  43
             Node copy = (Node) node.clone();
 1048  43
             copy.removeChildren();
 1049  
 
 1050  43
             if (!copyStack.isEmpty())
 1051  
             {
 1052  22
                 ((Node) copyStack.peek()).addChild(copy);
 1053  
             }
 1054  
 
 1055  43
             copyStack.push(copy);
 1056  43
         }
 1057  
 
 1058  
         /**
 1059  
          * Returns the result of the clone process. This is the root node of
 1060  
          * the cloned node hierarchy.
 1061  
          *
 1062  
          * @return the cloned root node
 1063  
          */
 1064  
         public Node getClone()
 1065  
         {
 1066  21
             return result;
 1067  
         }
 1068  
     }
 1069  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.