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