| /* |
| * 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> |
| * |
| * @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> { |
| |
| /** |
| * 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 <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 {@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); |
| } |
| |
| /** |
| * {@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<T> copy = (AbstractHierarchicalConfiguration<T>) 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<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 <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; |
| } |
| |
| /** |
| * 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 String 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 <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; |
| } |
| |
| /** |
| * 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) { |
| 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) { |
| 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<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() { |
| 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<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() { |
| beginRead(false); |
| try { |
| return getRootElementNameInternal(); |
| } finally { |
| endRead(); |
| } |
| } |
| |
| /** |
| * 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 <b>null</b>, 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 <b>null</b>) |
| */ |
| 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; |
| } |
| } |