AbstractHierarchicalConfiguration.java
- /*
- * 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;
- /**
- * <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>
- * <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>
- * </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>
- *
- * @param <T> the type of the nodes managed by this hierarchical configuration
- * @since 2.0
- */
- public abstract class AbstractHierarchicalConfiguration<T> extends AbstractConfiguration
- implements Cloneable, NodeKeyResolver<T>, HierarchicalConfiguration<T> {
- /**
- * A specialized visitor that fills a list with keys that are defined in a node hierarchy.
- */
- private final class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter<T> {
- /** Stores the list to be filled. */
- private final Set<String> keyList;
- /** A stack with the keys of the already processed nodes. */
- private final Stack<String> 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<String> 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<T> 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<T> 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<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);
- }
- }
- /**
- * 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 <T> the type of the nodes managed by this hierarchical configuration
- */
- private static final class DefinedVisitor<T> extends ConfigurationNodeVisitorAdapter<T> {
- /** 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<T> handler) {
- defined = handler.getValue(node) != null || !handler.getAttributes(node).isEmpty();
- }
- }
- /** The model for managing the data stored in this configuration. */
- private NodeModel<T> 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<T> 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 <strong>null</strong>, 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;
- }
- syncWrite(() -> {
- fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, true);
- addNodesInternal(key, nodes);
- fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, false);
- }, false);
- }
- /**
- * 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 <strong>null</strong>, 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) {
- syncWrite(() -> {
- fireEvent(ConfigurationEvent.CLEAR_TREE, key, null, true);
- fireEvent(ConfigurationEvent.CLEAR_TREE, key, clearTreeInternal(key), false);
- }, false);
- }
- /**
- * 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
- */
- @SuppressWarnings("unchecked")
- @Override
- public Object clone() {
- return syncRead(() -> {
- try {
- final AbstractHierarchicalConfiguration<T> copy = (AbstractHierarchicalConfiguration<T>) 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);
- }
- }, false);
- }
- /**
- * 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();
- /**
- * 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 <strong>false </strong>.
- *
- * @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<QueryResult<T>> fetchNodeList(final String key) {
- final NodeHandler<T> nodeHandler = getModel().getNodeHandler();
- return resolveKey(nodeHandler.getRootNode(), key, nodeHandler);
- }
- /**
- * Gets the expression engine used by this configuration. This method will never return <strong>null</strong>; 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<String> 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<String> getKeysInternal(final String prefix) {
- return getKeysInternal(prefix, DELIMITER);
- }
- /**
- * 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
- * @param delimiter TODO
- * @return an iterator with the found keys
- * @since 2.12.0
- */
- @Override
- protected Iterator<String> getKeysInternal(final String prefix, final String delimiter) {
- 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();
- 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) {
- return syncRead(() -> getMaxIndexInternal(key), false);
- }
- /**
- * 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<T> getModel() {
- return nodeModel;
- }
- /**
- * {@inheritDoc} This implementation returns the configuration's {@code NodeModel}. It is guarded by the current
- * {@code Synchronizer}.
- */
- @Override
- public NodeModel<T> getNodeModel() {
- return syncRead(this::getModel, false);
- }
- /**
- * 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 = results.stream().map(r -> valueFromResult(r, handler)).filter(Objects::nonNull).collect(Collectors.toList());
- if (list.size() < 1) {
- return null;
- }
- return list.size() == 1 ? list.get(0) : list;
- }
- /**
- * {@inheritDoc} This implementation handles synchronization and delegates to {@code getRootElementNameInternal()}.
- */
- @Override
- public final String getRootElementName() {
- return syncRead(this::getRootElementNameInternal, false);
- }
- /**
- * 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());
- }
- /**
- * 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 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();
- }
- /**
- * {@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> paths = new LinkedList<>();
- T currentNode = node;
- String key = cache.get(node);
- while (key == null && currentNode != null) {
- paths.add(0, currentNode);
- currentNode = handler.getParent(currentNode);
- key = cache.get(currentNode);
- }
- for (final T n : paths) {
- final String currentKey = getExpressionEngine().canonicalKey(n, key, handler);
- cache.put(n, currentKey);
- key = currentKey;
- }
- return key;
- }
- /**
- * {@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 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 {@code resolveKey()} and then filters out attribute results.
- */
- @Override
- public List<T> resolveNodeKey(final T root, final String key, final NodeHandler<T> handler) {
- return resolveKey(root, key, handler).stream().filter(r -> !r.isAttributeResult()).map(QueryResult::getNode)
- .collect(Collectors.toCollection(LinkedList::new));
- }
- /**
- * {@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<>();
- itValues.forEachRemaining(additionalValues::add);
- }
- // Remove remaining nodes
- if (itNodes.hasNext()) {
- removedItems = new LinkedList<>();
- itNodes.forEachRemaining(removedItems::add);
- }
- return new NodeUpdateData<>(changedValues, additionalValues, removedItems, key);
- }
- /**
- * 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 <strong>null</strong>, then the default expression engine will be
- * used
- * @since 1.3
- */
- @Override
- public void setExpressionEngine(final ExpressionEngine expressionEngine) {
- this.expressionEngine = expressionEngine;
- }
- /**
- * 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 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();
- }
- @Override
- public String toString() {
- return super.toString() + "(" + getRootElementNameInternal() + ")";
- }
- /**
- * 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 <strong>null</strong>)
- */
- private Object valueFromResult(final QueryResult<T> result, final NodeHandler<T> handler) {
- return result.isAttributeResult() ? result.getAttributeValue(handler) : handler.getValue(result.getNode());
- }
- /**
- * 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;
- }
- }