View Javadoc

    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 == 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 != 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         {
    1021             copyStack = new Stack();
    1022         }
    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             copyStack.pop();
    1033             if (copyStack.isEmpty())
    1034             {
    1035                 result = node;
    1036             }
    1037         }
    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             Node copy = (Node) node.clone();
    1048             copy.removeChildren();
    1049
    1050             if (!copyStack.isEmpty())
    1051             {
    1052                 ((Node) copyStack.peek()).addChild(copy);
    1053             }
    1054
    1055             copyStack.push(copy);
    1056         }
    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             return result;
    1067         }
    1068     }
    1069 }