    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.configuration;
    019
    020    import java.io.Serializable;
    021    import java.util.ArrayList;
    022    import java.util.Collection;
    023    import java.util.Iterator;
    024    import java.util.LinkedList;
    025    import java.util.List;
    026    import java.util.Set;
    027    import java.util.Stack;
    028
    029    import org.apache.commons.collections.iterators.SingletonIterator;
    030    import org.apache.commons.collections.set.ListOrderedSet;
    031    import org.apache.commons.configuration.event.ConfigurationEvent;
    032    import org.apache.commons.configuration.event.ConfigurationListener;
    033    import org.apache.commons.configuration.tree.ConfigurationNode;
    034    import org.apache.commons.configuration.tree.ConfigurationNodeVisitorAdapter;
    035    import org.apache.commons.configuration.tree.DefaultConfigurationNode;
    036    import org.apache.commons.configuration.tree.DefaultExpressionEngine;
    037    import org.apache.commons.configuration.tree.ExpressionEngine;
    038    import org.apache.commons.configuration.tree.NodeAddData;
    039    import org.apache.commons.configuration.tree.ViewNode;
    040    import org.apache.commons.lang.StringUtils;
    041
    042    /**
    043     * <p>A specialized configuration class that extends its base class by the
    044     * ability of keeping more structure in the stored properties.</p><p>There
    045     * are some sources of configuration data that cannot be stored very well in a
    046     * <code>BaseConfiguration</code> object because then their structure is lost.
    047     * This is especially true for XML documents. This class can deal with such
    048     * structured configuration sources by storing the properties in a tree-like
    049     * organization.</p><p>The internal used storage form allows for a more
    050     * sophisticated access to single properties. As an example consider the
    051     * following XML document:</p><p>
    052     *
    053     * <pre>
    054     * &lt;database&gt;
    055     *   &lt;tables&gt;
    056     *     &lt;table&gt;
    057     *       &lt;name&gt;users&lt;/name&gt;
    058     *       &lt;fields&gt;
    059     *         &lt;field&gt;
    060     *           &lt;name&gt;lid&lt;/name&gt;
    061     *           &lt;type&gt;long&lt;/name&gt;
    062     *         &lt;/field&gt;
    063     *         &lt;field&gt;
    064     *           &lt;name&gt;usrName&lt;/name&gt;
    065     *           &lt;type&gt;java.lang.String&lt;/type&gt;
    066     *         &lt;/field&gt;
    067     *        ...
    068     *       &lt;/fields&gt;
    069     *     &lt;/table&gt;
    070     *     &lt;table&gt;
    071     *       &lt;name&gt;documents&lt;/name&gt;
    072     *       &lt;fields&gt;
    073     *         &lt;field&gt;
    074     *           &lt;name&gt;docid&lt;/name&gt;
    075     *           &lt;type&gt;long&lt;/type&gt;
    076     *         &lt;/field&gt;
    077     *         ...
    078     *       &lt;/fields&gt;
    079     *     &lt;/table&gt;
    080     *     ...
    081     *   &lt;/tables&gt;
    082     * &lt;/database&gt;
    083     * </pre>
    084     *
    085     * </p><p>If this document is parsed and stored in a
    086     * <code>HierarchicalConfiguration</code> object (which can be done by one of
    087     * the sub classes), there are enhanced possibilities of accessing properties.
    088     * The keys for querying information can contain indices that select a certain
    089     * element if there are multiple hits.</p><p>For instance the key
    090     * <code>tables.table(0).name</code> can be used to find out the name of the
    091     * first table. In opposite <code>tables.table.name</code> would return a
    092     * collection with the names of all available tables. Similarly the key
    093     * <code>tables.table(1).fields.field.name</code> returns a collection with
    094     * the names of all fields of the second table. If another index is added after
    095     * the <code>field</code> element, a single field can be accessed:
    096     * <code>tables.table(1).fields.field(0).name</code>.</p><p>There is a
    097     * <code>getMaxIndex()</code> method that returns the maximum allowed index
    098     * that can be added to a given property key. This method can be used to iterate
    099     * over all values defined for a certain property.</p>
    100     * <p>Since the 1.3 release of <em>Commons Configuration</em> hierarchical
    101     * configurations support an <em>expression engine</em>. This expression engine
    102     * is responsible for evaluating the passed in configuration keys and map them
    103     * to the stored properties. The examples above are valid for the default
    104     * expression engine, which is used when a new <code>HierarchicalConfiguration</code>
    105     * instance is created. With the <code>setExpressionEngine()</code> method a
    106     * different expression engine can be set. For instance with
    107     * <code>{@link org.apache.commons.configuration.tree.xpath.XPathExpressionEngine}</code>
    108     * there is an expression engine available that supports configuration keys in
    109     * XPATH syntax.</p>
    110     * <p>In addition to the events common for all configuration classes hierarchical
    111     * configurations support some more events that correspond to some specific
    112     * methods and features:
    113     * <dl><dt><em>EVENT_ADD_NODES</em></dt><dd>The <code>addNodes()</code> method
    114     * was called; the event object contains the key, to which the nodes were added,
    115     * and a collection with the new nodes as value.</dd>
    116     * <dt><em>EVENT_CLEAR_TREE</em></dt><dd>The <code>clearTree()</code> method was
    117     * called; the event object stores the key of the removed sub tree.</dd>
    118     * <dt><em>EVENT_SUBNODE_CHANGED</em></dt><dd>A <code>SubnodeConfiguration</code>
    119     * that was created from this configuration has been changed. The value property
    120     * of the event object contains the original event object as it was sent by the
    121     * subnode configuration.</dd></dl></p>
    122     * <p><em>Note:</em>Configuration objects of this type can be read concurrently
    123     * by multiple threads. However if one of these threads modifies the object,
    124     * synchronization has to be performed manually.</p>
    125     *
    126     * @author Oliver Heger
    127     * @version $Id: HierarchicalConfiguration.java 1158122 2011-08-16 06:21:58Z oheger $
    128     */
    129    public class HierarchicalConfiguration extends AbstractConfiguration implements Serializable, Cloneable
    130    {
    131        /**
    132         * Constant for the clear tree event.
    133         * @since 1.3
    134         */
    135        public static final int EVENT_CLEAR_TREE = 10;
    136
    137        /**
    138         * Constant for the add nodes event.
    139         * @since 1.3
    140         */
    141        public static final int EVENT_ADD_NODES = 11;
    142
    143        /**
    144         * Constant for the subnode configuration modified event.
    145         * @since 1.5
    146         */
    147        public static final int EVENT_SUBNODE_CHANGED = 12;
    148
    149        /**
    150         * The serial version UID.
    151         */
    152        private static final long serialVersionUID = 3373812230395363192L;
    153
    154        /** Stores the default expression engine to be used for new objects.*/
    155        private static ExpressionEngine defaultExpressionEngine;
    156
    157        /** Stores the root node of this configuration. This field is required for
    158         * backwards compatibility only.
    159         */
    160        private Node root;
    161
    162        /** Stores the root configuration node.*/
    163        private ConfigurationNode rootNode;
    164
    165        /** Stores the expression engine for this instance.*/
    166        private transient ExpressionEngine expressionEngine;
    167
    168        /**
    169         * Creates a new instance of <code>HierarchicalConfiguration</code>.
    170         */
    171        public HierarchicalConfiguration()
    172        {
    173            setRootNode(new Node());
    174        }
    175
    176        /**
    177         * Creates a new instance of <code>HierarchicalConfiguration</code> and
    178         * copies all data contained in the specified configuration into the new
    179         * one.
    180         *
    181         * @param c the configuration that is to be copied (if <b>null</b>, this
    182         * constructor will behave like the standard constructor)
    183         * @since 1.4
    184         */
    185        public HierarchicalConfiguration(HierarchicalConfiguration c)
    186        {
    187            this();
    188            if (c != null)
    189            {
    190                CloneVisitor visitor = new CloneVisitor();
    191                c.getRootNode().visit(visitor);
    192                setRootNode(visitor.getClone());
    193            }
    194        }
    195
    196        /**
    197         * Returns the object to synchronize on a reload. This class is not
    198         * reloadable so this object isn't important
    199         *
    200         * @return the lock object
    201         */
    202        public Object getReloadLock()
    203        {
    204            return this;
    205        }
    206
    207        /**
    208         * Returns the root node of this hierarchical configuration. This method
    209         * exists for backwards compatibility only. New code should use the
    210         * <code>{@link #getRootNode()}</code> method instead, which operates on
    211         * the preferred data type <code>ConfigurationNode</code>.
    212         *
    213         * @return the root node
    214         */
    215        public Node getRoot()
    216        {
    217            if (root == null && rootNode != null)
    218            {
    219                // Dynamically create a snapshot of the root node
    220                return new Node(rootNode);
    221            }
    222
    223            return root;
    224        }
    225
    226        /**
    227         * Sets the root node of this hierarchical configuration. This method
    228         * exists for backwards compatibility only. New code should use the
    229         * <code>{@link #setRootNode(ConfigurationNode)}</code> method instead,
    230         * which operates on the preferred data type <code>ConfigurationNode</code>.
    231         *
    232         * @param node the root node
    233         */
    234        public void setRoot(Node node)
    235        {
    236            if (node == null)
    237            {
    238                throw new IllegalArgumentException("Root node must not be null!");
    239            }
    240            root = node;
    241            rootNode = null;
    242        }
    243
    244        /**
    245         * Returns the root node of this hierarchical configuration.
    246         *
    247         * @return the root node
    248         * @since 1.3
    249         */
    250        public ConfigurationNode getRootNode()
    251        {
    252            return (rootNode != null) ? rootNode : root;
    253        }
    254
    255        /**
    256         * Sets the root node of this hierarchical configuration.
    257         *
    258         * @param rootNode the root node
    259         * @since 1.3
    260         */
    261        public void setRootNode(ConfigurationNode rootNode)
    262        {
    263            if (rootNode == null)
    264            {
    265                throw new IllegalArgumentException("Root node must not be null!");
    266            }
    267            this.rootNode = rootNode;
    268
    269            // For backward compatibility also set the old root field.
    270            root = (rootNode instanceof Node) ? (Node) rootNode : null;
    271        }
    272
    273        /**
    274         * Returns the default expression engine.
    275         *
    276         * @return the default expression engine
    277         * @since 1.3
    278         */
    279        public static synchronized ExpressionEngine getDefaultExpressionEngine()
    280        {
    281            if (defaultExpressionEngine == null)
    282            {
    283                defaultExpressionEngine = new DefaultExpressionEngine();
    284            }
    285            return defaultExpressionEngine;
    286        }
    287
    288        /**
    289         * Sets the default expression engine. This expression engine will be used
    290         * if no specific engine was set for an instance. It is shared between all
    291         * hierarchical configuration instances. So modifying its properties will
    292         * impact all instances, for which no specific engine is set.
    293         *
    294         * @param engine the new default expression engine
    295         * @since 1.3
    296         */
    297        public static synchronized void setDefaultExpressionEngine(ExpressionEngine engine)
    298        {
    299            if (engine == null)
    300            {
    301                throw new IllegalArgumentException(
    302                        "Default expression engine must not be null!");
    303            }
    304            defaultExpressionEngine = engine;
    305        }
    306
    307        /**
    308         * Returns the expression engine used by this configuration. This method
    309         * will never return <b>null</b>; if no specific expression engine was set,
    310         * the default expression engine will be returned.
    311         *
    312         * @return the current expression engine
    313         * @since 1.3
    314         */
    315        public ExpressionEngine getExpressionEngine()
    316        {
    317            return (expressionEngine != null) ? expressionEngine
    318                    : getDefaultExpressionEngine();
    319        }
    320
    321        /**
    322         * Sets the expression engine to be used by this configuration. All property
    323         * keys this configuration has to deal with will be interpreted by this
    324         * engine.
    325         *
    326         * @param expressionEngine the new expression engine; can be <b>null</b>,
    327         * then the default expression engine will be used
    328         * @since 1.3
    329         */
    330        public void setExpressionEngine(ExpressionEngine expressionEngine)
    331        {
    332            this.expressionEngine = expressionEngine;
    333        }
    334
    335        /**
    336         * Fetches the specified property. This task is delegated to the associated
    337         * expression engine.
    338         *
    339         * @param key the key to be looked up
    340         * @return the found value
    341         */
    342        public Object getProperty(String key)
    343        {
    344            List nodes = fetchNodeList(key);
    345
    346            if (nodes.size() == 0)
    347            {
    348                return null;
    349            }
    350            else
    351            {
    352                List list = new ArrayList();
    353                for (Iterator it = nodes.iterator(); it.hasNext();)
    354                {
    355                    ConfigurationNode node = (ConfigurationNode) it.next();
    356                    if (node.getValue() != null)
    357                    {
    358                        list.add(node.getValue());
    359                    }
    360                }
    361
    362                if (list.size() < 1)
    363                {
    364                    return null;
    365                }
    366                else
    367                {
    368                    return (list.size() == 1) ? list.get(0) : list;
    369                }
    370            }
    371        }
    372
    373        /**
    374         * Adds the property with the specified key. This task will be delegated to
    375         * the associated <code>ExpressionEngine</code>, so the passed in key
    376         * must match the requirements of this implementation.
    377         *
    378         * @param key the key of the new property
    379         * @param obj the value of the new property
    380         */
    381        protected void addPropertyDirect(String key, Object obj)
    382        {
    383            NodeAddData data = getExpressionEngine().prepareAdd(getRootNode(), key);
    384            ConfigurationNode node = processNodeAddData(data);
    385            node.setValue(obj);
    386        }
    387
    388        /**
    389         * Adds a collection of nodes at the specified position of the configuration
    390         * tree. This method works similar to <code>addProperty()</code>, but
    391         * instead of a single property a whole collection of nodes can be added -
    392         * and thus complete configuration sub trees. E.g. with this method it is
    393         * possible to add parts of another <code>HierarchicalConfiguration</code>
    394         * object to this object. (However be aware that a
    395         * <code>ConfigurationNode</code> object can only belong to a single
    396         * configuration. So if nodes from one configuration are directly added to
    397         * another one using this method, the structure of the source configuration
    398         * will be broken. In this case you should clone the nodes to be added
    399         * before calling <code>addNodes()</code>.) If the passed in key refers to
    400         * an existing and unique node, the new nodes are added to this node.
    401         * Otherwise a new node will be created at the specified position in the
    402         * hierarchy.
    403         *
    404         * @param key the key where the nodes are to be added; can be <b>null </b>,
    405         * then they are added to the root node
    406         * @param nodes a collection with the <code>Node</code> objects to be
    407         * added
    408         */
    409        public void addNodes(String key, Collection nodes)
    410        {
    411            if (nodes == null || nodes.isEmpty())
    412            {
    413                return;
    414            }
    415
    416            fireEvent(EVENT_ADD_NODES, key, nodes, true);
    417            ConfigurationNode parent;
    418            List target = fetchNodeList(key);
    419            if (target.size() == 1)
    420            {
    421                // existing unique key
    422                parent = (ConfigurationNode) target.get(0);
    423            }
    424            else
    425            {
    426                // otherwise perform an add operation
    427                parent = processNodeAddData(getExpressionEngine().prepareAdd(
    428                        getRootNode(), key));
    429            }
    430
    431            if (parent.isAttribute())
    432            {
    433                throw new IllegalArgumentException(
    434                        "Cannot add nodes to an attribute node!");
    435            }
    436
    437            for (Iterator it = nodes.iterator(); it.hasNext();)
    438            {
    439                ConfigurationNode child = (ConfigurationNode) it.next();
    440                if (child.isAttribute())
    441                {
    442                    parent.addAttribute(child);
    443                }
    444                else
    445                {
    446                    parent.addChild(child);
    447                }
    448                clearReferences(child);
    449            }
    450            fireEvent(EVENT_ADD_NODES, key, nodes, false);
    451        }
    452
    453        /**
    454         * Checks if this configuration is empty. Empty means that there are no keys
    455         * with any values, though there can be some (empty) nodes.
    456         *
    457         * @return a flag if this configuration is empty
    458         */
    459        public boolean isEmpty()
    460        {
    461            return !nodeDefined(getRootNode());
    462        }
    463
    464        /**
    465         * Creates a new <code>Configuration</code> object containing all keys
    466         * that start with the specified prefix. This implementation will return a
    467         * <code>HierarchicalConfiguration</code> object so that the structure of
    468         * the keys will be saved. The nodes selected by the prefix (it is possible
    469         * that multiple nodes are selected) are mapped to the root node of the
    470         * returned configuration, i.e. their children and attributes will become
    471         * children and attributes of the new root node. However a value of the root
    472         * node is only set if exactly one of the selected nodes contain a value (if
    473         * multiple nodes have a value, there is simply no way to decide how these
    474         * values are merged together). Note that the returned
    475         * <code>Configuration</code> object is not connected to its source
    476         * configuration: updates on the source configuration are not reflected in
    477         * the subset and vice versa.
    478         *
    479         * @param prefix the prefix of the keys for the subset
    480         * @return a new configuration object representing the selected subset
    481         */
    482        public Configuration subset(String prefix)
    483        {
    484            Collection nodes = fetchNodeList(prefix);
    485            if (nodes.isEmpty())
    486            {
    487                return new HierarchicalConfiguration();
    488            }
    489
    490            final HierarchicalConfiguration parent = this;
    491            HierarchicalConfiguration result = new HierarchicalConfiguration()
    492            {
    493                // Override interpolate to always interpolate on the parent
    494                protected Object interpolate(Object value)
    495                {
    496                    return parent.interpolate(value);
    497                }
    498            };
    499            CloneVisitor visitor = new CloneVisitor();
    500
    501            // Initialize the new root node
    502            Object value = null;
    503            int valueCount = 0;
    504            for (Iterator it = nodes.iterator(); it.hasNext();)
    505            {
    506                ConfigurationNode nd = (ConfigurationNode) it.next();
    507                if (nd.getValue() != null)
    508                {
    509                    value = nd.getValue();
    510                    valueCount++;
    511                }
    512                nd.visit(visitor);
    513
    514                for (Iterator it2 = visitor.getClone().getChildren().iterator(); it2
    515                        .hasNext();)
    516                {
    517                    result.getRootNode().addChild((ConfigurationNode) it2.next());
    518                }
    519                for (Iterator it2 = visitor.getClone().getAttributes().iterator(); it2
    520                        .hasNext();)
    521                {
    522                    result.getRootNode().addAttribute(
    523                            (ConfigurationNode) it2.next());
    524                }
    525            }
    526
    527            // Determine the value of the new root
    528            if (valueCount == 1)
    529            {
    530                result.getRootNode().setValue(value);
    531            }
    532            return (result.isEmpty()) ? new HierarchicalConfiguration() : result;
    533        }
    534
    535        /**
    536         * <p>
    537         * Returns a hierarchical subnode configuration object that wraps the
    538         * configuration node specified by the given key. This method provides an
    539         * easy means of accessing sub trees of a hierarchical configuration. In the
    540         * returned configuration the sub tree can directly be accessed, it becomes
    541         * the root node of this configuration. Because of this the passed in key
    542         * must select exactly one configuration node; otherwise an
    543         * <code>IllegalArgumentException</code> will be thrown.
    544         * </p>
    545         * <p>
    546         * The difference between this method and the
    547         * <code>{@link #subset(String)}</code> method is that
    548         * <code>subset()</code> supports arbitrary subsets of configuration nodes
    549         * while <code>configurationAt()</code> only returns a single sub tree.
    550         * Please refer to the documentation of the
    551         * <code>SubnodeConfiguration</code> class to obtain further information
    552         * about subnode configurations and when they should be used.
    553         * </p>
    554         * <p>
    555         * With the <code>supportUpdate</code> flag the behavior of the returned
    556         * <code>SubnodeConfiguration</code> regarding updates of its parent
    557         * configuration can be determined. A subnode configuration operates on the
    558         * same nodes as its parent, so changes at one configuration are normally
    559         * directly visible for the other configuration. There are however changes
    560         * of the parent configuration, which are not recognized by the subnode
    561         * configuration per default. An example for this is a reload operation (for
    562         * file-based configurations): Here the complete node set of the parent
    563         * configuration is replaced, but the subnode configuration still references
    564         * the old nodes. If such changes should be detected by the subnode
    565         * configuration, the <code>supportUpdates</code> flag must be set to
    566         * <b>true</b>. This causes the subnode configuration to reevaluate the key
    567         * used for its creation each time it is accessed. This guarantees that the
    568         * subnode configuration always stays in sync with its key, even if the
    569         * parent configuration's data significantly changes. If such a change
    570         * makes the key invalid - because it now no longer points to exactly one
    571         * node -, the subnode configuration is not reconstructed, but keeps its
    572         * old data. It is then quasi detached from its parent.
    573         * </p>
    574         *
    575         * @param key the key that selects the sub tree
    576         * @param supportUpdates a flag whether the returned subnode configuration
    577         * should be able to handle updates of its parent
    578         * @return a hierarchical configuration that contains this sub tree
    579         * @see SubnodeConfiguration
    580         * @since 1.5
    581         */
    582        public SubnodeConfiguration configurationAt(String key,
    583                boolean supportUpdates)
    584        {
    585            List nodes = fetchNodeList(key);
    586            if (nodes.size() != 1)
    587            {
    588                throw new IllegalArgumentException(
    589                        "Passed in key must select exactly one node: " + key);
    590            }
    591            return supportUpdates ? createSubnodeConfiguration(
    592                    (ConfigurationNode) nodes.get(0), key)
    593                    : createSubnodeConfiguration((ConfigurationNode) nodes.get(0));
    594        }
    595
    596        /**
    597         * Returns a hierarchical subnode configuration for the node specified by
    598         * the given key. This is a short form for <code>configurationAt(key,
    599         * <b>false</b>)</code>.
    600         *
    601         * @param key the key that selects the sub tree
    602         * @return a hierarchical configuration that contains this sub tree
    603         * @see SubnodeConfiguration
    604         * @since 1.3
    605         */
    606        public SubnodeConfiguration configurationAt(String key)
    607        {
    608            return configurationAt(key, false);
    609        }
    610
    611        /**
    612         * Returns a list of sub configurations for all configuration nodes selected
    613         * by the given key. This method will evaluate the passed in key (using the
    614         * current <code>ExpressionEngine</code>) and then create a subnode
    615         * configuration for each returned node (like
    616         * <code>{@link #configurationAt(String)}</code>}). This is especially
    617         * useful when dealing with list-like structures. As an example consider the
    618         * configuration that contains data about database tables and their fields.
    619         * If you need access to all fields of a certain table, you can simply do
    620         *
    621         * <pre>
    622         * List fields = config.configurationsAt("tables.table(0).fields.field");
    623         * for(Iterator it = fields.iterator(); it.hasNext();)
    624         * {
    625         *     HierarchicalConfiguration sub = (HierarchicalConfiguration) it.next();
    626         *     // now the children and attributes of the field node can be
    627         *     // directly accessed
    628         *     String fieldName = sub.getString("name");
    629         *     String fieldType = sub.getString("type");
    630         *     ...
    631         * </pre>
    632         *
    633         * @param key the key for selecting the desired nodes
    634         * @return a list with hierarchical configuration objects; each
    635         * configuration represents one of the nodes selected by the passed in key
    636         * @since 1.3
    637         */
    638        public List configurationsAt(String key)
    639        {
    640            List nodes = fetchNodeList(key);
    641            List configs = new ArrayList(nodes.size());
    642            for (Iterator it = nodes.iterator(); it.hasNext();)
    643            {
    644                configs.add(createSubnodeConfiguration((ConfigurationNode) it.next()));
    645            }
    646            return configs;
    647        }
    648
    649        /**
    650         * Creates a subnode configuration for the specified node. This method is
    651         * called by <code>configurationAt()</code> and
    652         * <code>configurationsAt()</code>.
    653         *
    654         * @param node the node, for which a subnode configuration is to be created
    655         * @return the configuration for the given node
    656         * @since 1.3
    657         */
    658        protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node)
    659        {
    660            SubnodeConfiguration result = new SubnodeConfiguration(this, node);
    661            registerSubnodeConfiguration(result);
    662            return result;
    663        }
    664
    665        /**
    666         * Creates a new subnode configuration for the specified node and sets its
    667         * construction key. A subnode configuration created this way will be aware
    668         * of structural changes of its parent.
    669         *
    670         * @param node the node, for which a subnode configuration is to be created
    671         * @param subnodeKey the key used to construct the configuration
    672         * @return the configuration for the given node
    673         * @since 1.5
    674         */
    675        protected SubnodeConfiguration createSubnodeConfiguration(
    676                ConfigurationNode node, String subnodeKey)
    677        {
    678            SubnodeConfiguration result = createSubnodeConfiguration(node);
    679            result.setSubnodeKey(subnodeKey);
    680            return result;
    681        }
    682
    683        /**
    684         * This method is always called when a subnode configuration created from
    685         * this configuration has been modified. This implementation transforms the
    686         * received event into an event of type <code>EVENT_SUBNODE_CHANGED</code>
    687         * and notifies the registered listeners.
    688         *
    689         * @param event the event describing the change
    690         * @since 1.5
    691         */
    692        protected void subnodeConfigurationChanged(ConfigurationEvent event)
    693        {
    694            fireEvent(EVENT_SUBNODE_CHANGED, null, event, event.isBeforeUpdate());
    695        }
    696
    697        /**
    698         * Registers this instance at the given subnode configuration. This
    699         * implementation will register a change listener, so that modifications of
    700         * the subnode configuration can be tracked.
    701         *
    702         * @param config the subnode configuration
    703         * @since 1.5
    704         */
    705        void registerSubnodeConfiguration(SubnodeConfiguration config)
    706        {
    707            config.addConfigurationListener(new ConfigurationListener()
    708            {
    709                public void configurationChanged(ConfigurationEvent event)
    710                {
    711                    subnodeConfigurationChanged(event);
    712                }
    713            });
    714        }
    715
    716        /**
    717         * Checks if the specified key is contained in this configuration. Note that
    718         * for this configuration the term &quot;contained&quot; means that the key
    719         * has an associated value. If there is a node for this key that has no
    720         * value but children (either defined or undefined), this method will still
    721         * return <b>false </b>.
    722         *
    723         * @param key the key to be chekced
    724         * @return a flag if this key is contained in this configuration
    725         */
    726        public boolean containsKey(String key)
    727        {
    728            return getProperty(key) != null;
    729        }
    730
    731        /**
    732         * Sets the value of the specified property.
    733         *
    734         * @param key the key of the property to set
    735         * @param value the new value of this property
    736         */
    737        public void setProperty(String key, Object value)
    738        {
    739            fireEvent(EVENT_SET_PROPERTY, key, value, true);
    740
    741            // Update the existing nodes for this property
    742            Iterator itNodes = fetchNodeList(key).iterator();
    743            Iterator itValues;
    744            if (!isDelimiterParsingDisabled())
    745            {
    746                itValues = PropertyConverter.toIterator(value, getListDelimiter());
    747            }
    748            else
    749            {
    750                itValues = new SingletonIterator(value);
    751            }
    752
    753            while (itNodes.hasNext() && itValues.hasNext())
    754            {
    755                ((ConfigurationNode) itNodes.next()).setValue(itValues.next());
    756            }
    757
    758            // Add additional nodes if necessary
    759            while (itValues.hasNext())
    760            {
    761                addPropertyDirect(key, itValues.next());
    762            }
    763
    764            // Remove remaining nodes
    765            while (itNodes.hasNext())
    766            {
    767                clearNode((ConfigurationNode) itNodes.next());
    768            }
    769
    770            fireEvent(EVENT_SET_PROPERTY, key, value, false);
    771        }
    772
    773        /**
    774         * Clears this configuration. This is a more efficient implementation than
    775         * the one inherited from the base class. It directly removes all data from
    776         * the root node.
    777         */
    778        public void clear()
    779        {
    780            fireEvent(EVENT_CLEAR, null, null, true);
    781            getRootNode().removeAttributes();
    782            getRootNode().removeChildren();
    783            getRootNode().setValue(null);
    784            fireEvent(EVENT_CLEAR, null, null, false);
    785        }
    786
    787        /**
    788         * Removes all values of the property with the given name and of keys that
    789         * start with this name. So if there is a property with the key
    790         * &quot;foo&quot; and a property with the key &quot;foo.bar&quot;, a call
    791         * of <code>clearTree("foo")</code> would remove both properties.
    792         *
    793         * @param key the key of the property to be removed
    794         */
    795        public void clearTree(String key)
    796        {
    797            fireEvent(EVENT_CLEAR_TREE, key, null, true);
    798            List nodes = fetchNodeList(key);
    799
    800            for (Iterator it = nodes.iterator(); it.hasNext();)
    801            {
    802                removeNode((ConfigurationNode) it.next());
    803            }
    804            fireEvent(EVENT_CLEAR_TREE, key, nodes, false);
    805        }
    806
    807        /**
    808         * Removes the property with the given key. Properties with names that start
    809         * with the given key (i.e. properties below the specified key in the
    810         * hierarchy) won't be affected.
    811         *
    812         * @param key the key of the property to be removed
    813         */
    814        public void clearProperty(String key)
    815        {
    816            fireEvent(EVENT_CLEAR_PROPERTY, key, null, true);
    817            List nodes = fetchNodeList(key);
    818
    819            for (Iterator it = nodes.iterator(); it.hasNext();)
    820            {
    821                clearNode((ConfigurationNode) it.next());
    822            }
    823
    824            fireEvent(EVENT_CLEAR_PROPERTY, key, null, false);
    825        }
    826
    827        /**
    828         * Returns an iterator with all keys defined in this configuration.
    829         * Note that the keys returned by this method will not contain any
    830         * indices. This means that some structure will be lost.</p>
    831         *
    832         * @return an iterator with the defined keys in this configuration
    833         */
    834        public Iterator getKeys()
    835        {
    836            DefinedKeysVisitor visitor = new DefinedKeysVisitor();
    837            getRootNode().visit(visitor);
    838
    839            return visitor.getKeyList().iterator();
    840        }
    841
    842        /**
    843         * Returns an iterator with all keys defined in this configuration that
    844         * start with the given prefix. The returned keys will not contain any
    845         * indices. This implementation tries to locate a node whose key is the same
    846         * as the passed in prefix. Then the subtree of this node is traversed, and
    847         * the keys of all nodes encountered (including attributes) are added to the
    848         * result set.
    849         *
    850         * @param prefix the prefix of the keys to start with
    851         * @return an iterator with the found keys
    852         */
    853        public Iterator getKeys(String prefix)
    854        {
    855            DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
    856            if (containsKey(prefix))
    857            {
    858                // explicitly add the prefix
    859                visitor.getKeyList().add(prefix);
    860            }
    861
    862            List nodes = fetchNodeList(prefix);
    863
    864            for (Iterator itNodes = nodes.iterator(); itNodes.hasNext();)
    865            {
    866                ConfigurationNode node = (ConfigurationNode) itNodes.next();
    867                for (Iterator it = node.getChildren().iterator(); it.hasNext();)
    868                {
    869                    ((ConfigurationNode) it.next()).visit(visitor);
    870                }
    871                for (Iterator it = node.getAttributes().iterator(); it.hasNext();)
    872                {
    873                    ((ConfigurationNode) it.next()).visit(visitor);
    874                }
    875            }
    876
    877            return visitor.getKeyList().iterator();
    878        }
    879
    880        /**
    881         * Returns the maximum defined index for the given key. This is useful if
    882         * there are multiple values for this key. They can then be addressed
    883         * separately by specifying indices from 0 to the return value of this
    884         * method.
    885         *
    886         * @param key the key to be checked
    887         * @return the maximum defined index for this key
    888         */
    889        public int getMaxIndex(String key)
    890        {
    891            return fetchNodeList(key).size() - 1;
    892        }
    893
    894        /**
    895         * Creates a copy of this object. This new configuration object will contain
    896         * copies of all nodes in the same structure. Registered event listeners
    897         * won't be cloned; so they are not registered at the returned copy.
    898         *
    899         * @return the copy
    900         * @since 1.2
    901         */
    902        public Object clone()
    903        {
    904            try
    905            {
    906                HierarchicalConfiguration copy = (HierarchicalConfiguration) super
    907                        .clone();
    908
    909                // clone the nodes, too
    910                CloneVisitor v = new CloneVisitor();
    911                getRootNode().visit(v);
    912                copy.setRootNode(v.getClone());
    913
    914                return copy;
    915            }
    916            catch (CloneNotSupportedException cex)
    917            {
    918                // should not happen
    919                throw new ConfigurationRuntimeException(cex);
    920            }
    921        }
    922
    923        /**
    924         * Returns a configuration with the same content as this configuration, but
    925         * with all variables replaced by their actual values. This implementation
    926         * is specific for hierarchical configurations. It clones the current
    927         * configuration and runs a specialized visitor on the clone, which performs
    928         * interpolation on the single configuration nodes.
    929         *
    930         * @return a configuration with all variables interpolated
    931         * @since 1.5
    932         */
    933        public Configuration interpolatedConfiguration()
    934        {
    935            HierarchicalConfiguration c = (HierarchicalConfiguration) clone();
    936            c.getRootNode().visit(new ConfigurationNodeVisitorAdapter()
    937            {
    938                public void visitAfterChildren(ConfigurationNode node)
    939                {
    940                    node.setValue(interpolate(node.getValue()));
    941                }
    942            });
    943            return c;
    944        }
    945
    946        /**
    947         * Helper method for fetching a list of all nodes that are addressed by the
    948         * specified key.
    949         *
    950         * @param key the key
    951         * @return a list with all affected nodes (never <b>null </b>)
    952         */
    953        protected List fetchNodeList(String key)
    954        {
    955            return getExpressionEngine().query(getRootNode(), key);
    956        }
    957
    958        /**
    959         * Recursive helper method for fetching a property. This method processes
    960         * all facets of a configuration key, traverses the tree of properties and
    961         * fetches the the nodes of all matching properties.
    962         *
    963         * @param keyPart the configuration key iterator
    964         * @param node the actual node
    965         * @param nodes here the found nodes are stored
    966         * @deprecated Property keys are now evaluated by the expression engine
    967         * associated with the configuration; this method will no longer be called.
    968         * If you want to modify the way properties are looked up, consider
    969         * implementing you own <code>ExpressionEngine</code> implementation.
    970         */
    971        protected void findPropertyNodes(ConfigurationKey.KeyIterator keyPart,
    972                Node node, Collection nodes)
    973        {
    974        }
    975
    976        /**
    977         * Checks if the specified node is defined.
    978         *
    979         * @param node the node to be checked
    980         * @return a flag if this node is defined
    981         * @deprecated Use the method <code>{@link #nodeDefined(ConfigurationNode)}</code>
    982         * instead.
    983         */
    984        protected boolean nodeDefined(Node node)
    985        {
    986            return nodeDefined((ConfigurationNode) node);
    987        }
    988
    989        /**
    990         * Checks if the specified node is defined.
    991         *
    992         * @param node the node to be checked
    993         * @return a flag if this node is defined
    994         */
    995        protected boolean nodeDefined(ConfigurationNode node)
    996        {
    997            DefinedVisitor visitor = new DefinedVisitor();
    998            node.visit(visitor);
    999            return visitor.isDefined();
    1000        }
    1001
    1002        /**
    1003         * Removes the specified node from this configuration. This method ensures
    1004         * that parent nodes that become undefined by this operation are also
    1005         * removed.
    1006         *
    1007         * @param node the node to be removed
    1008         * @deprecated Use the method <code>{@link #removeNode(ConfigurationNode)}</code>
    1009         * instead.
    1010         */
    1011        protected void removeNode(Node node)
    1012        {
    1013            removeNode((ConfigurationNode) node);
    1014        }
    1015
    1016        /**
    1017         * Removes the specified node from this configuration. This method ensures
    1018         * that parent nodes that become undefined by this operation are also
    1019         * removed.
    1020         *
    1021         * @param node the node to be removed
    1022         */
    1023        protected void removeNode(ConfigurationNode node)
    1024        {
    1025            ConfigurationNode parent = node.getParentNode();
    1026            if (parent != null)
    1027            {
    1028                parent.removeChild(node);
    1029                if (!nodeDefined(parent))
    1030                {
    1031                    removeNode(parent);
    1032                }
    1033            }
    1034        }
    1035
    1036        /**
    1037         * Clears the value of the specified node. If the node becomes undefined by
    1038         * this operation, it is removed from the hierarchy.
    1039         *
    1040         * @param node the node to be cleared
    1041         * @deprecated Use the method <code>{@link #clearNode(ConfigurationNode)}</code>
    1042         * instead
    1043         */
    1044        protected void clearNode(Node node)
    1045        {
    1046            clearNode((ConfigurationNode) node);
    1047        }
    1048
    1049        /**
    1050         * Clears the value of the specified node. If the node becomes undefined by
    1051         * this operation, it is removed from the hierarchy.
    1052         *
    1053         * @param node the node to be cleared
    1054         */
    1055        protected void clearNode(ConfigurationNode node)
    1056        {
    1057            node.setValue(null);
    1058            if (!nodeDefined(node))
    1059            {
    1060                removeNode(node);
    1061            }
    1062        }
    1063
    1064        /**
    1065         * Returns a reference to the parent node of an add operation. Nodes for new
    1066         * properties can be added as children of this node. If the path for the
    1067         * specified key does not exist so far, it is created now.
    1068         *
    1069         * @param keyIt the iterator for the key of the new property
    1070         * @param startNode the node to start the search with
    1071         * @return the parent node for the add operation
    1072         * @deprecated Adding new properties is now to a major part delegated to the
    1073         * <code>ExpressionEngine</code> associated with this configuration instance.
    1074         * This method will no longer be called. Developers who want to modify the
    1075         * process of adding new properties should consider implementing their own
    1076         * expression engine.
    1077         */
    1078        protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt, Node startNode)
    1079        {
    1080            return null;
    1081        }
    1082
    1083        /**
    1084         * Finds the last existing node for an add operation. This method traverses
    1085         * the configuration tree along the specified key. The last existing node on
    1086         * this path is returned.
    1087         *
    1088         * @param keyIt the key iterator
    1089         * @param node the actual node
    1090         * @return the last existing node on the given path
    1091         * @deprecated Adding new properties is now to a major part delegated to the
    1092         * <code>ExpressionEngine</code> associated with this configuration instance.
    1093         * This method will no longer be called. Developers who want to modify the
    1094         * process of adding new properties should consider implementing their own
    1095         * expression engine.
    1096         */
    1097        protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt, Node node)
    1098        {
    1099            return null;
    1100        }
    1101
    1102        /**
    1103         * Creates the missing nodes for adding a new property. This method ensures
    1104         * that there are corresponding nodes for all components of the specified
    1105         * configuration key.
    1106         *
    1107         * @param keyIt the key iterator
    1108         * @param root the base node of the path to be created
    1109         * @return the last node of the path
    1110         * @deprecated Adding new properties is now to a major part delegated to the
    1111         * <code>ExpressionEngine</code> associated with this configuration instance.
    1112         * This method will no longer be called. Developers who want to modify the
    1113         * process of adding new properties should consider implementing their own
    1114         * expression engine.
    1115         */
    1116        protected Node createAddPath(ConfigurationKey.KeyIterator keyIt, Node root)
    1117        {
    1118            return null;
    1119        }
    1120
    1121        /**
    1122         * Creates a new <code>Node</code> object with the specified name. This
    1123         * method can be overloaded in derived classes if a specific node type is
    1124         * needed. This base implementation always returns a new object of the
    1125         * <code>Node</code> class.
    1126         *
    1127         * @param name the name of the new node
    1128         * @return the new node
    1129         */
    1130        protected Node createNode(String name)
    1131        {
    1132            return new Node(name);
    1133        }
    1134
    1135        /**
    1136         * Helper method for processing a node add data object obtained from the
    1137         * expression engine. This method will create all new nodes.
    1138         *
    1139         * @param data the data object
    1140         * @return the new node
    1141         * @since 1.3
    1142         */
    1143        private ConfigurationNode processNodeAddData(NodeAddData data)
    1144        {
    1145            ConfigurationNode node = data.getParent();
    1146
    1147            // Create missing nodes on the path
    1148            for (Iterator it = data.getPathNodes().iterator(); it.hasNext();)
    1149            {
    1150                ConfigurationNode child = createNode((String) it.next());
    1151                node.addChild(child);
    1152                node = child;
    1153            }
    1154
    1155            // Add new target node
    1156            ConfigurationNode child = createNode(data.getNewNodeName());
    1157            if (data.isAttribute())
    1158            {
    1159                node.addAttribute(child);
    1160            }
    1161            else
    1162            {
    1163                node.addChild(child);
    1164            }
    1165            return child;
    1166        }
    1167
    1168        /**
    1169         * Clears all reference fields in a node structure. A configuration node can
    1170         * store a so-called &quot;reference&quot;. The meaning of this data is
    1171         * determined by a concrete sub class. Typically such references are
    1172         * specific for a configuration instance. If this instance is cloned or
    1173         * copied, they must be cleared. This can be done using this method.
    1174         *
    1175         * @param node the root node of the node hierarchy, in which the references
    1176         * are to be cleared
    1177         * @since 1.4
    1178         */
    1179        protected static void clearReferences(ConfigurationNode node)
    1180        {
    1181            node.visit(new ConfigurationNodeVisitorAdapter()
    1182            {
    1183                public void visitBeforeChildren(ConfigurationNode node)
    1184                {
    1185                    node.setReference(null);
    1186                }
    1187            });
    1188        }
    1189
    1190        /**
    1191         * Transforms the specified object into a Node. This method treats view
    1192         * nodes in a special way. This is necessary because ViewNode does not
    1193         * extend HierarchicalConfiguration.Node; thus the API for the node visitor
    1194         * is slightly different. Therefore a view node is transformed into a
    1195         * special compatibility Node object.
    1196         *
    1197         * @param obj the original node object
    1198         * @return the node to be used
    1199         */
    1200        private static Node getNodeFor(Object obj)
    1201        {
    1202            Node nd;
    1203            if (obj instanceof ViewNode)
    1204            {
    1205                final ViewNode viewNode = (ViewNode) obj;
    1206                nd = new Node(viewNode)
    1207                {
    1208                    public void setReference(Object reference)
    1209                    {
    1210                        super.setReference(reference);
    1211                        // also set the reference at the original node
    1212                        viewNode.setReference(reference);
    1213                    }
    1214                };
    1215            }
    1216            else
    1217            {
    1218                nd = (Node) obj;
    1219            }
    1220            return nd;
    1221        }
    1222
    1223        /**
    1224         * A data class for storing (hierarchical) property information. A property
    1225         * can have a value and an arbitrary number of child properties. From
    1226         * version 1.3 on this class is only a thin wrapper over the
    1227         * <code>{@link org.apache.commons.configuration.tree.DefaultConfigurationNode DefaultconfigurationNode}</code>
    1228         * class that exists mainly for the purpose of backwards compatibility.
    1229         */
    1230        public static class Node extends DefaultConfigurationNode implements Serializable
    1231        {
    1232            /**
    1233             * The serial version UID.
    1234             */
    1235            private static final long serialVersionUID = -6357500633536941775L;
    1236
    1237            /**
    1238             * Creates a new instance of <code>Node</code>.
    1239             */
    1240            public Node()
    1241            {
    1242                super();
    1243            }
    1244
    1245            /**
    1246             * Creates a new instance of <code>Node</code> and sets the name.
    1247             *
    1248             * @param name the node's name
    1249             */
    1250            public Node(String name)
    1251            {
    1252                super(name);
    1253            }
    1254
    1255            /**
    1256             * Creates a new instance of <code>Node</code> and sets the name and the value.
    1257             *
    1258             * @param name the node's name
    1259             * @param value the value
    1260             */
    1261            public Node(String name, Object value)
    1262            {
    1263                super(name, value);
    1264            }
    1265
    1266            /**
    1267             * Creates a new instance of <code>Node</code> based on the given
    1268             * source node. All properties of the source node, including its
    1269             * children and attributes, will be copied.
    1270             *
    1271             * @param src the node to be copied
    1272             */
    1273            public Node(ConfigurationNode src)
    1274            {
    1275                this(src.getName(), src.getValue());
    1276                setReference(src.getReference());
    1277                for (Iterator it = src.getChildren().iterator(); it.hasNext();)
    1278                {
    1279                    ConfigurationNode nd = (ConfigurationNode) it.next();
    1280                    // Don't change the parent node
    1281                    ConfigurationNode parent = nd.getParentNode();
    1282                    addChild(nd);
    1283                    nd.setParentNode(parent);
    1284                }
    1285
    1286                for (Iterator it = src.getAttributes().iterator(); it.hasNext();)
    1287                {
    1288                    ConfigurationNode nd = (ConfigurationNode) it.next();
    1289                    // Don't change the parent node
    1290                    ConfigurationNode parent = nd.getParentNode();
    1291                    addAttribute(nd);
    1292                    nd.setParentNode(parent);
    1293                }
    1294            }
    1295
    1296            /**
    1297             * Returns the parent of this node.
    1298             *
    1299             * @return this node's parent (can be <b>null</b>)
    1300             */
    1301            public Node getParent()
    1302            {
    1303                return (Node) getParentNode();
    1304            }
    1305
    1306            /**
    1307             * Sets the parent of this node.
    1308             *
    1309             * @param node the parent node
    1310             */
    1311            public void setParent(Node node)
    1312            {
    1313                setParentNode(node);
    1314            }
    1315
    1316            /**
    1317             * Adds the given node to the children of this node.
    1318             *
    1319             * @param node the child to be added
    1320             */
    1321            public void addChild(Node node)
    1322            {
    1323                addChild((ConfigurationNode) node);
    1324            }
    1325
    1326            /**
    1327             * Returns a flag whether this node has child elements.
    1328             *
    1329             * @return <b>true</b> if there is a child node, <b>false</b> otherwise
    1330             */
    1331            public boolean hasChildren()
    1332            {
    1333                return getChildrenCount() > 0 || getAttributeCount() > 0;
    1334            }
    1335
    1336            /**
    1337             * Removes the specified child from this node.
    1338             *
    1339             * @param child the child node to be removed
    1340             * @return a flag if the child could be found
    1341             */
    1342            public boolean remove(Node child)
    1343            {
    1344                return child.isAttribute() ? removeAttribute(child) : removeChild(child);
    1345            }
    1346
    1347            /**
    1348             * Removes all children with the given name.
    1349             *
    1350             * @param name the name of the children to be removed
    1351             * @return a flag if children with this name existed
    1352             */
    1353            public boolean remove(String name)
    1354            {
    1355                boolean childrenRemoved = removeChild(name);
    1356                boolean attrsRemoved = removeAttribute(name);
    1357                return childrenRemoved || attrsRemoved;
    1358            }
    1359
    1360            /**
    1361             * A generic method for traversing this node and all of its children.
    1362             * This method sends the passed in visitor to this node and all of its
    1363             * children.
    1364             *
    1365             * @param visitor the visitor
    1366             * @param key here a configuration key with the name of the root node of
    1367             * the iteration can be passed; if this key is not <b>null </b>, the
    1368             * full pathes to the visited nodes are builded and passed to the
    1369             * visitor's <code>visit()</code> methods
    1370             */
    1371            public void visit(NodeVisitor visitor, ConfigurationKey key)
    1372            {
    1373                int length = 0;
    1374                if (key != null)
    1375                {
    1376                    length = key.length();
    1377                    if (getName() != null)
    1378                    {
    1379                        key
    1380                                .append(StringUtils
    1381                                        .replace(
    1382                                                isAttribute() ? ConfigurationKey
    1383                                                        .constructAttributeKey(getName())
    1384                                                        : getName(),
    1385                                                String
    1386                                                        .valueOf(ConfigurationKey.PROPERTY_DELIMITER),
    1387                                                ConfigurationKey.ESCAPED_DELIMITER));
    1388                    }
    1389                }
    1390
    1391                visitor.visitBeforeChildren(this, key);
    1392
    1393                for (Iterator it = getChildren().iterator(); it.hasNext()
    1394                        && !visitor.terminate();)
    1395                {
    1396                    Object obj = it.next();
    1397                    getNodeFor(obj).visit(visitor, key);
    1398                }
    1399                for (Iterator it = getAttributes().iterator(); it.hasNext()
    1400                        && !visitor.terminate();)
    1401                {
    1402                    Object obj = it.next();
    1403                    getNodeFor(obj).visit(visitor, key);
    1404                }
    1405
    1406                visitor.visitAfterChildren(this, key);
    1407                if (key != null)
    1408                {
    1409                    key.setLength(length);
    1410                }
    1411            }
    1412        }
    1413
    1414        /**
    1415         * <p>Definition of a visitor class for traversing a node and all of its
    1416         * children.</p><p>This class defines the interface of a visitor for
    1417         * <code>Node</code> objects and provides a default implementation. The
    1418         * method <code>visit()</code> of <code>Node</code> implements a generic
    1419         * iteration algorithm based on the <em>Visitor</em> pattern. By providing
    1420         * different implementations of visitors it is possible to collect different
    1421         * data during the iteration process.</p>
    1422         *
    1423         */
    1424        public static class NodeVisitor
    1425        {
    1426            /**
    1427             * Visits the specified node. This method is called during iteration for
    1428             * each node before its children have been visited.
    1429             *
    1430             * @param node the actual node
    1431             * @param key the key of this node (may be <b>null </b>)
    1432             */
    1433            public void visitBeforeChildren(Node node, ConfigurationKey key)
    1434            {
    1435            }
    1436
    1437            /**
    1438             * Visits the specified node after its children have been processed.
    1439             * This gives a visitor the opportunity of collecting additional data
    1440             * after the child nodes have been visited.
    1441             *
    1442             * @param node the node to be visited
    1443             * @param key the key of this node (may be <b>null </b>)
    1444             */
    1445            public void visitAfterChildren(Node node, ConfigurationKey key)
    1446            {
    1447            }
    1448
    1449            /**
    1450             * Returns a flag that indicates if iteration should be stopped. This
    1451             * method is called after each visited node. It can be useful for
    1452             * visitors that search a specific node. If this node is found, the
    1453             * whole process can be stopped. This base implementation always returns
    1454             * <b>false </b>.
    1455             *
    1456             * @return a flag if iteration should be stopped
    1457             */
    1458            public boolean terminate()
    1459            {
    1460                return false;
    1461            }
    1462        }
    1463
    1464        /**
    1465         * A specialized visitor that checks if a node is defined.
    1466         * &quot;Defined&quot; in this terms means that the node or at least one of
    1467         * its sub nodes is associated with a value.
    1468         *
    1469         */
    1470        static class DefinedVisitor extends ConfigurationNodeVisitorAdapter
    1471        {
    1472            /** Stores the defined flag. */
    1473            private boolean defined;
    1474
    1475            /**
    1476             * Checks if iteration should be stopped. This can be done if the first
    1477             * defined node is found.
    1478             *
    1479             * @return a flag if iteration should be stopped
    1480             */
    1481            public boolean terminate()
    1482            {
    1483                return isDefined();
    1484            }
    1485
    1486            /**
    1487             * Visits the node. Checks if a value is defined.
    1488             *
    1489             * @param node the actual node
    1490             */
    1491            public void visitBeforeChildren(ConfigurationNode node)
    1492            {
    1493                defined = node.getValue() != null;
    1494            }
    1495
    1496            /**
    1497             * Returns the defined flag.
    1498             *
    1499             * @return the defined flag
    1500             */
    1501            public boolean isDefined()
    1502            {
    1503                return defined;
    1504            }
    1505        }
    1506
    1507        /**
    1508         * A specialized visitor that fills a list with keys that are defined in a
    1509         * node hierarchy.
    1510         */
    1511        class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter
    1512        {
    1513            /** Stores the list to be filled. */
    1514            private Set keyList;
    1515
    1516            /** A stack with the keys of the already processed nodes. */
    1517            private Stack parentKeys;
    1518
    1519            /**
    1520             * Default constructor.
    1521             */
    1522            public DefinedKeysVisitor()
    1523            {
    1524                keyList = new ListOrderedSet();
    1525                parentKeys = new Stack();
    1526            }
    1527
    1528            /**
    1529             * Creates a new <code>DefinedKeysVisitor</code> instance and sets the
    1530             * prefix for the keys to fetch.
    1531             *
    1532             * @param prefix the prefix
    1533             */
    1534            public DefinedKeysVisitor(String prefix)
    1535            {
    1536                this();
    1537                parentKeys.push(prefix);
    1538            }
    1539
    1540            /**
    1541             * Returns the list with all defined keys.
    1542             *
    1543             * @return the list with the defined keys
    1544             */
    1545            public Set getKeyList()
    1546            {
    1547                return keyList;
    1548            }
    1549
    1550            /**
    1551             * Visits the node after its children has been processed. Removes this
    1552             * node's key from the stack.
    1553             *
    1554             * @param node the node
    1555             */
    1556            public void visitAfterChildren(ConfigurationNode node)
    1557            {
    1558                parentKeys.pop();
    1559            }
    1560
    1561            /**
    1562             * Visits the specified node. If this node has a value, its key is added
    1563             * to the internal list.
    1564             *
    1565             * @param node the node to be visited
    1566             */
    1567            public void visitBeforeChildren(ConfigurationNode node)
    1568            {
    1569                String parentKey = parentKeys.isEmpty() ? null
    1570                        : (String) parentKeys.peek();
    1571                String key = getExpressionEngine().nodeKey(node, parentKey);
    1572                parentKeys.push(key);
    1573                if (node.getValue() != null)
    1574                {
    1575                    keyList.add(key);
    1576                }
    1577            }
    1578        }
    1579
    1580        /**
    1581         * A specialized visitor that is able to create a deep copy of a node
    1582         * hierarchy.
    1583         */
    1584        static class CloneVisitor extends ConfigurationNodeVisitorAdapter
    1585        {
    1586            /** A stack with the actual object to be copied. */
    1587            private Stack copyStack;
    1588
    1589            /** Stores the result of the clone process. */
    1590            private ConfigurationNode result;
    1591
    1592            /**
    1593             * Creates a new instance of <code>CloneVisitor</code>.
    1594             */
    1595            public CloneVisitor()
    1596            {
    1597                copyStack = new Stack();
    1598            }
    1599
    1600            /**
    1601             * Visits the specified node after its children have been processed.
    1602             *
    1603             * @param node the node
    1604             */
    1605            public void visitAfterChildren(ConfigurationNode node)
    1606            {
    1607                ConfigurationNode copy = (ConfigurationNode) copyStack.pop();
    1608                if (copyStack.isEmpty())
    1609                {
    1610                    result = copy;
    1611                }
    1612            }
    1613
    1614            /**
    1615             * Visits and copies the specified node.
    1616             *
    1617             * @param node the node
    1618             */
    1619            public void visitBeforeChildren(ConfigurationNode node)
    1620            {
    1621                ConfigurationNode copy = (ConfigurationNode) node.clone();
    1622                copy.setParentNode(null);
    1623
    1624                if (!copyStack.isEmpty())
    1625                {
    1626                    if (node.isAttribute())
    1627                    {
    1628                        ((ConfigurationNode) copyStack.peek()).addAttribute(copy);
    1629                    }
    1630                    else
    1631                    {
    1632                        ((ConfigurationNode) copyStack.peek()).addChild(copy);
    1633                    }
    1634                }
    1635
    1636                copyStack.push(copy);
    1637            }
    1638
    1639            /**
    1640             * Returns the result of the clone process. This is the root node of the
    1641             * cloned node hierarchy.
    1642             *
    1643             * @return the cloned root node
    1644             */
    1645            public ConfigurationNode getClone()
    1646            {
    1647                return result;
    1648            }
    1649        }
    1650
    1651        /**
    1652         * A specialized visitor base class that can be used for storing the tree of
    1653         * configuration nodes. The basic idea is that each node can be associated
    1654         * with a reference object. This reference object has a concrete meaning in
    1655         * a derived class, e.g. an entry in a JNDI context or an XML element. When
    1656         * the configuration tree is set up, the <code>load()</code> method is
    1657         * responsible for setting the reference objects. When the configuration
    1658         * tree is later modified, new nodes do not have a defined reference object.
    1659         * This visitor class processes all nodes and finds the ones without a
    1660         * defined reference object. For those nodes the <code>insert()</code>
    1661         * method is called, which must be defined in concrete sub classes. This
    1662         * method can perform all steps to integrate the new node into the original
    1663         * structure.
    1664         *
    1665         */
    1666        protected abstract static class BuilderVisitor extends NodeVisitor
    1667        {
    1668            /**
    1669             * Visits the specified node before its children have been traversed.
    1670             *
    1671             * @param node the node to visit
    1672             * @param key the current key
    1673             */
    1674            public void visitBeforeChildren(Node node, ConfigurationKey key)
    1675            {
    1676                Collection subNodes = new LinkedList(node.getChildren());
    1677                subNodes.addAll(node.getAttributes());
    1678                Iterator children = subNodes.iterator();
    1679                Node sibling1 = null;
    1680                Node nd = null;
    1681
    1682                while (children.hasNext())
    1683                {
    1684                    // find the next new node
    1685                    do
    1686                    {
    1687                        sibling1 = nd;
    1688                        Object obj = children.next();
    1689                        nd = getNodeFor(obj);
    1690                    } while (nd.getReference() != null && children.hasNext());
    1691
    1692                    if (nd.getReference() == null)
    1693                    {
    1694                        // find all following new nodes
    1695                        List newNodes = new LinkedList();
    1696                        newNodes.add(nd);
    1697                        while (children.hasNext())
    1698                        {
    1699                            Object obj = children.next();
    1700                            nd = getNodeFor(obj);
    1701                            if (nd.getReference() == null)
    1702                            {
    1703                                newNodes.add(nd);
    1704                            }
    1705                            else
    1706                            {
    1707                                break;
    1708                            }
    1709                        }
    1710
    1711                        // Insert all new nodes
    1712                        Node sibling2 = (nd.getReference() == null) ? null : nd;
    1713                        for (Iterator it = newNodes.iterator(); it.hasNext();)
    1714                        {
    1715                            Node insertNode = (Node) it.next();
    1716                            if (insertNode.getReference() == null)
    1717                            {
    1718                                Object ref = insert(insertNode, node, sibling1, sibling2);
    1719                                if (ref != null)
    1720                                {
    1721                                    insertNode.setReference(ref);
    1722                                }
    1723                                sibling1 = insertNode;
    1724                            }
    1725                        }
    1726                    }
    1727                }
    1728            }
    1729
    1730            /**
    1731             * Inserts a new node into the structure constructed by this builder.
    1732             * This method is called for each node that has been added to the
    1733             * configuration tree after the configuration has been loaded from its
    1734             * source. These new nodes have to be inserted into the original
    1735             * structure. The passed in nodes define the position of the node to be
    1736             * inserted: its parent and the siblings between to insert. The return
    1737             * value is interpreted as the new reference of the affected
    1738             * <code>Node</code> object; if it is not <b>null </b>, it is passed
    1739             * to the node's <code>setReference()</code> method.
    1740             *
    1741             * @param newNode the node to be inserted
    1742             * @param parent the parent node
    1743             * @param sibling1 the sibling after which the node is to be inserted;
    1744             * can be <b>null </b> if the new node is going to be the first child
    1745             * node
    1746             * @param sibling2 the sibling before which the node is to be inserted;
    1747             * can be <b>null </b> if the new node is going to be the last child
    1748             * node
    1749             * @return the reference object for the node to be inserted
    1750             */
    1751            protected abstract Object insert(Node newNode, Node parent, Node sibling1, Node sibling2);
    1752        }
    1753    }