    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
    018package org.apache.commons.configuration2;
    019
    020import java.util.ArrayList;
    021import java.util.Collection;
    022import java.util.Collections;
    023import java.util.HashMap;
    024import java.util.Iterator;
    025import java.util.LinkedHashSet;
    026import java.util.LinkedList;
    027import java.util.List;
    028import java.util.Map;
    029import java.util.Set;
    030import java.util.Stack;
    031
    032import org.apache.commons.configuration2.event.ConfigurationEvent;
    033import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
    034import org.apache.commons.configuration2.sync.NoOpSynchronizer;
    035import org.apache.commons.configuration2.tree.ConfigurationNodeVisitorAdapter;
    036import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
    037import org.apache.commons.configuration2.tree.ExpressionEngine;
    038import org.apache.commons.configuration2.tree.NodeAddData;
    039import org.apache.commons.configuration2.tree.NodeHandler;
    040import org.apache.commons.configuration2.tree.NodeKeyResolver;
    041import org.apache.commons.configuration2.tree.NodeModel;
    042import org.apache.commons.configuration2.tree.NodeTreeWalker;
    043import org.apache.commons.configuration2.tree.NodeUpdateData;
    044import org.apache.commons.configuration2.tree.QueryResult;
    045
    046/**
    047 * <p>
    048 * A specialized configuration class that extends its base class by the ability
    049 * of keeping more structure in the stored properties.
    050 * </p>
    051 * <p>
    052 * There are some sources of configuration data that cannot be stored very well
    053 * in a {@code BaseConfiguration} object because then their structure is lost.
    054 * This is for instance true for XML documents. This class can deal with such
    055 * structured configuration sources by storing the properties in a tree-like
    056 * organization. The exact storage structure of the underlying data does not
    057 * matter for the configuration instance; it uses a {@link NodeModel} object for
    058 * accessing it.
    059 * </p>
    060 * <p>
    061 * The hierarchical organization allows for a more sophisticated access to
    062 * single properties. As an example consider the following XML document:
    063 * </p>
    064 *
    065 * <pre>
    066 * &lt;database&gt;
    067 *   &lt;tables&gt;
    068 *     &lt;table&gt;
    069 *       &lt;name&gt;users&lt;/name&gt;
    070 *       &lt;fields&gt;
    071 *         &lt;field&gt;
    072 *           &lt;name&gt;lid&lt;/name&gt;
    073 *           &lt;type&gt;long&lt;/name&gt;
    074 *         &lt;/field&gt;
    075 *         &lt;field&gt;
    076 *           &lt;name&gt;usrName&lt;/name&gt;
    077 *           &lt;type&gt;java.lang.String&lt;/type&gt;
    078 *         &lt;/field&gt;
    079 *        ...
    080 *       &lt;/fields&gt;
    081 *     &lt;/table&gt;
    082 *     &lt;table&gt;
    083 *       &lt;name&gt;documents&lt;/name&gt;
    084 *       &lt;fields&gt;
    085 *         &lt;field&gt;
    086 *           &lt;name&gt;docid&lt;/name&gt;
    087 *           &lt;type&gt;long&lt;/type&gt;
    088 *         &lt;/field&gt;
    089 *         ...
    090 *       &lt;/fields&gt;
    091 *     &lt;/table&gt;
    092 *     ...
    093 *   &lt;/tables&gt;
    094 * &lt;/database&gt;
    095 * </pre>
    096 *
    097 * <p>
    098 * If this document is parsed and stored in a hierarchical configuration object
    099 * (which can be done by one of the sub classes), there are enhanced
    100 * possibilities of accessing properties. Per default, the keys for querying
    101 * information can contain indices that select a specific element if there are
    102 * multiple hits.
    103 * </p>
    104 * <p>
    105 * For instance the key {@code tables.table(0).name} can be used to find out the
    106 * name of the first table. In opposite {@code tables.table.name} would return a
    107 * collection with the names of all available tables. Similarly the key
    108 * {@code tables.table(1).fields.field.name} returns a collection with the names
    109 * of all fields of the second table. If another index is added after the
    110 * {@code field} element, a single field can be accessed:
    111 * {@code tables.table(1).fields.field(0).name}.
    112 * </p>
    113 * <p>
    114 * There is a {@code getMaxIndex()} method that returns the maximum allowed
    115 * index that can be added to a given property key. This method can be used to
    116 * iterate over all values defined for a certain property.
    117 * </p>
    118 * <p>
    119 * Since the 1.3 release of <em>Commons Configuration</em> hierarchical
    120 * configurations support an <em>expression engine</em>. This expression engine
    121 * is responsible for evaluating the passed in configuration keys and map them
    122 * to the stored properties. The examples above are valid for the default
    123 * expression engine, which is used when a new
    124 * {@code AbstractHierarchicalConfiguration} instance is created. With the
    125 * {@code setExpressionEngine()} method a different expression engine can be
    126 * set. For instance with
    127 * {@link org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine}
    128 * there is an expression engine available that supports configuration keys in
    129 * XPATH syntax.
    130 * </p>
    131 * <p>
    132 * In addition to the events common for all configuration classes, hierarchical
    133 * configurations support some more events that correspond to some specific
    134 * methods and features. For those events specific event type constants in
    135 * {@code ConfigurationEvent} exist:
    136 * </p>
    137 * <dl>
    138 * <dt><em>ADD_NODES</em></dt>
    139 * <dd>The {@code addNodes()} method was called; the event object contains the
    140 * key, to which the nodes were added, and a collection with the new nodes as
    141 * value.</dd>
    142 * <dt><em>CLEAR_TREE</em></dt>
    143 * <dd>The {@code clearTree()} method was called; the event object stores the
    144 * key of the removed sub tree.</dd>
    145 * <dt><em>SUBNODE_CHANGED</em></dt>
    146 * <dd>A {@code SubnodeConfiguration} that was created from this configuration
    147 * has been changed. The value property of the event object contains the
    148 * original event object as it was sent by the subnode configuration.</dd>
    149 * </dl>
    150 * <p>
    151 * Whether an {@code AbstractHierarchicalConfiguration} object is thread-safe or
    152 * not depends on the underlying {@code NodeModel} and the
    153 * {@link org.apache.commons.configuration2.sync.Synchronizer Synchronizer}
    154 * it is associated with. Some {@code NodeModel} implementations are inherently
    155 * thread-safe; they do not require a special {@code Synchronizer}. (Per
    156 * default, a dummy {@code Synchronizer} is used which is not thread-safe!) The
    157 * methods for querying or updating configuration data invoke this
    158 * {@code Synchronizer} accordingly. When accessing the configuration's root
    159 * node directly, the client application is responsible for proper
    160 * synchronization. This is achieved by calling the methods
    161 * {@link #lock(org.apache.commons.configuration2.sync.LockMode) lock()},
    162 * and {@link #unlock(org.apache.commons.configuration2.sync.LockMode) unlock()} with a proper
    163 * {@link org.apache.commons.configuration2.sync.LockMode LockMode} argument.
    164 * In any case, it is recommended to not access the
    165 * root node directly, but to use corresponding methods for querying or updating
    166 * configuration data instead. Direct manipulations of a configuration's node
    167 * structure circumvent many internal mechanisms and thus can cause undesired
    168 * effects. For concrete subclasses dealing with specific node structures, this
    169 * situation may be different.
    170 * </p>
    171 *
    172 * @version $Id: AbstractHierarchicalConfiguration.java 1842194 2018-09-27 22:24:23Z ggregory $
    173 * @since 2.0
    174 * @param <T> the type of the nodes managed by this hierarchical configuration
    175 */
    176public abstract class AbstractHierarchicalConfiguration<T> extends AbstractConfiguration
    177    implements Cloneable, NodeKeyResolver<T>, HierarchicalConfiguration<T>
    178{
    179    /** The model for managing the data stored in this configuration. */
    180    private NodeModel<T> model;
    181
    182    /** Stores the expression engine for this instance.*/
    183    private ExpressionEngine expressionEngine;
    184
    185    /**
    186     * Creates a new instance of {@code AbstractHierarchicalConfiguration} and
    187     * sets the {@code NodeModel} to be used.
    188     *
    189     * @param nodeModel the {@code NodeModel}
    190     */
    191    protected AbstractHierarchicalConfiguration(final NodeModel<T> nodeModel)
    192    {
    193        model = nodeModel;
    194    }
    195
    196    /**
    197     * {@inheritDoc} This implementation handles synchronization and delegates
    198     * to {@code getRootElementNameInternal()}.
    199     */
    200    @Override
    201    public final String getRootElementName()
    202    {
    203        beginRead(false);
    204        try
    205        {
    206            return getRootElementNameInternal();
    207        }
    208        finally
    209        {
    210            endRead();
    211        }
    212    }
    213
    214    /**
    215     * Actually obtains the name of the root element. This method is called by
    216     * {@code getRootElementName()}. It just returns the name of the root node.
    217     * Subclasses that treat the root element name differently can override this
    218     * method.
    219     *
    220     * @return the name of this configuration's root element
    221     */
    222    protected String getRootElementNameInternal()
    223    {
    224        final NodeHandler<T> nodeHandler = getModel().getNodeHandler();
    225        return nodeHandler.nodeName(nodeHandler.getRootNode());
    226    }
    227
    228    /**
    229     * {@inheritDoc} This implementation returns the configuration's
    230     * {@code NodeModel}. It is guarded by the current {@code Synchronizer}.
    231     */
    232    @Override
    233    public NodeModel<T> getNodeModel()
    234    {
    235        beginRead(false);
    236        try
    237        {
    238            return getModel();
    239        }
    240        finally
    241        {
    242            endRead();
    243        }
    244    }
    245
    246    /**
    247     * Returns the expression engine used by this configuration. This method
    248     * will never return <b>null</b>; if no specific expression engine was set,
    249     * the default expression engine will be returned.
    250     *
    251     * @return the current expression engine
    252     * @since 1.3
    253     */
    254    @Override
    255    public ExpressionEngine getExpressionEngine()
    256    {
    257        return (expressionEngine != null) ? expressionEngine
    258                : DefaultExpressionEngine.INSTANCE;
    259    }
    260
    261    /**
    262     * Sets the expression engine to be used by this configuration. All property
    263     * keys this configuration has to deal with will be interpreted by this
    264     * engine.
    265     *
    266     * @param expressionEngine the new expression engine; can be <b>null</b>,
    267     * then the default expression engine will be used
    268     * @since 1.3
    269     */
    270    @Override
    271    public void setExpressionEngine(final ExpressionEngine expressionEngine)
    272    {
    273        this.expressionEngine = expressionEngine;
    274    }
    275
    276    /**
    277     * Fetches the specified property. This task is delegated to the associated
    278     * expression engine.
    279     *
    280     * @param key the key to be looked up
    281     * @return the found value
    282     */
    283    @Override
    284    protected Object getPropertyInternal(final String key)
    285    {
    286        final List<QueryResult<T>> results = fetchNodeList(key);
    287
    288        if (results.isEmpty())
    289        {
    290            return null;
    291        }
    292        final NodeHandler<T> handler = getModel().getNodeHandler();
    293        final List<Object> list = new ArrayList<>();
    294        for (final QueryResult<T> result : results)
    295        {
    296            final Object value = valueFromResult(result, handler);
    297            if (value != null)
    298            {
    299                list.add(value);
    300            }
    301        }
    302
    303        if (list.size() < 1)
    304        {
    305            return null;
    306        }
    307        return (list.size() == 1) ? list.get(0) : list;
    308    }
    309
    310    /**
    311     * Adds the property with the specified key. This task will be delegated to
    312     * the associated {@code ExpressionEngine}, so the passed in key
    313     * must match the requirements of this implementation.
    314     *
    315     * @param key the key of the new property
    316     * @param obj the value of the new property
    317     */
    318    @Override
    319    protected void addPropertyInternal(final String key, final Object obj)
    320    {
    321        addPropertyToModel(key, getListDelimiterHandler().parse(obj));
    322    }
    323
    324    /**
    325     * {@inheritDoc} This method is not called in the normal way (via
    326     * {@code addProperty()} for hierarchical configurations because all values
    327     * to be added for the property have to be passed to the model in a single
    328     * step. However, to allow derived classes to add an arbitrary value as an
    329     * object, a special implementation is provided here. The passed in object
    330     * is not parsed as a list, but passed directly as only value to the model.
    331     */
    332    @Override
    333    protected void addPropertyDirect(final String key, final Object value)
    334    {
    335        addPropertyToModel(key, Collections.singleton(value));
    336    }
    337
    338    /**
    339     * Helper method for executing an add property operation on the model.
    340     *
    341     * @param key the key of the new property
    342     * @param values the values to be added for this property
    343     */
    344    private void addPropertyToModel(final String key, final Iterable<?> values)
    345    {
    346        getModel().addProperty(key, values, this);
    347    }
    348
    349    /**
    350     * Adds a collection of nodes at the specified position of the configuration
    351     * tree. This method works similar to {@code addProperty()}, but
    352     * instead of a single property a whole collection of nodes can be added -
    353     * and thus complete configuration sub trees. E.g. with this method it is
    354     * possible to add parts of another {@code BaseHierarchicalConfiguration}
    355     * object to this object. If the passed in key refers to
    356     * an existing and unique node, the new nodes are added to this node.
    357     * Otherwise a new node will be created at the specified position in the
    358     * hierarchy. Implementation node: This method performs some book-keeping
    359     * and then delegates to {@code addNodesInternal()}.
    360     *
    361     * @param key the key where the nodes are to be added; can be <b>null</b>,
    362     * then they are added to the root node
    363     * @param nodes a collection with the {@code Node} objects to be
    364     * added
    365     */
    366    @Override
    367    public final void addNodes(final String key, final Collection<? extends T> nodes)
    368    {
    369        if (nodes == null || nodes.isEmpty())
    370        {
    371            return;
    372        }
    373
    374        beginWrite(false);
    375        try
    376        {
    377            fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, true);
    378            addNodesInternal(key, nodes);
    379            fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, false);
    380        }
    381        finally
    382        {
    383            endWrite();
    384        }
    385    }
    386
    387    /**
    388     * Actually adds a collection of new nodes to this configuration. This
    389     * method is called by {@code addNodes()}. It can be overridden by
    390     * subclasses that need to adapt this operation.
    391     *
    392     * @param key the key where the nodes are to be added; can be <b>null</b>,
    393     *        then they are added to the root node
    394     * @param nodes a collection with the {@code Node} objects to be added
    395     * @since 2.0
    396     */
    397    protected void addNodesInternal(final String key, final Collection<? extends T> nodes)
    398    {
    399        getModel().addNodes(key, nodes, this);
    400    }
    401
    402    /**
    403     * Checks if this configuration is empty. Empty means that there are no keys
    404     * with any values, though there can be some (empty) nodes.
    405     *
    406     * @return a flag if this configuration is empty
    407     */
    408    @Override
    409    protected boolean isEmptyInternal()
    410    {
    411        return !nodeDefined(getModel().getNodeHandler().getRootNode());
    412    }
    413
    414    /**
    415     * Checks if the specified key is contained in this configuration. Note that
    416     * for this configuration the term &quot;contained&quot; means that the key
    417     * has an associated value. If there is a node for this key that has no
    418     * value but children (either defined or undefined), this method will still
    419     * return <b>false </b>.
    420     *
    421     * @param key the key to be checked
    422     * @return a flag if this key is contained in this configuration
    423     */
    424    @Override
    425    protected boolean containsKeyInternal(final String key)
    426    {
    427        return getPropertyInternal(key) != null;
    428    }
    429
    430    /**
    431     * Sets the value of the specified property.
    432     *
    433     * @param key the key of the property to set
    434     * @param value the new value of this property
    435     */
    436    @Override
    437    protected void setPropertyInternal(final String key, final Object value)
    438    {
    439        getModel().setProperty(key, value, this);
    440    }
    441
    442    /**
    443     * {@inheritDoc} This implementation delegates to the expression engine.
    444     */
    445    @Override
    446    public List<QueryResult<T>> resolveKey(final T root, final String key,
    447            final NodeHandler<T> handler)
    448    {
    449        return getExpressionEngine().query(root, key, handler);
    450    }
    451
    452    /**
    453     * {@inheritDoc} This implementation delegates to {@code resolveKey()} and
    454     * then filters out attribute results.
    455     */
    456    @Override
    457    public List<T> resolveNodeKey(final T root, final String key, final NodeHandler<T> handler)
    458    {
    459        final List<QueryResult<T>> results = resolveKey(root, key, handler);
    460        final List<T> targetNodes = new LinkedList<>();
    461        for (final QueryResult<T> result : results)
    462        {
    463            if (!result.isAttributeResult())
    464            {
    465                targetNodes.add(result.getNode());
    466            }
    467        }
    468        return targetNodes;
    469    }
    470
    471    /**
    472     * {@inheritDoc} This implementation delegates to the expression engine.
    473     */
    474    @Override
    475    public NodeAddData<T> resolveAddKey(final T root, final String key,
    476            final NodeHandler<T> handler)
    477    {
    478        return getExpressionEngine().prepareAdd(root, key, handler);
    479    }
    480
    481    /**
    482     * {@inheritDoc} This implementation executes a query for the given key and
    483     * constructs a {@code NodeUpdateData} object based on the results. It
    484     * determines which nodes need to be changed and whether new ones need to be
    485     * added or existing ones need to be removed.
    486     */
    487    @Override
    488    public NodeUpdateData<T> resolveUpdateKey(final T root, final String key,
    489            final Object newValue, final NodeHandler<T> handler)
    490    {
    491        final Iterator<QueryResult<T>> itNodes = fetchNodeList(key).iterator();
    492        final Iterator<?> itValues = getListDelimiterHandler().parse(newValue).iterator();
    493        final Map<QueryResult<T>, Object> changedValues =
    494                new HashMap<>();
    495        Collection<Object> additionalValues = null;
    496        Collection<QueryResult<T>> removedItems = null;
    497
    498        while (itNodes.hasNext() && itValues.hasNext())
    499        {
    500            changedValues.put(itNodes.next(), itValues.next());
    501        }
    502
    503        // Add additional nodes if necessary
    504        if (itValues.hasNext())
    505        {
    506            additionalValues = new LinkedList<>();
    507            while (itValues.hasNext())
    508            {
    509                additionalValues.add(itValues.next());
    510            }
    511        }
    512
    513        // Remove remaining nodes
    514        if (itNodes.hasNext())
    515        {
    516            removedItems = new LinkedList<>();
    517            while (itNodes.hasNext())
    518            {
    519                removedItems.add(itNodes.next());
    520            }
    521        }
    522
    523        return new NodeUpdateData<>(changedValues, additionalValues,
    524                removedItems, key);
    525    }
    526
    527    /**
    528     * {@inheritDoc} This implementation uses the expression engine to generate a
    529     * canonical key for the passed in node. For this purpose, the path to the
    530     * root node has to be traversed. The cache is used to store and access keys
    531     * for nodes encountered on the path.
    532     */
    533    @Override
    534    public String nodeKey(final T node, final Map<T, String> cache, final NodeHandler<T> handler)
    535    {
    536        final List<T> path = new LinkedList<>();
    537        T currentNode = node;
    538        String key = cache.get(node);
    539        while (key == null && currentNode != null)
    540        {
    541            path.add(0, currentNode);
    542            currentNode = handler.getParent(currentNode);
    543            key = cache.get(currentNode);
    544        }
    545
    546        for (final T n : path)
    547        {
    548            final String currentKey = getExpressionEngine().canonicalKey(n, key, handler);
    549            cache.put(n, currentKey);
    550            key = currentKey;
    551        }
    552
    553        return key;
    554    }
    555
    556    /**
    557     * Clears this configuration. This is a more efficient implementation than
    558     * the one inherited from the base class. It delegates to the node model.
    559     */
    560    @Override
    561    protected void clearInternal()
    562    {
    563        getModel().clear(this);
    564    }
    565
    566    /**
    567     * Removes all values of the property with the given name and of keys that
    568     * start with this name. So if there is a property with the key
    569     * &quot;foo&quot; and a property with the key &quot;foo.bar&quot;, a call
    570     * of {@code clearTree("foo")} would remove both properties.
    571     *
    572     * @param key the key of the property to be removed
    573     */
    574    @Override
    575    public final void clearTree(final String key)
    576    {
    577        beginWrite(false);
    578        try
    579        {
    580            fireEvent(ConfigurationEvent.CLEAR_TREE, key, null, true);
    581            final Object nodes = clearTreeInternal(key);
    582            fireEvent(ConfigurationEvent.CLEAR_TREE, key, nodes, false);
    583        }
    584        finally
    585        {
    586            endWrite();
    587        }
    588    }
    589
    590    /**
    591     * Actually clears the tree of elements referenced by the given key. This
    592     * method is called by {@code clearTree()}. Subclasses that need to adapt
    593     * this operation can override this method. This base implementation
    594     * delegates to the node model.
    595     *
    596     * @param key the key of the property to be removed
    597     * @return an object with information about the nodes that have been removed
    598     *         (this is needed for firing a meaningful event of type
    599     *         CLEAR_TREE)
    600     * @since 2.0
    601     */
    602    protected Object clearTreeInternal(final String key)
    603    {
    604        return getModel().clearTree(key, this);
    605    }
    606
    607    /**
    608     * Removes the property with the given key. Properties with names that start
    609     * with the given key (i.e. properties below the specified key in the
    610     * hierarchy) won't be affected. This implementation delegates to the node+
    611     * model.
    612     *
    613     * @param key the key of the property to be removed
    614     */
    615    @Override
    616    protected void clearPropertyDirect(final String key)
    617    {
    618        getModel().clearProperty(key, this);
    619    }
    620
    621    /**
    622     * {@inheritDoc} This implementation is slightly more efficient than the
    623     * default implementation. It does not iterate over the key set, but
    624     * directly queries its size after it has been constructed. Note that
    625     * constructing the key set is still an O(n) operation.
    626     */
    627    @Override
    628    protected int sizeInternal()
    629    {
    630        return visitDefinedKeys().getKeyList().size();
    631    }
    632
    633    /**
    634     * Returns an iterator with all keys defined in this configuration.
    635     * Note that the keys returned by this method will not contain any
    636     * indices. This means that some structure will be lost.
    637     *
    638     * @return an iterator with the defined keys in this configuration
    639     */
    640    @Override
    641    protected Iterator<String> getKeysInternal()
    642    {
    643        return visitDefinedKeys().getKeyList().iterator();
    644    }
    645
    646    /**
    647     * Creates a {@code DefinedKeysVisitor} and visits all defined keys with it.
    648     *
    649     * @return the visitor after all keys have been visited
    650     */
    651    private DefinedKeysVisitor visitDefinedKeys()
    652    {
    653        final DefinedKeysVisitor visitor = new DefinedKeysVisitor();
    654        final NodeHandler<T> nodeHandler = getModel().getNodeHandler();
    655        NodeTreeWalker.INSTANCE.walkDFS(nodeHandler.getRootNode(), visitor,
    656                nodeHandler);
    657        return visitor;
    658    }
    659
    660    /**
    661     * Returns an iterator with all keys defined in this configuration that
    662     * start with the given prefix. The returned keys will not contain any
    663     * indices. This implementation tries to locate a node whose key is the same
    664     * as the passed in prefix. Then the subtree of this node is traversed, and
    665     * the keys of all nodes encountered (including attributes) are added to the
    666     * result set.
    667     *
    668     * @param prefix the prefix of the keys to start with
    669     * @return an iterator with the found keys
    670     */
    671    @Override
    672    protected Iterator<String> getKeysInternal(final String prefix)
    673    {
    674        final DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
    675        if (containsKey(prefix))
    676        {
    677            // explicitly add the prefix
    678            visitor.getKeyList().add(prefix);
    679        }
    680
    681        final List<QueryResult<T>> results = fetchNodeList(prefix);
    682        final NodeHandler<T> handler = getModel().getNodeHandler();
    683
    684        for (final QueryResult<T> result : results)
    685        {
    686            if (!result.isAttributeResult())
    687            {
    688                for (final T c : handler.getChildren(result.getNode()))
    689                {
    690                    NodeTreeWalker.INSTANCE.walkDFS(c, visitor, handler);
    691                }
    692                visitor.handleAttributeKeys(prefix, result.getNode(), handler);
    693            }
    694        }
    695
    696        return visitor.getKeyList().iterator();
    697    }
    698
    699    /**
    700     * Returns the maximum defined index for the given key. This is useful if
    701     * there are multiple values for this key. They can then be addressed
    702     * separately by specifying indices from 0 to the return value of this
    703     * method. If the passed in key is not contained in this configuration,
    704     * result is -1.
    705     *
    706     * @param key the key to be checked
    707     * @return the maximum defined index for this key
    708     */
    709    @Override
    710    public final int getMaxIndex(final String key)
    711    {
    712        beginRead(false);
    713        try
    714        {
    715            return getMaxIndexInternal(key);
    716        }
    717        finally
    718        {
    719            endRead();
    720        }
    721    }
    722
    723    /**
    724     * Actually retrieves the maximum defined index for the given key. This
    725     * method is called by {@code getMaxIndex()}. Subclasses that need to adapt
    726     * this operation have to override this method.
    727     *
    728     * @param key the key to be checked
    729     * @return the maximum defined index for this key
    730     * @since 2.0
    731     */
    732    protected int getMaxIndexInternal(final String key)
    733    {
    734        return fetchNodeList(key).size() - 1;
    735    }
    736
    737    /**
    738     * Creates a copy of this object. This new configuration object will contain
    739     * copies of all nodes in the same structure. Registered event listeners
    740     * won't be cloned; so they are not registered at the returned copy.
    741     *
    742     * @return the copy
    743     * @since 1.2
    744     */
    745    @Override
    746    public Object clone()
    747    {
    748        beginRead(false);
    749        try
    750        {
    751            @SuppressWarnings("unchecked") // clone returns the same type
    752            final
    753            AbstractHierarchicalConfiguration<T> copy =
    754                    (AbstractHierarchicalConfiguration<T>) super.clone();
    755            copy.setSynchronizer(NoOpSynchronizer.INSTANCE);
    756            copy.cloneInterpolator(this);
    757            copy.setSynchronizer(ConfigurationUtils.cloneSynchronizer(getSynchronizer()));
    758            copy.model = cloneNodeModel();
    759
    760            return copy;
    761        }
    762        catch (final CloneNotSupportedException cex)
    763        {
    764            // should not happen
    765            throw new ConfigurationRuntimeException(cex);
    766        }
    767        finally
    768        {
    769            endRead();
    770        }
    771    }
    772
    773    /**
    774     * Creates a clone of the node model. This method is called by
    775     * {@code clone()}.
    776     *
    777     * @return the clone of the {@code NodeModel}
    778     * @since 2.0
    779     */
    780    protected abstract NodeModel<T> cloneNodeModel();
    781
    782    /**
    783     * Helper method for resolving the specified key.
    784     *
    785     * @param key the key
    786     * @return a list with all results selected by this key
    787     */
    788    protected List<QueryResult<T>> fetchNodeList(final String key)
    789    {
    790        final NodeHandler<T> nodeHandler = getModel().getNodeHandler();
    791        return resolveKey(nodeHandler.getRootNode(), key, nodeHandler);
    792    }
    793
    794    /**
    795     * Checks if the specified node is defined.
    796     *
    797     * @param node the node to be checked
    798     * @return a flag if this node is defined
    799     */
    800    protected boolean nodeDefined(final T node)
    801    {
    802        final DefinedVisitor<T> visitor = new DefinedVisitor<>();
    803        NodeTreeWalker.INSTANCE.walkBFS(node, visitor, getModel().getNodeHandler());
    804        return visitor.isDefined();
    805    }
    806
    807    /**
    808     * Returns the {@code NodeModel} used by this configuration. This method is
    809     * intended for internal use only. Access to the model is granted without
    810     * any synchronization. This is in contrast to the &quot;official&quot;
    811     * {@code getNodeModel()} method which is guarded by the configuration's
    812     * {@code Synchronizer}.
    813     *
    814     * @return the node model
    815     */
    816    protected NodeModel<T> getModel()
    817    {
    818        return model;
    819    }
    820
    821    /**
    822     * Extracts the value from a query result.
    823     *
    824     * @param result the {@code QueryResult}
    825     * @param handler the {@code NodeHandler}
    826     * @return the value of this result (may be <b>null</b>)
    827     */
    828    private Object valueFromResult(final QueryResult<T> result, final NodeHandler<T> handler)
    829    {
    830        return result.isAttributeResult() ? result.getAttributeValue(handler)
    831                : handler.getValue(result.getNode());
    832    }
    833
    834    /**
    835     * A specialized visitor that checks if a node is defined.
    836     * &quot;Defined&quot; in this terms means that the node or at least one of
    837     * its sub nodes is associated with a value.
    838     *
    839     * @param <T> the type of the nodes managed by this hierarchical configuration
    840     */
    841    private static class DefinedVisitor<T> extends
    842            ConfigurationNodeVisitorAdapter<T>
    843    {
    844        /** Stores the defined flag. */
    845        private boolean defined;
    846
    847        /**
    848         * Checks if iteration should be stopped. This can be done if the first
    849         * defined node is found.
    850         *
    851         * @return a flag if iteration should be stopped
    852         */
    853        @Override
    854        public boolean terminate()
    855        {
    856            return isDefined();
    857        }
    858
    859        /**
    860         * Visits the node. Checks if a value is defined.
    861         *
    862         * @param node the actual node
    863         */
    864        @Override
    865        public void visitBeforeChildren(final T node, final NodeHandler<T> handler)
    866        {
    867            defined =
    868                    handler.getValue(node) != null
    869                            || !handler.getAttributes(node).isEmpty();
    870        }
    871
    872        /**
    873         * Returns the defined flag.
    874         *
    875         * @return the defined flag
    876         */
    877        public boolean isDefined()
    878        {
    879            return defined;
    880        }
    881    }
    882
    883    /**
    884     * A specialized visitor that fills a list with keys that are defined in a
    885     * node hierarchy.
    886     */
    887    private class DefinedKeysVisitor extends
    888            ConfigurationNodeVisitorAdapter<T>
    889    {
    890        /** Stores the list to be filled. */
    891        private final Set<String> keyList;
    892
    893        /** A stack with the keys of the already processed nodes. */
    894        private final Stack<String> parentKeys;
    895
    896        /**
    897         * Default constructor.
    898         */
    899        public DefinedKeysVisitor()
    900        {
    901            keyList = new LinkedHashSet<>();
    902            parentKeys = new Stack<>();
    903        }
    904
    905        /**
    906         * Creates a new {@code DefinedKeysVisitor} instance and sets the
    907         * prefix for the keys to fetch.
    908         *
    909         * @param prefix the prefix
    910         */
    911        public DefinedKeysVisitor(final String prefix)
    912        {
    913            this();
    914            parentKeys.push(prefix);
    915        }
    916
    917        /**
    918         * Returns the list with all defined keys.
    919         *
    920         * @return the list with the defined keys
    921         */
    922        public Set<String> getKeyList()
    923        {
    924            return keyList;
    925        }
    926
    927        /**
    928         * {@inheritDoc} This implementation removes this
    929         * node's key from the stack.
    930         */
    931        @Override
    932        public void visitAfterChildren(final T node, final NodeHandler<T> handler)
    933        {
    934            parentKeys.pop();
    935        }
    936
    937        /**
    938         * {@inheritDoc} If this node has a value, its key is added
    939         * to the internal list.
    940         */
    941        @Override
    942        public void visitBeforeChildren(final T node, final NodeHandler<T> handler)
    943        {
    944            final String parentKey = parentKeys.isEmpty() ? null
    945                    : parentKeys.peek();
    946            final String key = getExpressionEngine().nodeKey(node, parentKey, handler);
    947            parentKeys.push(key);
    948            if (handler.getValue(node) != null)
    949            {
    950                keyList.add(key);
    951            }
    952            handleAttributeKeys(key, node, handler);
    953        }
    954
    955        /**
    956         * Appends all attribute keys of the current node.
    957         *
    958         * @param parentKey the parent key
    959         * @param node the current node
    960         * @param handler the {@code NodeHandler}
    961         */
    962        public void handleAttributeKeys(final String parentKey, final T node,
    963                final NodeHandler<T> handler)
    964        {
    965            for (final String attr : handler.getAttributes(node))
    966            {
    967                keyList.add(getExpressionEngine().attributeKey(parentKey, attr));
    968            }
    969        }
    970    }
    971
    972    @Override
    973    public String toString()
    974    {
    975        return super.toString() + "(" + getRootElementNameInternal() + ")";
    976    }
    977}