org.apache.commons.configuration2.AbstractHierarchicalConfiguration Maven / Gradle / Ivy
Show all versions of commons-configuration2 Show documentation
/*
* 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.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.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.stream.Collectors;
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 {@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.
*
*
* 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 {@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}.
*
*
* 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.
*
*
* 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
* {@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.
*
*
* 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:
*
*
* - ADD_NODES
* - 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.
* - CLEAR_TREE
* - The {@code clearTree()} method was called; the event object stores the key of the removed sub tree.
* - SUBNODE_CHANGED
* - 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.
*
*
* 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.
*
*
* @since 2.0
* @param the type of the nodes managed by this hierarchical configuration
*/
public abstract class AbstractHierarchicalConfiguration extends AbstractConfiguration
implements Cloneable, NodeKeyResolver, HierarchicalConfiguration {
/**
* A specialized visitor that fills a list with keys that are defined in a node hierarchy.
*/
private final class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter {
/** Stores the list to be filled. */
private final Set keyList;
/** A stack with the keys of the already processed nodes. */
private final Stack parentKeys;
/**
* Default constructor.
*/
public DefinedKeysVisitor() {
keyList = new LinkedHashSet<>();
parentKeys = new Stack<>();
}
/**
* 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);
}
/**
* Gets the list with all defined keys.
*
* @return the list with the defined keys
*/
public Set getKeyList() {
return keyList;
}
/**
* 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 handler) {
handler.getAttributes(node).forEach(attr -> keyList.add(getExpressionEngine().attributeKey(parentKey, attr)));
}
/**
* {@inheritDoc} This implementation removes this node's key from the stack.
*/
@Override
public void visitAfterChildren(final T node, final NodeHandler handler) {
parentKeys.pop();
}
/**
* {@inheritDoc} If this node has a value, its key is added to the internal list.
*/
@Override
public void visitBeforeChildren(final T node, final NodeHandler 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);
}
}
/**
* 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.
*
* @param the type of the nodes managed by this hierarchical configuration
*/
private static final class DefinedVisitor extends ConfigurationNodeVisitorAdapter {
/** Stores the defined flag. */
private boolean defined;
/**
* Returns the defined flag.
*
* @return the defined flag
*/
public boolean isDefined() {
return defined;
}
/**
* 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.
*
* @param node the actual node
*/
@Override
public void visitBeforeChildren(final T node, final NodeHandler handler) {
defined = handler.getValue(node) != null || !handler.getAttributes(node).isEmpty();
}
}
/** The model for managing the data stored in this configuration. */
private NodeModel nodeModel;
/** Stores the expression engine for this instance. */
private ExpressionEngine expressionEngine;
/**
* Creates a new instance of {@code AbstractHierarchicalConfiguration} and sets the {@code NodeModel} to be used.
*
* @param nodeModel the {@code NodeModel}
*/
protected AbstractHierarchicalConfiguration(final NodeModel nodeModel) {
this.nodeModel = nodeModel;
}
/**
* 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 null, 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 {@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 null, 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);
}
/**
* {@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));
}
/**
* 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));
}
/**
* 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);
}
/**
* 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 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);
}
/**
* 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
* {@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);
fireEvent(ConfigurationEvent.CLEAR_TREE, key, clearTreeInternal(key), false);
} finally {
endWrite();
}
}
/**
* 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);
}
/**
* 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 copy = (AbstractHierarchicalConfiguration) super.clone();
copy.setSynchronizer(NoOpSynchronizer.INSTANCE);
copy.cloneInterpolator(this);
copy.setSynchronizer(ConfigurationUtils.cloneSynchronizer(getSynchronizer()));
copy.nodeModel = 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 {@code clone()}.
*
* @return the clone of the {@code NodeModel}
* @since 2.0
*/
protected abstract NodeModel cloneNodeModel();
/**
* 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 .
*
* @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;
}
/**
* Tests whether this configuration contains one or more matches to this value. This operation stops at first
* match but may be more expensive than the containsKey method.
* @since 2.11.0
*/
@Override
protected boolean containsValueInternal(final Object value) {
return contains(getKeys(), value);
}
/**
* Helper method for resolving the specified key.
*
* @param key the key
* @return a list with all results selected by this key
*/
protected List> fetchNodeList(final String key) {
final NodeHandler nodeHandler = getModel().getNodeHandler();
return resolveKey(nodeHandler.getRootNode(), key, nodeHandler);
}
/**
* Gets 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.
*
* @return the current expression engine
* @since 1.3
*/
@Override
public ExpressionEngine getExpressionEngine() {
return expressionEngine != null ? expressionEngine : DefaultExpressionEngine.INSTANCE;
}
/**
* Gets 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 getKeysInternal() {
return visitDefinedKeys().getKeyList().iterator();
}
/**
* Gets 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 getKeysInternal(final String prefix) {
final DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
if (containsKey(prefix)) {
// explicitly add the prefix
visitor.getKeyList().add(prefix);
}
final List> results = fetchNodeList(prefix);
final NodeHandler handler = getModel().getNodeHandler();
results.forEach(result -> {
if (!result.isAttributeResult()) {
handler.getChildren(result.getNode()).forEach(c -> NodeTreeWalker.INSTANCE.walkDFS(c, visitor, handler));
visitor.handleAttributeKeys(prefix, result.getNode(), handler);
}
});
return visitor.getKeyList().iterator();
}
/**
* Gets 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 {@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;
}
/**
* Gets 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 "official"
* {@code getNodeModel()} method which is guarded by the configuration's {@code Synchronizer}.
*
* @return the node model
*/
protected NodeModel getModel() {
return nodeModel;
}
/**
* {@inheritDoc} This implementation returns the configuration's {@code NodeModel}. It is guarded by the current
* {@code Synchronizer}.
*/
@Override
public NodeModel getNodeModel() {
beginRead(false);
try {
return getModel();
} finally {
endRead();
}
}
/**
* 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> results = fetchNodeList(key);
if (results.isEmpty()) {
return null;
}
final NodeHandler handler = getModel().getNodeHandler();
final List