/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.configuration2;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.apache.commons.configuration2.event.ConfigurationEvent;
import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
import org.apache.commons.configuration2.sync.NoOpSynchronizer;
import org.apache.commons.configuration2.tree.ConfigurationNodeVisitorAdapter;
import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
import org.apache.commons.configuration2.tree.ExpressionEngine;
import org.apache.commons.configuration2.tree.NodeAddData;
import org.apache.commons.configuration2.tree.NodeHandler;
import org.apache.commons.configuration2.tree.NodeKeyResolver;
import org.apache.commons.configuration2.tree.NodeModel;
import org.apache.commons.configuration2.tree.NodeTreeWalker;
import org.apache.commons.configuration2.tree.NodeUpdateData;
import org.apache.commons.configuration2.tree.QueryResult;

A specialized configuration class that extends its base class by the ability of keeping more structure in the stored properties.

There are some sources of configuration data that cannot be stored very well in a BaseConfiguration object because then their structure is lost. This is for instance true for XML documents. This class can deal with such structured configuration sources by storing the properties in a tree-like organization. The exact storage structure of the underlying data does not matter for the configuration instance; it uses a NodeModel object for accessing it.

The hierarchical organization allows for a more sophisticated access to single properties. As an example consider the following XML document:

<database>
  <tables>
    <table>
      <name>users</name>
      <fields>
        <field>
          <name>lid</name>
          <type>long</name>
        </field>
        <field>
          <name>usrName</name>
          <type>java.lang.String</type>
        </field>
       ...
      </fields>
    </table>
    <table>
      <name>documents</name>
      <fields>
        <field>
          <name>docid</name>
          <type>long</type>
        </field>
        ...
      </fields>
    </table>
    ...
  </tables>
</database>

If this document is parsed and stored in a hierarchical configuration object (which can be done by one of the sub classes), there are enhanced possibilities of accessing properties. Per default, the keys for querying information can contain indices that select a specific element if there are multiple hits.

For instance the key tables.table(0).name can be used to find out the name of the first table. In opposite tables.table.name would return a collection with the names of all available tables. Similarly the key tables.table(1).fields.field.name returns a collection with the names of all fields of the second table. If another index is added after the field element, a single field can be accessed: tables.table(1).fields.field(0).name.

There is a getMaxIndex() method that returns the maximum allowed index that can be added to a given property key. This method can be used to iterate over all values defined for a certain property.

Since the 1.3 release of Commons Configuration hierarchical configurations support an expression engine. This expression engine is responsible for evaluating the passed in configuration keys and map them to the stored properties. The examples above are valid for the default expression engine, which is used when a new AbstractHierarchicalConfiguration instance is created. With the setExpressionEngine() method a different expression engine can be set. For instance with XPathExpressionEngine there is an expression engine available that supports configuration keys in XPATH syntax.

In addition to the events common for all configuration classes, hierarchical configurations support some more events that correspond to some specific methods and features. For those events specific event type constants in ConfigurationEvent exist:

ADD_NODES
The addNodes() method was called; the event object contains the key, to which the nodes were added, and a collection with the new nodes as value.
CLEAR_TREE
The clearTree() method was called; the event object stores the key of the removed sub tree.
SUBNODE_CHANGED
A SubnodeConfiguration that was created from this configuration has been changed. The value property of the event object contains the original event object as it was sent by the subnode configuration.

Whether an AbstractHierarchicalConfiguration object is thread-safe or not depends on the underlying NodeModel and the Synchronizer it is associated with. Some NodeModel implementations are inherently thread-safe; they do not require a special Synchronizer. (Per default, a dummy Synchronizer is used which is not thread-safe!) The methods for querying or updating configuration data invoke this Synchronizer accordingly. When accessing the configuration's root node directly, the client application is responsible for proper synchronization. This is achieved by calling the methods lock(), and unlock() with a proper LockMode argument. In any case, it is recommended to not access the root node directly, but to use corresponding methods for querying or updating configuration data instead. Direct manipulations of a configuration's node structure circumvent many internal mechanisms and thus can cause undesired effects. For concrete subclasses dealing with specific node structures, this situation may be different.

Type parameters:
  • <T> – the type of the nodes managed by this hierarchical configuration
Since:2.0
/** * <p> * A specialized configuration class that extends its base class by the ability * of keeping more structure in the stored properties. * </p> * <p> * There are some sources of configuration data that cannot be stored very well * in a {@code BaseConfiguration} object because then their structure is lost. * This is for instance true for XML documents. This class can deal with such * structured configuration sources by storing the properties in a tree-like * organization. The exact storage structure of the underlying data does not * matter for the configuration instance; it uses a {@link NodeModel} object for * accessing it. * </p> * <p> * The hierarchical organization allows for a more sophisticated access to * single properties. As an example consider the following XML document: * </p> * * <pre> * &lt;database&gt; * &lt;tables&gt; * &lt;table&gt; * &lt;name&gt;users&lt;/name&gt; * &lt;fields&gt; * &lt;field&gt; * &lt;name&gt;lid&lt;/name&gt; * &lt;type&gt;long&lt;/name&gt; * &lt;/field&gt; * &lt;field&gt; * &lt;name&gt;usrName&lt;/name&gt; * &lt;type&gt;java.lang.String&lt;/type&gt; * &lt;/field&gt; * ... * &lt;/fields&gt; * &lt;/table&gt; * &lt;table&gt; * &lt;name&gt;documents&lt;/name&gt; * &lt;fields&gt; * &lt;field&gt; * &lt;name&gt;docid&lt;/name&gt; * &lt;type&gt;long&lt;/type&gt; * &lt;/field&gt; * ... * &lt;/fields&gt; * &lt;/table&gt; * ... * &lt;/tables&gt; * &lt;/database&gt; * </pre> * * <p> * If this document is parsed and stored in a hierarchical configuration object * (which can be done by one of the sub classes), there are enhanced * possibilities of accessing properties. Per default, the keys for querying * information can contain indices that select a specific element if there are * multiple hits. * </p> * <p> * For instance the key {@code tables.table(0).name} can be used to find out the * name of the first table. In opposite {@code tables.table.name} would return a * collection with the names of all available tables. Similarly the key * {@code tables.table(1).fields.field.name} returns a collection with the names * of all fields of the second table. If another index is added after the * {@code field} element, a single field can be accessed: * {@code tables.table(1).fields.field(0).name}. * </p> * <p> * There is a {@code getMaxIndex()} method that returns the maximum allowed * index that can be added to a given property key. This method can be used to * iterate over all values defined for a certain property. * </p> * <p> * Since the 1.3 release of <em>Commons Configuration</em> hierarchical * configurations support an <em>expression engine</em>. This expression engine * is responsible for evaluating the passed in configuration keys and map them * to the stored properties. The examples above are valid for the default * expression engine, which is used when a new * {@code AbstractHierarchicalConfiguration} instance is created. With the * {@code setExpressionEngine()} method a different expression engine can be * set. For instance with * {@link org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine} * there is an expression engine available that supports configuration keys in * XPATH syntax. * </p> * <p> * In addition to the events common for all configuration classes, hierarchical * configurations support some more events that correspond to some specific * methods and features. For those events specific event type constants in * {@code ConfigurationEvent} exist: * </p> * <dl> * <dt><em>ADD_NODES</em></dt> * <dd>The {@code addNodes()} method was called; the event object contains the * key, to which the nodes were added, and a collection with the new nodes as * value.</dd> * <dt><em>CLEAR_TREE</em></dt> * <dd>The {@code clearTree()} method was called; the event object stores the * key of the removed sub tree.</dd> * <dt><em>SUBNODE_CHANGED</em></dt> * <dd>A {@code SubnodeConfiguration} that was created from this configuration * has been changed. The value property of the event object contains the * original event object as it was sent by the subnode configuration.</dd> * </dl> * <p> * Whether an {@code AbstractHierarchicalConfiguration} object is thread-safe or * not depends on the underlying {@code NodeModel} and the * {@link org.apache.commons.configuration2.sync.Synchronizer Synchronizer} * it is associated with. Some {@code NodeModel} implementations are inherently * thread-safe; they do not require a special {@code Synchronizer}. (Per * default, a dummy {@code Synchronizer} is used which is not thread-safe!) The * methods for querying or updating configuration data invoke this * {@code Synchronizer} accordingly. When accessing the configuration's root * node directly, the client application is responsible for proper * synchronization. This is achieved by calling the methods * {@link #lock(org.apache.commons.configuration2.sync.LockMode) lock()}, * and {@link #unlock(org.apache.commons.configuration2.sync.LockMode) unlock()} with a proper * {@link org.apache.commons.configuration2.sync.LockMode LockMode} argument. * In any case, it is recommended to not access the * root node directly, but to use corresponding methods for querying or updating * configuration data instead. Direct manipulations of a configuration's node * structure circumvent many internal mechanisms and thus can cause undesired * effects. For concrete subclasses dealing with specific node structures, this * situation may be different. * </p> * * @since 2.0 * @param <T> the type of the nodes managed by this hierarchical configuration */
public abstract class AbstractHierarchicalConfiguration<T> extends AbstractConfiguration implements Cloneable, NodeKeyResolver<T>, HierarchicalConfiguration<T> {
The model for managing the data stored in this configuration.
/** The model for managing the data stored in this configuration. */
private NodeModel<T> model;
Stores the expression engine for this instance.
/** Stores the expression engine for this instance.*/
private ExpressionEngine expressionEngine;
Creates a new instance of AbstractHierarchicalConfiguration and sets the NodeModel to be used.
Params:
  • nodeModel – the NodeModel
/** * Creates a new instance of {@code AbstractHierarchicalConfiguration} and * sets the {@code NodeModel} to be used. * * @param nodeModel the {@code NodeModel} */
protected AbstractHierarchicalConfiguration(final NodeModel<T> nodeModel) { model = nodeModel; }
{@inheritDoc} This implementation handles synchronization and delegates to getRootElementNameInternal().
/** * {@inheritDoc} This implementation handles synchronization and delegates * to {@code getRootElementNameInternal()}. */
@Override public final String getRootElementName() { beginRead(false); try { return getRootElementNameInternal(); } finally { endRead(); } }
Actually obtains the name of the root element. This method is called by getRootElementName(). It just returns the name of the root node. Subclasses that treat the root element name differently can override this method.
Returns:the name of this configuration's root element
/** * Actually obtains the name of the root element. This method is called by * {@code getRootElementName()}. It just returns the name of the root node. * Subclasses that treat the root element name differently can override this * method. * * @return the name of this configuration's root element */
protected String getRootElementNameInternal() { final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); return nodeHandler.nodeName(nodeHandler.getRootNode()); }
{@inheritDoc} This implementation returns the configuration's NodeModel. It is guarded by the current Synchronizer.
/** * {@inheritDoc} This implementation returns the configuration's * {@code NodeModel}. It is guarded by the current {@code Synchronizer}. */
@Override public NodeModel<T> getNodeModel() { beginRead(false); try { return getModel(); } finally { endRead(); } }
Returns the expression engine used by this configuration. This method will never return null; if no specific expression engine was set, the default expression engine will be returned.
Returns:the current expression engine
Since:1.3
/** * Returns the expression engine used by this configuration. This method * will never return <b>null</b>; if no specific expression engine was set, * the default expression engine will be returned. * * @return the current expression engine * @since 1.3 */
@Override public ExpressionEngine getExpressionEngine() { return expressionEngine != null ? expressionEngine : DefaultExpressionEngine.INSTANCE; }
Sets the expression engine to be used by this configuration. All property keys this configuration has to deal with will be interpreted by this engine.
Params:
  • expressionEngine – the new expression engine; can be null, then the default expression engine will be used
Since:1.3
/** * Sets the expression engine to be used by this configuration. All property * keys this configuration has to deal with will be interpreted by this * engine. * * @param expressionEngine the new expression engine; can be <b>null</b>, * then the default expression engine will be used * @since 1.3 */
@Override public void setExpressionEngine(final ExpressionEngine expressionEngine) { this.expressionEngine = expressionEngine; }
Fetches the specified property. This task is delegated to the associated expression engine.
Params:
  • key – the key to be looked up
Returns:the found value
/** * Fetches the specified property. This task is delegated to the associated * expression engine. * * @param key the key to be looked up * @return the found value */
@Override protected Object getPropertyInternal(final String key) { final List<QueryResult<T>> results = fetchNodeList(key); if (results.isEmpty()) { return null; } final NodeHandler<T> handler = getModel().getNodeHandler(); final List<Object> list = new ArrayList<>(); for (final QueryResult<T> result : results) { final Object value = valueFromResult(result, handler); if (value != null) { list.add(value); } } if (list.size() < 1) { return null; } return list.size() == 1 ? list.get(0) : list; }
Adds the property with the specified key. This task will be delegated to the associated ExpressionEngine, so the passed in key must match the requirements of this implementation.
Params:
  • key – the key of the new property
  • obj – the value of the new property
/** * Adds the property with the specified key. This task will be delegated to * the associated {@code ExpressionEngine}, so the passed in key * must match the requirements of this implementation. * * @param key the key of the new property * @param obj the value of the new property */
@Override protected void addPropertyInternal(final String key, final Object obj) { addPropertyToModel(key, getListDelimiterHandler().parse(obj)); }
{@inheritDoc} This method is not called in the normal way (via addProperty() for hierarchical configurations because all values to be added for the property have to be passed to the model in a single step. However, to allow derived classes to add an arbitrary value as an object, a special implementation is provided here. The passed in object is not parsed as a list, but passed directly as only value to the model.
/** * {@inheritDoc} This method is not called in the normal way (via * {@code addProperty()} for hierarchical configurations because all values * to be added for the property have to be passed to the model in a single * step. However, to allow derived classes to add an arbitrary value as an * object, a special implementation is provided here. The passed in object * is not parsed as a list, but passed directly as only value to the model. */
@Override protected void addPropertyDirect(final String key, final Object value) { addPropertyToModel(key, Collections.singleton(value)); }
Helper method for executing an add property operation on the model.
Params:
  • key – the key of the new property
  • values – the values to be added for this property
/** * Helper method for executing an add property operation on the model. * * @param key the key of the new property * @param values the values to be added for this property */
private void addPropertyToModel(final String key, final Iterable<?> values) { getModel().addProperty(key, values, this); }
Adds a collection of nodes at the specified position of the configuration tree. This method works similar to addProperty(), but instead of a single property a whole collection of nodes can be added - and thus complete configuration sub trees. E.g. with this method it is possible to add parts of another BaseHierarchicalConfiguration object to this object. If the passed in key refers to an existing and unique node, the new nodes are added to this node. Otherwise a new node will be created at the specified position in the hierarchy. Implementation node: This method performs some book-keeping and then delegates to addNodesInternal().
Params:
  • key – the key where the nodes are to be added; can be null, then they are added to the root node
  • nodes – a collection with the Node objects to be added
/** * Adds a collection of nodes at the specified position of the configuration * tree. This method works similar to {@code addProperty()}, but * instead of a single property a whole collection of nodes can be added - * and thus complete configuration sub trees. E.g. with this method it is * possible to add parts of another {@code BaseHierarchicalConfiguration} * object to this object. If the passed in key refers to * an existing and unique node, the new nodes are added to this node. * Otherwise a new node will be created at the specified position in the * hierarchy. Implementation node: This method performs some book-keeping * and then delegates to {@code addNodesInternal()}. * * @param key the key where the nodes are to be added; can be <b>null</b>, * then they are added to the root node * @param nodes a collection with the {@code Node} objects to be * added */
@Override public final void addNodes(final String key, final Collection<? extends T> nodes) { if (nodes == null || nodes.isEmpty()) { return; } beginWrite(false); try { fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, true); addNodesInternal(key, nodes); fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, false); } finally { endWrite(); } }
Actually adds a collection of new nodes to this configuration. This method is called by addNodes(). It can be overridden by subclasses that need to adapt this operation.
Params:
  • key – the key where the nodes are to be added; can be null, then they are added to the root node
  • nodes – a collection with the Node objects to be added
Since:2.0
/** * Actually adds a collection of new nodes to this configuration. This * method is called by {@code addNodes()}. It can be overridden by * subclasses that need to adapt this operation. * * @param key the key where the nodes are to be added; can be <b>null</b>, * then they are added to the root node * @param nodes a collection with the {@code Node} objects to be added * @since 2.0 */
protected void addNodesInternal(final String key, final Collection<? extends T> nodes) { getModel().addNodes(key, nodes, this); }
Checks if this configuration is empty. Empty means that there are no keys with any values, though there can be some (empty) nodes.
Returns:a flag if this configuration is empty
/** * Checks if this configuration is empty. Empty means that there are no keys * with any values, though there can be some (empty) nodes. * * @return a flag if this configuration is empty */
@Override protected boolean isEmptyInternal() { return !nodeDefined(getModel().getNodeHandler().getRootNode()); }
Checks if the specified key is contained in this configuration. Note that for this configuration the term "contained" means that the key has an associated value. If there is a node for this key that has no value but children (either defined or undefined), this method will still return false .
Params:
  • key – the key to be checked
Returns:a flag if this key is contained in this configuration
/** * Checks if the specified key is contained in this configuration. Note that * for this configuration the term &quot;contained&quot; means that the key * has an associated value. If there is a node for this key that has no * value but children (either defined or undefined), this method will still * return <b>false </b>. * * @param key the key to be checked * @return a flag if this key is contained in this configuration */
@Override protected boolean containsKeyInternal(final String key) { return getPropertyInternal(key) != null; }
Sets the value of the specified property.
Params:
  • key – the key of the property to set
  • value – the new value of this property
/** * Sets the value of the specified property. * * @param key the key of the property to set * @param value the new value of this property */
@Override protected void setPropertyInternal(final String key, final Object value) { getModel().setProperty(key, value, this); }
{@inheritDoc} This implementation delegates to the expression engine.
/** * {@inheritDoc} This implementation delegates to the expression engine. */
@Override public List<QueryResult<T>> resolveKey(final T root, final String key, final NodeHandler<T> handler) { return getExpressionEngine().query(root, key, handler); }
{@inheritDoc} This implementation delegates to resolveKey() and then filters out attribute results.
/** * {@inheritDoc} This implementation delegates to {@code resolveKey()} and * then filters out attribute results. */
@Override public List<T> resolveNodeKey(final T root, final String key, final NodeHandler<T> handler) { final List<QueryResult<T>> results = resolveKey(root, key, handler); final List<T> targetNodes = new LinkedList<>(); for (final QueryResult<T> result : results) { if (!result.isAttributeResult()) { targetNodes.add(result.getNode()); } } return targetNodes; }
{@inheritDoc} This implementation delegates to the expression engine.
/** * {@inheritDoc} This implementation delegates to the expression engine. */
@Override public NodeAddData<T> resolveAddKey(final T root, final String key, final NodeHandler<T> handler) { return getExpressionEngine().prepareAdd(root, key, handler); }
{@inheritDoc} This implementation executes a query for the given key and constructs a NodeUpdateData object based on the results. It determines which nodes need to be changed and whether new ones need to be added or existing ones need to be removed.
/** * {@inheritDoc} This implementation executes a query for the given key and * constructs a {@code NodeUpdateData} object based on the results. It * determines which nodes need to be changed and whether new ones need to be * added or existing ones need to be removed. */
@Override public NodeUpdateData<T> resolveUpdateKey(final T root, final String key, final Object newValue, final NodeHandler<T> handler) { final Iterator<QueryResult<T>> itNodes = fetchNodeList(key).iterator(); final Iterator<?> itValues = getListDelimiterHandler().parse(newValue).iterator(); final Map<QueryResult<T>, Object> changedValues = new HashMap<>(); Collection<Object> additionalValues = null; Collection<QueryResult<T>> removedItems = null; while (itNodes.hasNext() && itValues.hasNext()) { changedValues.put(itNodes.next(), itValues.next()); } // Add additional nodes if necessary if (itValues.hasNext()) { additionalValues = new LinkedList<>(); while (itValues.hasNext()) { additionalValues.add(itValues.next()); } } // Remove remaining nodes if (itNodes.hasNext()) { removedItems = new LinkedList<>(); while (itNodes.hasNext()) { removedItems.add(itNodes.next()); } } return new NodeUpdateData<>(changedValues, additionalValues, removedItems, key); }
{@inheritDoc} This implementation uses the expression engine to generate a canonical key for the passed in node. For this purpose, the path to the root node has to be traversed. The cache is used to store and access keys for nodes encountered on the path.
/** * {@inheritDoc} This implementation uses the expression engine to generate a * canonical key for the passed in node. For this purpose, the path to the * root node has to be traversed. The cache is used to store and access keys * for nodes encountered on the path. */
@Override public String nodeKey(final T node, final Map<T, String> cache, final NodeHandler<T> handler) { final List<T> path = new LinkedList<>(); T currentNode = node; String key = cache.get(node); while (key == null && currentNode != null) { path.add(0, currentNode); currentNode = handler.getParent(currentNode); key = cache.get(currentNode); } for (final T n : path) { final String currentKey = getExpressionEngine().canonicalKey(n, key, handler); cache.put(n, currentKey); key = currentKey; } return key; }
Clears this configuration. This is a more efficient implementation than the one inherited from the base class. It delegates to the node model.
/** * Clears this configuration. This is a more efficient implementation than * the one inherited from the base class. It delegates to the node model. */
@Override protected void clearInternal() { getModel().clear(this); }
Removes all values of the property with the given name and of keys that start with this name. So if there is a property with the key "foo" and a property with the key "foo.bar", a call of clearTree("foo") would remove both properties.
Params:
  • key – the key of the property to be removed
/** * Removes all values of the property with the given name and of keys that * start with this name. So if there is a property with the key * &quot;foo&quot; and a property with the key &quot;foo.bar&quot;, a call * of {@code clearTree("foo")} would remove both properties. * * @param key the key of the property to be removed */
@Override public final void clearTree(final String key) { beginWrite(false); try { fireEvent(ConfigurationEvent.CLEAR_TREE, key, null, true); final Object nodes = clearTreeInternal(key); fireEvent(ConfigurationEvent.CLEAR_TREE, key, nodes, false); } finally { endWrite(); } }
Actually clears the tree of elements referenced by the given key. This method is called by clearTree(). Subclasses that need to adapt this operation can override this method. This base implementation delegates to the node model.
Params:
  • key – the key of the property to be removed
Returns:an object with information about the nodes that have been removed (this is needed for firing a meaningful event of type CLEAR_TREE)
Since:2.0
/** * Actually clears the tree of elements referenced by the given key. This * method is called by {@code clearTree()}. Subclasses that need to adapt * this operation can override this method. This base implementation * delegates to the node model. * * @param key the key of the property to be removed * @return an object with information about the nodes that have been removed * (this is needed for firing a meaningful event of type * CLEAR_TREE) * @since 2.0 */
protected Object clearTreeInternal(final String key) { return getModel().clearTree(key, this); }
Removes the property with the given key. Properties with names that start with the given key (i.e. properties below the specified key in the hierarchy) won't be affected. This implementation delegates to the node+ model.
Params:
  • key – the key of the property to be removed
/** * Removes the property with the given key. Properties with names that start * with the given key (i.e. properties below the specified key in the * hierarchy) won't be affected. This implementation delegates to the node+ * model. * * @param key the key of the property to be removed */
@Override protected void clearPropertyDirect(final String key) { getModel().clearProperty(key, this); }
{@inheritDoc} This implementation is slightly more efficient than the default implementation. It does not iterate over the key set, but directly queries its size after it has been constructed. Note that constructing the key set is still an O(n) operation.
/** * {@inheritDoc} This implementation is slightly more efficient than the * default implementation. It does not iterate over the key set, but * directly queries its size after it has been constructed. Note that * constructing the key set is still an O(n) operation. */
@Override protected int sizeInternal() { return visitDefinedKeys().getKeyList().size(); }
Returns an iterator with all keys defined in this configuration. Note that the keys returned by this method will not contain any indices. This means that some structure will be lost.
Returns:an iterator with the defined keys in this configuration
/** * Returns an iterator with all keys defined in this configuration. * Note that the keys returned by this method will not contain any * indices. This means that some structure will be lost. * * @return an iterator with the defined keys in this configuration */
@Override protected Iterator<String> getKeysInternal() { return visitDefinedKeys().getKeyList().iterator(); }
Creates a DefinedKeysVisitor and visits all defined keys with it.
Returns:the visitor after all keys have been visited
/** * Creates a {@code DefinedKeysVisitor} and visits all defined keys with it. * * @return the visitor after all keys have been visited */
private DefinedKeysVisitor visitDefinedKeys() { final DefinedKeysVisitor visitor = new DefinedKeysVisitor(); final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); NodeTreeWalker.INSTANCE.walkDFS(nodeHandler.getRootNode(), visitor, nodeHandler); return visitor; }
Returns an iterator with all keys defined in this configuration that start with the given prefix. The returned keys will not contain any indices. This implementation tries to locate a node whose key is the same as the passed in prefix. Then the subtree of this node is traversed, and the keys of all nodes encountered (including attributes) are added to the result set.
Params:
  • prefix – the prefix of the keys to start with
Returns:an iterator with the found keys
/** * Returns an iterator with all keys defined in this configuration that * start with the given prefix. The returned keys will not contain any * indices. This implementation tries to locate a node whose key is the same * as the passed in prefix. Then the subtree of this node is traversed, and * the keys of all nodes encountered (including attributes) are added to the * result set. * * @param prefix the prefix of the keys to start with * @return an iterator with the found keys */
@Override protected Iterator<String> getKeysInternal(final String prefix) { final DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix); if (containsKey(prefix)) { // explicitly add the prefix visitor.getKeyList().add(prefix); } final List<QueryResult<T>> results = fetchNodeList(prefix); final NodeHandler<T> handler = getModel().getNodeHandler(); for (final QueryResult<T> result : results) { if (!result.isAttributeResult()) { for (final T c : handler.getChildren(result.getNode())) { NodeTreeWalker.INSTANCE.walkDFS(c, visitor, handler); } visitor.handleAttributeKeys(prefix, result.getNode(), handler); } } return visitor.getKeyList().iterator(); }
Returns the maximum defined index for the given key. This is useful if there are multiple values for this key. They can then be addressed separately by specifying indices from 0 to the return value of this method. If the passed in key is not contained in this configuration, result is -1.
Params:
  • key – the key to be checked
Returns:the maximum defined index for this key
/** * Returns the maximum defined index for the given key. This is useful if * there are multiple values for this key. They can then be addressed * separately by specifying indices from 0 to the return value of this * method. If the passed in key is not contained in this configuration, * result is -1. * * @param key the key to be checked * @return the maximum defined index for this key */
@Override public final int getMaxIndex(final String key) { beginRead(false); try { return getMaxIndexInternal(key); } finally { endRead(); } }
Actually retrieves the maximum defined index for the given key. This method is called by getMaxIndex(). Subclasses that need to adapt this operation have to override this method.
Params:
  • key – the key to be checked
Returns:the maximum defined index for this key
Since:2.0
/** * Actually retrieves the maximum defined index for the given key. This * method is called by {@code getMaxIndex()}. Subclasses that need to adapt * this operation have to override this method. * * @param key the key to be checked * @return the maximum defined index for this key * @since 2.0 */
protected int getMaxIndexInternal(final String key) { return fetchNodeList(key).size() - 1; }
Creates a copy of this object. This new configuration object will contain copies of all nodes in the same structure. Registered event listeners won't be cloned; so they are not registered at the returned copy.
Returns:the copy
Since:1.2
/** * Creates a copy of this object. This new configuration object will contain * copies of all nodes in the same structure. Registered event listeners * won't be cloned; so they are not registered at the returned copy. * * @return the copy * @since 1.2 */
@Override public Object clone() { beginRead(false); try { @SuppressWarnings("unchecked") // clone returns the same type final AbstractHierarchicalConfiguration<T> copy = (AbstractHierarchicalConfiguration<T>) super.clone(); copy.setSynchronizer(NoOpSynchronizer.INSTANCE); copy.cloneInterpolator(this); copy.setSynchronizer(ConfigurationUtils.cloneSynchronizer(getSynchronizer())); copy.model = cloneNodeModel(); return copy; } catch (final CloneNotSupportedException cex) { // should not happen throw new ConfigurationRuntimeException(cex); } finally { endRead(); } }
Creates a clone of the node model. This method is called by clone().
Returns:the clone of the NodeModel
Since:2.0
/** * Creates a clone of the node model. This method is called by * {@code clone()}. * * @return the clone of the {@code NodeModel} * @since 2.0 */
protected abstract NodeModel<T> cloneNodeModel();
Helper method for resolving the specified key.
Params:
  • key – the key
Returns:a list with all results selected by this key
/** * Helper method for resolving the specified key. * * @param key the key * @return a list with all results selected by this key */
protected List<QueryResult<T>> fetchNodeList(final String key) { final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); return resolveKey(nodeHandler.getRootNode(), key, nodeHandler); }
Checks if the specified node is defined.
Params:
  • node – the node to be checked
Returns:a flag if this node is defined
/** * Checks if the specified node is defined. * * @param node the node to be checked * @return a flag if this node is defined */
protected boolean nodeDefined(final T node) { final DefinedVisitor<T> visitor = new DefinedVisitor<>(); NodeTreeWalker.INSTANCE.walkBFS(node, visitor, getModel().getNodeHandler()); return visitor.isDefined(); }
Returns the NodeModel used by this configuration. This method is intended for internal use only. Access to the model is granted without any synchronization. This is in contrast to the "official" getNodeModel() method which is guarded by the configuration's Synchronizer.
Returns:the node model
/** * Returns the {@code NodeModel} used by this configuration. This method is * intended for internal use only. Access to the model is granted without * any synchronization. This is in contrast to the &quot;official&quot; * {@code getNodeModel()} method which is guarded by the configuration's * {@code Synchronizer}. * * @return the node model */
protected NodeModel<T> getModel() { return model; }
Extracts the value from a query result.
Params:
  • result – the QueryResult
  • handler – the NodeHandler
Returns:the value of this result (may be null)
/** * Extracts the value from a query result. * * @param result the {@code QueryResult} * @param handler the {@code NodeHandler} * @return the value of this result (may be <b>null</b>) */
private Object valueFromResult(final QueryResult<T> result, final NodeHandler<T> handler) { return result.isAttributeResult() ? result.getAttributeValue(handler) : handler.getValue(result.getNode()); }
A specialized visitor that checks if a node is defined. "Defined" in this terms means that the node or at least one of its sub nodes is associated with a value.
Type parameters:
  • <T> – the type of the nodes managed by this hierarchical configuration
/** * A specialized visitor that checks if a node is defined. * &quot;Defined&quot; in this terms means that the node or at least one of * its sub nodes is associated with a value. * * @param <T> the type of the nodes managed by this hierarchical configuration */
private static class DefinedVisitor<T> extends ConfigurationNodeVisitorAdapter<T> {
Stores the defined flag.
/** Stores the defined flag. */
private boolean defined;
Checks if iteration should be stopped. This can be done if the first defined node is found.
Returns:a flag if iteration should be stopped
/** * Checks if iteration should be stopped. This can be done if the first * defined node is found. * * @return a flag if iteration should be stopped */
@Override public boolean terminate() { return isDefined(); }
Visits the node. Checks if a value is defined.
Params:
  • node – the actual node
/** * Visits the node. Checks if a value is defined. * * @param node the actual node */
@Override public void visitBeforeChildren(final T node, final NodeHandler<T> handler) { defined = handler.getValue(node) != null || !handler.getAttributes(node).isEmpty(); }
Returns the defined flag.
Returns:the defined flag
/** * Returns the defined flag. * * @return the defined flag */
public boolean isDefined() { return defined; } }
A specialized visitor that fills a list with keys that are defined in a node hierarchy.
/** * A specialized visitor that fills a list with keys that are defined in a * node hierarchy. */
private class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter<T> {
Stores the list to be filled.
/** Stores the list to be filled. */
private final Set<String> keyList;
A stack with the keys of the already processed nodes.
/** A stack with the keys of the already processed nodes. */
private final Stack<String> parentKeys;
Default constructor.
/** * Default constructor. */
public DefinedKeysVisitor() { keyList = new LinkedHashSet<>(); parentKeys = new Stack<>(); }
Creates a new DefinedKeysVisitor instance and sets the prefix for the keys to fetch.
Params:
  • prefix – the prefix
/** * Creates a new {@code DefinedKeysVisitor} instance and sets the * prefix for the keys to fetch. * * @param prefix the prefix */
public DefinedKeysVisitor(final String prefix) { this(); parentKeys.push(prefix); }
Returns the list with all defined keys.
Returns:the list with the defined keys
/** * Returns the list with all defined keys. * * @return the list with the defined keys */
public Set<String> getKeyList() { return keyList; }
{@inheritDoc} This implementation removes this node's key from the stack.
/** * {@inheritDoc} This implementation removes this * node's key from the stack. */
@Override public void visitAfterChildren(final T node, final NodeHandler<T> handler) { parentKeys.pop(); }
{@inheritDoc} If this node has a value, its key is added to the internal list.
/** * {@inheritDoc} If this node has a value, its key is added * to the internal list. */
@Override public void visitBeforeChildren(final T node, final NodeHandler<T> handler) { final String parentKey = parentKeys.isEmpty() ? null : parentKeys.peek(); final String key = getExpressionEngine().nodeKey(node, parentKey, handler); parentKeys.push(key); if (handler.getValue(node) != null) { keyList.add(key); } handleAttributeKeys(key, node, handler); }
Appends all attribute keys of the current node.
Params:
  • parentKey – the parent key
  • node – the current node
  • handler – the NodeHandler
/** * Appends all attribute keys of the current node. * * @param parentKey the parent key * @param node the current node * @param handler the {@code NodeHandler} */
public void handleAttributeKeys(final String parentKey, final T node, final NodeHandler<T> handler) { for (final String attr : handler.getAttributes(node)) { keyList.add(getExpressionEngine().attributeKey(parentKey, attr)); } } } @Override public String toString() { return super.toString() + "(" + getRootElementNameInternal() + ")"; } }