| /* |
| * 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.tree; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; |
| import org.apache.commons.lang3.mutable.Mutable; |
| import org.apache.commons.lang3.mutable.MutableObject; |
| |
| /** |
| * <p> |
| * A specialized node model implementation which operates on |
| * {@link ImmutableNode} structures. |
| * </p> |
| * <p> |
| * This {@code NodeModel} implementation keeps all its data as a tree of |
| * {@link ImmutableNode} objects in memory. The managed structure can be |
| * manipulated in a thread-safe, non-blocking way. This is achieved by using |
| * atomic variables: The root of the tree is stored in an atomic reference |
| * variable. Each update operation causes a new structure to be constructed |
| * (which reuses as much from the original structure as possible). The old root |
| * node is then replaced by the new one using an atomic compare-and-set |
| * operation. If this fails, the manipulation has to be done anew on the updated |
| * structure. |
| * </p> |
| * |
| * @since 2.0 |
| */ |
| public class InMemoryNodeModel implements NodeModel<ImmutableNode> |
| { |
| /** |
| * A dummy node handler instance used in operations which require only a |
| * limited functionality. |
| */ |
| private static final NodeHandler<ImmutableNode> DUMMY_HANDLER = |
| new TreeData(null, |
| Collections.<ImmutableNode, ImmutableNode> emptyMap(), |
| Collections.<ImmutableNode, ImmutableNode> emptyMap(), null, new ReferenceTracker()); |
| |
| /** Stores information about the current nodes structure. */ |
| private final AtomicReference<TreeData> structure; |
| |
| /** |
| * Creates a new instance of {@code InMemoryNodeModel} which is initialized |
| * with an empty root node. |
| */ |
| public InMemoryNodeModel() |
| { |
| this(null); |
| } |
| |
| /** |
| * Creates a new instance of {@code InMemoryNodeModel} and initializes it |
| * from the given root node. If the passed in node is <b>null</b>, a new, |
| * empty root node is created. |
| * |
| * @param root the new root node for this model |
| */ |
| public InMemoryNodeModel(final ImmutableNode root) |
| { |
| structure = |
| new AtomicReference<>( |
| createTreeData(initialRootNode(root), null)); |
| } |
| |
| /** |
| * Returns the root node of this mode. Note: This method should be used with |
| * care. The model may be updated concurrently which causes the root node to |
| * be replaced. If the root node is to be processed further (e.g. by |
| * executing queries on it), the model should be asked for its |
| * {@code NodeHandler}, and the root node should be obtained from there. The |
| * connection between a node handler and its root node remain constant |
| * because an update of the model causes the whole node handler to be |
| * replaced. |
| * |
| * @return the current root node |
| */ |
| public ImmutableNode getRootNode() |
| { |
| return getTreeData().getRootNode(); |
| } |
| |
| /** |
| * {@inheritDoc} {@code InMemoryNodeModel} implements the |
| * {@code NodeHandler} interface itself. So this implementation just returns |
| * the <strong>this</strong> reference. |
| */ |
| @Override |
| public NodeHandler<ImmutableNode> getNodeHandler() |
| { |
| return getReferenceNodeHandler(); |
| } |
| |
| @Override |
| public void addProperty(final String key, final Iterable<?> values, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| addProperty(key, null, values, resolver); |
| } |
| |
| /** |
| * Adds new property values using a tracked node as root node. This method |
| * works like the normal {@code addProperty()} method, but the origin of the |
| * operation (also for the interpretation of the passed in key) is a tracked |
| * node identified by the passed in {@code NodeSelector}. The selector can |
| * be <b>null</b>, then the root node is assumed. |
| * |
| * @param key the key |
| * @param selector the {@code NodeSelector} defining the root node (or |
| * <b>null</b>) |
| * @param values the values to be added |
| * @param resolver the {@code NodeKeyResolver} |
| * @throws ConfigurationRuntimeException if the selector cannot be resolved |
| */ |
| public void addProperty(final String key, final NodeSelector selector, |
| final Iterable<?> values, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| if (valuesNotEmpty(values)) |
| { |
| updateModel(tx -> { |
| initializeAddTransaction(tx, key, values, resolver); |
| return true; |
| }, selector, resolver); |
| } |
| } |
| |
| @Override |
| public void addNodes(final String key, final Collection<? extends ImmutableNode> nodes, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| addNodes(key, null, nodes, resolver); |
| } |
| |
| /** |
| * Adds new nodes using a tracked node as root node. This method works like |
| * the normal {@code addNodes()} method, but the origin of the operation |
| * (also for the interpretation of the passed in key) is a tracked node |
| * identified by the passed in {@code NodeSelector}. The selector can be |
| * <b>null</b>, then the root node is assumed. |
| * |
| * @param key the key |
| * @param selector the {@code NodeSelector} defining the root node (or |
| * <b>null</b>) |
| * @param nodes the collection of new nodes to be added |
| * @param resolver the {@code NodeKeyResolver} |
| * @throws ConfigurationRuntimeException if the selector cannot be resolved |
| */ |
| public void addNodes(final String key, final NodeSelector selector, |
| final Collection<? extends ImmutableNode> nodes, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| if (nodes != null && !nodes.isEmpty()) |
| { |
| updateModel(tx -> { |
| final List<QueryResult<ImmutableNode>> results = |
| resolver.resolveKey(tx.getQueryRoot(), key, |
| tx.getCurrentData()); |
| if (results.size() == 1) |
| { |
| if (results.get(0).isAttributeResult()) |
| { |
| throw attributeKeyException(key); |
| } |
| tx.addAddNodesOperation(results.get(0).getNode(), nodes); |
| } |
| else |
| { |
| final NodeAddData<ImmutableNode> addData = |
| resolver.resolveAddKey(tx.getQueryRoot(), key, |
| tx.getCurrentData()); |
| if (addData.isAttribute()) |
| { |
| throw attributeKeyException(key); |
| } |
| final ImmutableNode newNode = |
| new ImmutableNode.Builder(nodes.size()) |
| .name(addData.getNewNodeName()) |
| .addChildren(nodes).create(); |
| addNodesByAddData(tx, addData, |
| Collections.singleton(newNode)); |
| } |
| return true; |
| }, selector, resolver); |
| } |
| } |
| |
| @Override |
| public void setProperty(final String key, final Object value, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| setProperty(key, null, value, resolver); |
| } |
| |
| /** |
| * Sets the value of a property using a tracked node as root node. This |
| * method works like the normal {@code setProperty()} method, but the origin |
| * of the operation (also for the interpretation of the passed in key) is a |
| * tracked node identified by the passed in {@code NodeSelector}. The |
| * selector can be <b>null</b>, then the root node is assumed. |
| * |
| * @param key the key |
| * @param selector the {@code NodeSelector} defining the root node (or |
| * <b>null</b>) |
| * @param value the new value for this property |
| * @param resolver the {@code NodeKeyResolver} |
| * @throws ConfigurationRuntimeException if the selector cannot be resolved |
| */ |
| public void setProperty(final String key, final NodeSelector selector, |
| final Object value, final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| updateModel(tx -> { |
| boolean added = false; |
| final NodeUpdateData<ImmutableNode> updateData = |
| resolver.resolveUpdateKey(tx.getQueryRoot(), key, |
| value, tx.getCurrentData()); |
| if (!updateData.getNewValues().isEmpty()) |
| { |
| initializeAddTransaction(tx, key, |
| updateData.getNewValues(), resolver); |
| added = true; |
| } |
| final boolean cleared = |
| initializeClearTransaction(tx, |
| updateData.getRemovedNodes()); |
| final boolean updated = |
| initializeUpdateTransaction(tx, |
| updateData.getChangedValues()); |
| return added || cleared || updated; |
| }, selector, resolver); |
| } |
| |
| /** |
| * {@inheritDoc} This implementation checks whether nodes become undefined |
| * after subtrees have been removed. If this is the case, such nodes are |
| * removed, too. Return value is a collection with {@code QueryResult} |
| * objects for the elements to be removed from the model. |
| */ |
| @Override |
| public List<QueryResult<ImmutableNode>> clearTree(final String key, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| return clearTree(key, null, resolver); |
| } |
| |
| /** |
| * Clears a whole sub tree using a tracked node as root node. This method |
| * works like the normal {@code clearTree()} method, but the origin of the |
| * operation (also for the interpretation of the passed in key) is a tracked |
| * node identified by the passed in {@code NodeSelector}. The selector can |
| * be <b>null</b>, then the root node is assumed. |
| * |
| * @param key the key |
| * @param selector the {@code NodeSelector} defining the root node (or |
| * <b>null</b>) |
| * @param resolver the {@code NodeKeyResolver} |
| * @return a list with the results to be removed |
| * @throws ConfigurationRuntimeException if the selector cannot be resolved |
| */ |
| public List<QueryResult<ImmutableNode>> clearTree(final String key, |
| final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| final List<QueryResult<ImmutableNode>> removedElements = |
| new LinkedList<>(); |
| updateModel(tx -> { |
| boolean changes = false; |
| final TreeData currentStructure = tx.getCurrentData(); |
| final List<QueryResult<ImmutableNode>> results = resolver.resolveKey( |
| tx.getQueryRoot(), key, currentStructure); |
| removedElements.clear(); |
| removedElements.addAll(results); |
| for (final QueryResult<ImmutableNode> result : results) |
| { |
| if (result.isAttributeResult()) |
| { |
| tx.addRemoveAttributeOperation(result.getNode(), |
| result.getAttributeName()); |
| } |
| else |
| { |
| if (result.getNode() == currentStructure.getRootNode()) |
| { |
| // the whole model is to be cleared |
| clear(resolver); |
| return false; |
| } |
| tx.addRemoveNodeOperation( |
| currentStructure.getParent(result.getNode()), |
| result.getNode()); |
| } |
| changes = true; |
| } |
| return changes; |
| }, selector, resolver); |
| |
| return removedElements; |
| } |
| |
| /** |
| * {@inheritDoc} If this operation leaves an affected node in an undefined |
| * state, it is removed from the model. |
| */ |
| @Override |
| public void clearProperty(final String key, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| clearProperty(key, null, resolver); |
| } |
| |
| /** |
| * Clears a property using a tracked node as root node. This method works |
| * like the normal {@code clearProperty()} method, but the origin of the |
| * operation (also for the interpretation of the passed in key) is a tracked |
| * node identified by the passed in {@code NodeSelector}. The selector can |
| * be <b>null</b>, then the root node is assumed. |
| * |
| * @param key the key |
| * @param selector the {@code NodeSelector} defining the root node (or |
| * <b>null</b>) |
| * @param resolver the {@code NodeKeyResolver} |
| * @throws ConfigurationRuntimeException if the selector cannot be resolved |
| */ |
| public void clearProperty(final String key, final NodeSelector selector, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| updateModel(tx -> { |
| final List<QueryResult<ImmutableNode>> results = |
| resolver.resolveKey(tx.getQueryRoot(), key, |
| tx.getCurrentData()); |
| return initializeClearTransaction(tx, results); |
| }, selector, resolver); |
| } |
| |
| /** |
| * {@inheritDoc} A new empty root node is created with the same name as the |
| * current root node. Implementation note: Because this is a hard reset the |
| * usual dance for dealing with concurrent updates is not required here. |
| * |
| * @param resolver the {@code NodeKeyResolver} |
| */ |
| @Override |
| public void clear(final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| final ImmutableNode newRoot = |
| new ImmutableNode.Builder().name(getRootNode().getNodeName()) |
| .create(); |
| setRootNode(newRoot); |
| } |
| |
| /** |
| * {@inheritDoc} This implementation simply returns the current root node of this |
| * model. |
| */ |
| @Override |
| public ImmutableNode getInMemoryRepresentation() |
| { |
| return getTreeData().getRootNode(); |
| } |
| |
| /** |
| * {@inheritDoc} All tracked nodes and reference objects managed by this |
| * model are cleared.Care has to be taken when this method is used and the |
| * model is accessed by multiple threads. It is not deterministic which |
| * concurrent operations see the old root and which see the new root node. |
| * |
| * @param newRoot the new root node to be set (can be <b>null</b>, then an |
| * empty root node is set) |
| */ |
| @Override |
| public void setRootNode(final ImmutableNode newRoot) |
| { |
| structure.set(createTreeData(initialRootNode(newRoot), structure.get())); |
| } |
| |
| /** |
| * Replaces the root node of this model. This method is similar to |
| * {@link #setRootNode(ImmutableNode)}; however, tracked nodes will not get |
| * lost. The model applies the selectors of all tracked nodes on the new |
| * nodes hierarchy, so that corresponding nodes are selected (this may cause |
| * nodes to become detached if a select operation fails). This operation is |
| * useful if the new nodes hierarchy to be set is known to be similar to the |
| * old one. Note that reference objects are lost; there is no way to |
| * automatically match nodes between the old and the new nodes hierarchy. |
| * |
| * @param newRoot the new root node to be set (must not be <b>null</b>) |
| * @param resolver the {@code NodeKeyResolver} |
| * @throws IllegalArgumentException if the new root node is <b>null</b> |
| */ |
| public void replaceRoot(final ImmutableNode newRoot, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| if (newRoot == null) |
| { |
| throw new IllegalArgumentException( |
| "Replaced root node must not be null!"); |
| } |
| |
| final TreeData current = structure.get(); |
| // this step is needed to get a valid NodeHandler |
| final TreeData temp = |
| createTreeDataForRootAndTracker(newRoot, |
| current.getNodeTracker()); |
| structure.set(temp.updateNodeTracker(temp.getNodeTracker().update( |
| newRoot, null, resolver, temp))); |
| } |
| |
| /** |
| * Merges the root node of this model with the specified node. This method |
| * is typically caused by configuration implementations when a configuration |
| * source is loaded, and its data has to be added to the model. It is |
| * possible to define the new name of the root node and to pass in a map |
| * with reference objects. |
| * |
| * @param node the node to be merged with the root node |
| * @param rootName the new name of the root node; can be <b>null</b>, then |
| * the name of the root node is not changed unless it is <b>null</b> |
| * @param references an optional map with reference objects |
| * @param rootRef an optional reference object for the new root node |
| * @param resolver the {@code NodeKeyResolver} |
| */ |
| public void mergeRoot(final ImmutableNode node, final String rootName, |
| final Map<ImmutableNode, ?> references, final Object rootRef, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| updateModel(tx -> { |
| final TreeData current = tx.getCurrentData(); |
| final String newRootName = |
| determineRootName(current.getRootNode(), node, rootName); |
| if (newRootName != null) |
| { |
| tx.addChangeNodeNameOperation(current.getRootNode(), |
| newRootName); |
| } |
| tx.addAddNodesOperation(current.getRootNode(), |
| node.getChildren()); |
| tx.addAttributesOperation(current.getRootNode(), |
| node.getAttributes()); |
| if (node.getValue() != null) |
| { |
| tx.addChangeNodeValueOperation(current.getRootNode(), |
| node.getValue()); |
| } |
| if (references != null) |
| { |
| tx.addNewReferences(references); |
| } |
| if (rootRef != null) |
| { |
| tx.addNewReference(current.getRootNode(), rootRef); |
| } |
| return true; |
| }, null, resolver); |
| } |
| |
| /** |
| * Adds a node to be tracked. After this method has been called with a |
| * specific {@code NodeSelector}, the node associated with this key can be |
| * always obtained using {@link #getTrackedNode(NodeSelector)} with the same |
| * selector. This is useful because during updates of a model parts of the |
| * structure are replaced. Therefore, it is not a good idea to simply hold a |
| * reference to a node; this might become outdated soon. Rather, the node |
| * should be tracked. This mechanism ensures that always the correct node |
| * reference can be obtained. |
| * |
| * @param selector the {@code NodeSelector} defining the desired node |
| * @param resolver the {@code NodeKeyResolver} |
| * @throws ConfigurationRuntimeException if the selector does not select a |
| * single node |
| */ |
| public void trackNode(final NodeSelector selector, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| boolean done; |
| do |
| { |
| final TreeData current = structure.get(); |
| final NodeTracker newTracker = |
| current.getNodeTracker().trackNode(current.getRootNode(), |
| selector, resolver, current); |
| done = |
| structure.compareAndSet(current, |
| current.updateNodeTracker(newTracker)); |
| } while (!done); |
| } |
| |
| /** |
| * Allows tracking all nodes selected by a key. This method evaluates the |
| * specified key on the current nodes structure. For all selected nodes |
| * corresponding {@code NodeSelector} objects are created, and they are |
| * tracked. The returned collection of {@code NodeSelector} objects can be |
| * used for interacting with the selected nodes. |
| * |
| * @param key the key for selecting the nodes to track |
| * @param resolver the {@code NodeKeyResolver} |
| * @return a collection with the {@code NodeSelector} objects for the new |
| * tracked nodes |
| */ |
| public Collection<NodeSelector> selectAndTrackNodes(final String key, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| final Mutable<Collection<NodeSelector>> refSelectors = |
| new MutableObject<>(); |
| boolean done; |
| do |
| { |
| final TreeData current = structure.get(); |
| final List<ImmutableNode> nodes = |
| resolver.resolveNodeKey(current.getRootNode(), key, current); |
| if (nodes.isEmpty()) |
| { |
| return Collections.emptyList(); |
| } |
| done = |
| structure.compareAndSet( |
| current, |
| createSelectorsForTrackedNodes(refSelectors, nodes, |
| current, resolver)); |
| } while (!done); |
| return refSelectors.getValue(); |
| } |
| |
| /** |
| * Tracks all nodes which are children of the node selected by the passed in |
| * key. If the key selects exactly one node, for all children of this node |
| * {@code NodeSelector} objects are created, and they become tracked nodes. |
| * The returned collection of {@code NodeSelector} objects can be used for |
| * interacting with the selected nodes. |
| * |
| * @param key the key for selecting the parent node whose children are to be |
| * tracked |
| * @param resolver the {@code NodeKeyResolver} |
| * @return a collection with the {@code NodeSelector} objects for the new |
| * tracked nodes |
| */ |
| public Collection<NodeSelector> trackChildNodes(final String key, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| final Mutable<Collection<NodeSelector>> refSelectors = |
| new MutableObject<>(); |
| boolean done; |
| do |
| { |
| refSelectors.setValue(Collections.<NodeSelector> emptyList()); |
| final TreeData current = structure.get(); |
| final List<ImmutableNode> nodes = |
| resolver.resolveNodeKey(current.getRootNode(), key, current); |
| if (nodes.size() == 1) |
| { |
| final ImmutableNode node = nodes.get(0); |
| done = |
| node.getChildren().isEmpty() |
| || structure.compareAndSet( |
| current, |
| createSelectorsForTrackedNodes( |
| refSelectors, |
| node.getChildren(), current, |
| resolver)); |
| } |
| else |
| { |
| done = true; |
| } |
| } while (!done); |
| return refSelectors.getValue(); |
| } |
| |
| /** |
| * Tracks a node which is a child of another node selected by the passed in |
| * key. If the selected node has a child node with this name, it is tracked |
| * and its selector is returned. Otherwise, a new child node with this name |
| * is created first. |
| * |
| * @param key the key for selecting the parent node |
| * @param childName the name of the child node |
| * @param resolver the {@code NodeKeyResolver} |
| * @return the {@code NodeSelector} for the tracked child node |
| * @throws ConfigurationRuntimeException if the passed in key does not |
| * select a single node |
| */ |
| public NodeSelector trackChildNodeWithCreation(final String key, |
| final String childName, final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| final MutableObject<NodeSelector> refSelector = |
| new MutableObject<>(); |
| boolean done; |
| |
| do |
| { |
| final TreeData current = structure.get(); |
| final List<ImmutableNode> nodes = |
| resolver.resolveNodeKey(current.getRootNode(), key, current); |
| if (nodes.size() != 1) |
| { |
| throw new ConfigurationRuntimeException( |
| "Key does not select a single node: " + key); |
| } |
| |
| final ImmutableNode parent = nodes.get(0); |
| final TreeData newData = |
| createDataWithTrackedChildNode(current, parent, childName, |
| resolver, refSelector); |
| |
| done = structure.compareAndSet(current, newData); |
| } while (!done); |
| |
| return refSelector.getValue(); |
| } |
| |
| /** |
| * Returns the current {@code ImmutableNode} instance associated with the |
| * given {@code NodeSelector}. The node must be a tracked node, i.e. |
| * {@link #trackNode(NodeSelector, NodeKeyResolver)} must have been called |
| * before with the given selector. |
| * |
| * @param selector the {@code NodeSelector} defining the desired node |
| * @return the current {@code ImmutableNode} associated with this selector |
| * @throws ConfigurationRuntimeException if the selector is unknown |
| */ |
| public ImmutableNode getTrackedNode(final NodeSelector selector) |
| { |
| return structure.get().getNodeTracker().getTrackedNode(selector); |
| } |
| |
| /** |
| * Replaces a tracked node by another node. If the tracked node is not yet |
| * detached, it becomes now detached. The passed in node (which must not be |
| * <b>null</b>) becomes the new root node of an independent model for this |
| * tracked node. Further updates of this model do not affect the tracked |
| * node's model and vice versa. |
| * |
| * @param selector the {@code NodeSelector} defining the tracked node |
| * @param newNode the node replacing the tracked node (must not be |
| * <b>null</b>) |
| * @throws ConfigurationRuntimeException if the selector cannot be resolved |
| * @throws IllegalArgumentException if the replacement node is <b>null</b> |
| */ |
| public void replaceTrackedNode(final NodeSelector selector, final ImmutableNode newNode) |
| { |
| if (newNode == null) |
| { |
| throw new IllegalArgumentException( |
| "Replacement node must not be null!"); |
| } |
| |
| boolean done; |
| do |
| { |
| final TreeData currentData = structure.get(); |
| done = |
| replaceDetachedTrackedNode(currentData, selector, newNode) |
| || replaceActiveTrackedNode(currentData, selector, |
| newNode); |
| } while (!done); |
| } |
| |
| /** |
| * Returns a {@code NodeHandler} for a tracked node. Such a handler may be |
| * required for operations on a sub tree of the model. The handler to be |
| * returned depends on the current state of the tracked node. If it is still |
| * active, a handler is used which shares some data (especially the parent |
| * mapping) with this model. Detached track nodes in contrast have their own |
| * separate model; in this case a handler associated with this model is |
| * returned. |
| * |
| * @param selector the {@code NodeSelector} defining the tracked node |
| * @return a {@code NodeHandler} for this tracked node |
| * @throws ConfigurationRuntimeException if the selector is unknown |
| */ |
| public NodeHandler<ImmutableNode> getTrackedNodeHandler( |
| final NodeSelector selector) |
| { |
| final TreeData currentData = structure.get(); |
| final InMemoryNodeModel detachedNodeModel = |
| currentData.getNodeTracker().getDetachedNodeModel(selector); |
| return detachedNodeModel != null ? detachedNodeModel.getNodeHandler() |
| : new TrackedNodeHandler(currentData.getNodeTracker() |
| .getTrackedNode(selector), currentData); |
| } |
| |
| /** |
| * Returns a flag whether the specified tracked node is detached. As long as |
| * the {@code NodeSelector} associated with that node returns a single |
| * instance, the tracked node is said to be <em>life</em>. If now an update |
| * of the model happens which invalidates the selector (maybe the target |
| * node was removed), the tracked node becomes detached. It is still |
| * possible to query the node; here the latest valid instance is returned. |
| * But further changes on the node model are no longer tracked for this |
| * node. So even if there are further changes which would make the |
| * {@code NodeSelector} valid again, the tracked node stays in detached |
| * state. |
| * |
| * @param selector the {@code NodeSelector} defining the desired node |
| * @return a flag whether this tracked node is in detached state |
| * @throws ConfigurationRuntimeException if the selector is unknown |
| */ |
| public boolean isTrackedNodeDetached(final NodeSelector selector) |
| { |
| return structure.get().getNodeTracker().isTrackedNodeDetached(selector); |
| } |
| |
| /** |
| * Removes a tracked node. This method is the opposite of |
| * {@code trackNode()}. It has to be called if there is no longer the need |
| * to track a specific node. Note that for each call of {@code trackNode()} |
| * there has to be a corresponding {@code untrackNode()} call. This ensures |
| * that multiple observers can track the same node. |
| * |
| * @param selector the {@code NodeSelector} defining the desired node |
| * @throws ConfigurationRuntimeException if the specified node is not |
| * tracked |
| */ |
| public void untrackNode(final NodeSelector selector) |
| { |
| boolean done; |
| do |
| { |
| final TreeData current = structure.get(); |
| final NodeTracker newTracker = |
| current.getNodeTracker().untrackNode(selector); |
| done = |
| structure.compareAndSet(current, |
| current.updateNodeTracker(newTracker)); |
| } while (!done); |
| } |
| |
| /** |
| * Returns a {@code ReferenceNodeHandler} object for this model. This |
| * extended node handler can be used to query references objects stored for |
| * this model. |
| * |
| * @return the {@code ReferenceNodeHandler} |
| */ |
| public ReferenceNodeHandler getReferenceNodeHandler() |
| { |
| return getTreeData(); |
| } |
| |
| /** |
| * Returns the current {@code TreeData} object. This object contains all |
| * information about the current node structure. |
| * |
| * @return the current {@code TreeData} object |
| */ |
| TreeData getTreeData() |
| { |
| return structure.get(); |
| } |
| |
| /** |
| * Updates the mapping from nodes to their parents for the passed in |
| * hierarchy of nodes. This method traverses all children and grand-children |
| * of the passed in root node. For each node in the subtree the parent |
| * relation is added to the map. |
| * |
| * @param parents the map with parent nodes |
| * @param root the root node of the current tree |
| */ |
| static void updateParentMapping(final Map<ImmutableNode, ImmutableNode> parents, |
| final ImmutableNode root) |
| { |
| NodeTreeWalker.INSTANCE.walkBFS(root, |
| new ConfigurationNodeVisitorAdapter<ImmutableNode>() |
| { |
| @Override |
| public void visitBeforeChildren(final ImmutableNode node, |
| final NodeHandler<ImmutableNode> handler) |
| { |
| for (final ImmutableNode c : node) |
| { |
| parents.put(c, node); |
| } |
| } |
| }, DUMMY_HANDLER); |
| } |
| |
| /** |
| * Checks if the passed in node is defined. Result is <b>true</b> if the |
| * node contains any data. |
| * |
| * @param node the node in question |
| * @return <b>true</b> if the node is defined, <b>false</b> otherwise |
| */ |
| static boolean checkIfNodeDefined(final ImmutableNode node) |
| { |
| return node.getValue() != null || !node.getChildren().isEmpty() |
| || !node.getAttributes().isEmpty(); |
| } |
| |
| /** |
| * Initializes a transaction for an add operation. |
| * |
| * @param tx the transaction to be initialized |
| * @param key the key |
| * @param values the collection with node values |
| * @param resolver the {@code NodeKeyResolver} |
| */ |
| private void initializeAddTransaction(final ModelTransaction tx, final String key, |
| final Iterable<?> values, final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| final NodeAddData<ImmutableNode> addData = |
| resolver.resolveAddKey(tx.getQueryRoot(), key, |
| tx.getCurrentData()); |
| if (addData.isAttribute()) |
| { |
| addAttributeProperty(tx, addData, values); |
| } |
| else |
| { |
| addNodeProperty(tx, addData, values); |
| } |
| } |
| |
| /** |
| * Creates a {@code TreeData} object for the specified root node. |
| * |
| * @param root the root node of the current tree |
| * @param current the current {@code TreeData} object (may be <b>null</b>) |
| * @return the {@code TreeData} describing the current tree |
| */ |
| private TreeData createTreeData(final ImmutableNode root, final TreeData current) |
| { |
| final NodeTracker newTracker = |
| current != null ? current.getNodeTracker() |
| .detachAllTrackedNodes() : new NodeTracker(); |
| return createTreeDataForRootAndTracker(root, newTracker); |
| } |
| |
| /** |
| * Creates a {@code TreeData} object for the specified root node and |
| * {@code NodeTracker}. Other parameters are set to default values. |
| * |
| * @param root the new root node for this model |
| * @param newTracker the new {@code NodeTracker} |
| * @return the new {@code TreeData} object |
| */ |
| private TreeData createTreeDataForRootAndTracker(final ImmutableNode root, |
| final NodeTracker newTracker) |
| { |
| return new TreeData(root, createParentMapping(root), |
| Collections.<ImmutableNode, ImmutableNode> emptyMap(), |
| newTracker, new ReferenceTracker()); |
| } |
| |
| /** |
| * Handles an add property operation if the property to be added is a node. |
| * |
| * @param tx the transaction |
| * @param addData the {@code NodeAddData} |
| * @param values the collection with node values |
| */ |
| private static void addNodeProperty(final ModelTransaction tx, |
| final NodeAddData<ImmutableNode> addData, final Iterable<?> values) |
| { |
| final Collection<ImmutableNode> newNodes = |
| createNodesToAdd(addData.getNewNodeName(), values); |
| addNodesByAddData(tx, addData, newNodes); |
| } |
| |
| /** |
| * Initializes a transaction to add a collection of nodes as described by a |
| * {@code NodeAddData} object. If necessary, new path nodes are created. |
| * Eventually, the new nodes are added as children to the specified target |
| * node. |
| * |
| * @param tx the transaction |
| * @param addData the {@code NodeAddData} |
| * @param newNodes the collection of new child nodes |
| */ |
| private static void addNodesByAddData(final ModelTransaction tx, |
| final NodeAddData<ImmutableNode> addData, |
| final Collection<ImmutableNode> newNodes) |
| { |
| if (addData.getPathNodes().isEmpty()) |
| { |
| tx.addAddNodesOperation(addData.getParent(), newNodes); |
| } |
| else |
| { |
| final ImmutableNode newChild = createNodeToAddWithPath(addData, newNodes); |
| tx.addAddNodeOperation(addData.getParent(), newChild); |
| } |
| } |
| |
| /** |
| * Handles an add property operation if the property to be added is an |
| * attribute. |
| * |
| * @param tx the transaction |
| * @param addData the {@code NodeAddData} |
| * @param values the collection with node values |
| */ |
| private static void addAttributeProperty(final ModelTransaction tx, |
| final NodeAddData<ImmutableNode> addData, final Iterable<?> values) |
| { |
| if (addData.getPathNodes().isEmpty()) |
| { |
| tx.addAttributeOperation(addData.getParent(), |
| addData.getNewNodeName(), values.iterator().next()); |
| } |
| else |
| { |
| final int pathNodeCount = addData.getPathNodes().size(); |
| final ImmutableNode childWithAttribute = |
| new ImmutableNode.Builder() |
| .name(addData.getPathNodes().get(pathNodeCount - 1)) |
| .addAttribute(addData.getNewNodeName(), |
| values.iterator().next()).create(); |
| final ImmutableNode newChild = |
| pathNodeCount > 1 ? createNodeOnPath(addData |
| .getPathNodes().subList(0, pathNodeCount - 1) |
| .iterator(), |
| Collections.singleton(childWithAttribute)) |
| : childWithAttribute; |
| tx.addAddNodeOperation(addData.getParent(), newChild); |
| } |
| } |
| |
| /** |
| * Creates a collection with new nodes with a given name and a value from a |
| * given collection. |
| * |
| * @param newNodeName the name of the new nodes |
| * @param values the collection with node values |
| * @return the newly created collection |
| */ |
| private static Collection<ImmutableNode> createNodesToAdd( |
| final String newNodeName, final Iterable<?> values) |
| { |
| final Collection<ImmutableNode> nodes = new LinkedList<>(); |
| for (final Object value : values) |
| { |
| nodes.add(new ImmutableNode.Builder().name(newNodeName) |
| .value(value).create()); |
| } |
| return nodes; |
| } |
| |
| /** |
| * Creates a node structure consisting of the path nodes defined by the |
| * passed in {@code NodeAddData} instance and all new child nodes. |
| * |
| * @param addData the {@code NodeAddData} |
| * @param newNodes the collection of new child nodes |
| * @return the parent node of the newly created hierarchy |
| */ |
| private static ImmutableNode createNodeToAddWithPath( |
| final NodeAddData<ImmutableNode> addData, |
| final Collection<ImmutableNode> newNodes) |
| { |
| return createNodeOnPath(addData.getPathNodes().iterator(), newNodes); |
| } |
| |
| /** |
| * Recursive helper method for creating a path node for an add operation. |
| * All path nodes except for the last have a single child. The last path |
| * node has the new nodes as children. |
| * |
| * @param it the iterator over the names of the path nodes |
| * @param newNodes the collection of new child nodes |
| * @return the newly created path node |
| */ |
| private static ImmutableNode createNodeOnPath(final Iterator<String> it, |
| final Collection<ImmutableNode> newNodes) |
| { |
| final String nodeName = it.next(); |
| final ImmutableNode.Builder builder; |
| if (it.hasNext()) |
| { |
| builder = new ImmutableNode.Builder(1); |
| builder.addChild(createNodeOnPath(it, newNodes)); |
| } |
| else |
| { |
| builder = new ImmutableNode.Builder(newNodes.size()); |
| builder.addChildren(newNodes); |
| } |
| return builder.name(nodeName).create(); |
| } |
| |
| /** |
| * Initializes a transaction to clear the values of a property based on the |
| * passed in collection of affected results. |
| * |
| * @param tx the transaction to be initialized |
| * @param results a collection with results pointing to the nodes to be |
| * cleared |
| * @return a flag whether there are elements to be cleared |
| */ |
| private static boolean initializeClearTransaction(final ModelTransaction tx, |
| final Collection<QueryResult<ImmutableNode>> results) |
| { |
| for (final QueryResult<ImmutableNode> result : results) |
| { |
| if (result.isAttributeResult()) |
| { |
| tx.addRemoveAttributeOperation(result.getNode(), |
| result.getAttributeName()); |
| } |
| else |
| { |
| tx.addClearNodeValueOperation(result.getNode()); |
| } |
| } |
| |
| return !results.isEmpty(); |
| } |
| |
| /** |
| * Initializes a transaction to change the values of some query results |
| * based on the passed in map. |
| * |
| * @param tx the transaction to be initialized |
| * @param changedValues the map defining the elements to be changed |
| * @return a flag whether there are elements to be updated |
| */ |
| private static boolean initializeUpdateTransaction(final ModelTransaction tx, |
| final Map<QueryResult<ImmutableNode>, Object> changedValues) |
| { |
| for (final Map.Entry<QueryResult<ImmutableNode>, Object> e : changedValues |
| .entrySet()) |
| { |
| if (e.getKey().isAttributeResult()) |
| { |
| tx.addAttributeOperation(e.getKey().getNode(), e.getKey() |
| .getAttributeName(), e.getValue()); |
| } |
| else |
| { |
| tx.addChangeNodeValueOperation(e.getKey().getNode(), |
| e.getValue()); |
| } |
| } |
| |
| return !changedValues.isEmpty(); |
| } |
| |
| /** |
| * Determines the initial root node of this model. If a root node has been |
| * provided, it is used. Otherwise, an empty dummy root node is created. |
| * |
| * @param providedRoot the passed in root node |
| * @return the root node to be used |
| */ |
| private static ImmutableNode initialRootNode(final ImmutableNode providedRoot) |
| { |
| return providedRoot != null ? providedRoot |
| : new ImmutableNode.Builder().create(); |
| } |
| |
| /** |
| * Determines the name of the root node for a merge operation. If a root |
| * name is provided, it is used. Otherwise, if the current root node has no |
| * name, the name of the node to be merged is used. A result of <b>null</b> |
| * means that no node name has to be set. |
| * |
| * @param rootNode the current root node |
| * @param node the node to be merged with the root node |
| * @param rootName the name of the resulting node |
| * @return the new name of the root node |
| */ |
| private static String determineRootName(final ImmutableNode rootNode, |
| final ImmutableNode node, final String rootName) |
| { |
| if (rootName != null) |
| { |
| return rootName; |
| } |
| if (rootNode.getNodeName() == null) |
| { |
| return node.getNodeName(); |
| } |
| return null; |
| } |
| |
| /** |
| * Creates the mapping to parent nodes for the nodes structured represented |
| * by the passed in root node. Each node is assigned its parent node. Here |
| * an iterative algorithm is used rather than a recursive one to avoid stack |
| * overflow for huge structures. |
| * |
| * @param root the root node of the structure |
| * @return the parent node mapping |
| */ |
| private Map<ImmutableNode, ImmutableNode> createParentMapping( |
| final ImmutableNode root) |
| { |
| final Map<ImmutableNode, ImmutableNode> parents = |
| new HashMap<>(); |
| updateParentMapping(parents, root); |
| return parents; |
| } |
| |
| /** |
| * Performs a non-blocking, thread-safe update of this model based on a |
| * transaction initialized by the passed in initializer. This method uses |
| * the atomic reference for the model's current data to ensure that an |
| * update was successful even if the model is concurrently accessed. |
| * |
| * @param txInit the {@code TransactionInitializer} |
| * @param selector an optional {@code NodeSelector} defining the target node |
| * of the transaction |
| * @param resolver the {@code NodeKeyResolver} |
| */ |
| private void updateModel(final TransactionInitializer txInit, |
| final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| boolean done; |
| |
| do |
| { |
| final TreeData currentData = getTreeData(); |
| done = |
| executeTransactionOnDetachedTrackedNode(txInit, selector, |
| currentData, resolver) |
| || executeTransactionOnCurrentStructure(txInit, |
| selector, currentData, resolver); |
| } while (!done); |
| } |
| |
| /** |
| * Executes a transaction on the current data of this model. This method is |
| * called if an operation is to be executed on the model's root node or a |
| * tracked node which is not yet detached. |
| * |
| * @param txInit the {@code TransactionInitializer} |
| * @param selector an optional {@code NodeSelector} defining the target node |
| * @param currentData the current data of the model |
| * @param resolver the {@code NodeKeyResolver} |
| * @return a flag whether the operation has been completed successfully |
| */ |
| private boolean executeTransactionOnCurrentStructure( |
| final TransactionInitializer txInit, final NodeSelector selector, |
| final TreeData currentData, final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| final boolean done; |
| final ModelTransaction tx = |
| new ModelTransaction(currentData, selector, resolver); |
| if (!txInit.initTransaction(tx)) |
| { |
| done = true; |
| } |
| else |
| { |
| final TreeData newData = tx.execute(); |
| done = structure.compareAndSet(tx.getCurrentData(), newData); |
| } |
| return done; |
| } |
| |
| /** |
| * Tries to execute a transaction on the model of a detached tracked node. |
| * This method checks whether the target node of the transaction is a |
| * tracked node and if this node is already detached. If this is the case, |
| * the update operation is independent on this model and has to be executed |
| * on the specific model for the detached node. |
| * |
| * @param txInit the {@code TransactionInitializer} |
| * @param selector an optional {@code NodeSelector} defining the target node |
| * @param currentData the current data of the model |
| * @param resolver the {@code NodeKeyResolver} @return a flag whether the |
| * transaction could be executed |
| * @throws ConfigurationRuntimeException if the selector cannot be resolved |
| */ |
| private boolean executeTransactionOnDetachedTrackedNode( |
| final TransactionInitializer txInit, final NodeSelector selector, |
| final TreeData currentData, final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| if (selector != null) |
| { |
| final InMemoryNodeModel detachedNodeModel = |
| currentData.getNodeTracker().getDetachedNodeModel(selector); |
| if (detachedNodeModel != null) |
| { |
| detachedNodeModel.updateModel(txInit, null, resolver); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Replaces a tracked node if it is already detached. |
| * |
| * @param currentData the current data of the model |
| * @param selector the {@code NodeSelector} defining the tracked node |
| * @param newNode the node replacing the tracked node |
| * @return a flag whether the operation was successful |
| */ |
| private boolean replaceDetachedTrackedNode(final TreeData currentData, |
| final NodeSelector selector, final ImmutableNode newNode) |
| { |
| final InMemoryNodeModel detachedNodeModel = |
| currentData.getNodeTracker().getDetachedNodeModel(selector); |
| if (detachedNodeModel != null) |
| { |
| detachedNodeModel.setRootNode(newNode); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Replaces an active tracked node. The node then becomes detached. |
| * |
| * @param currentData the current data of the model |
| * @param selector the {@code NodeSelector} defining the tracked node |
| * @param newNode the node replacing the tracked node |
| * @return a flag whether the operation was successful |
| */ |
| private boolean replaceActiveTrackedNode(final TreeData currentData, |
| final NodeSelector selector, final ImmutableNode newNode) |
| { |
| final NodeTracker newTracker = |
| currentData.getNodeTracker().replaceAndDetachTrackedNode( |
| selector, newNode); |
| return structure.compareAndSet(currentData, |
| currentData.updateNodeTracker(newTracker)); |
| } |
| |
| /** |
| * Creates tracked node entries for the specified nodes and creates the |
| * corresponding selectors. |
| * |
| * @param refSelectors the reference where to store the selectors |
| * @param nodes the nodes to be tracked |
| * @param current the current {@code TreeData} object |
| * @param resolver the {@code NodeKeyResolver} |
| * @return the updated {@code TreeData} object |
| */ |
| private static TreeData createSelectorsForTrackedNodes( |
| final Mutable<Collection<NodeSelector>> refSelectors, |
| final List<ImmutableNode> nodes, final TreeData current, |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| final List<NodeSelector> selectors = |
| new ArrayList<>(nodes.size()); |
| final Map<ImmutableNode, String> cache = new HashMap<>(); |
| for (final ImmutableNode node : nodes) |
| { |
| selectors.add(new NodeSelector(resolver.nodeKey(node, cache, |
| current))); |
| } |
| refSelectors.setValue(selectors); |
| final NodeTracker newTracker = |
| current.getNodeTracker().trackNodes(selectors, nodes); |
| return current.updateNodeTracker(newTracker); |
| } |
| |
| /** |
| * Adds a tracked node that has already been resolved to the specified data |
| * object. |
| * |
| * @param current the current {@code TreeData} object |
| * @param node the node in question |
| * @param resolver the {@code NodeKeyResolver} |
| * @param refSelector here the newly created {@code NodeSelector} is |
| * returned |
| * @return the new {@code TreeData} instance |
| */ |
| private static TreeData updateDataWithNewTrackedNode(final TreeData current, |
| final ImmutableNode node, final NodeKeyResolver<ImmutableNode> resolver, |
| final MutableObject<NodeSelector> refSelector) |
| { |
| final NodeSelector selector = |
| new NodeSelector(resolver.nodeKey(node, |
| new HashMap<>(), current)); |
| refSelector.setValue(selector); |
| final NodeTracker newTracker = |
| current.getNodeTracker().trackNodes( |
| Collections.singleton(selector), |
| Collections.singleton(node)); |
| return current.updateNodeTracker(newTracker); |
| } |
| |
| /** |
| * Creates a new data object with a tracked child node of the given parent |
| * node. If such a child node already exists, it is used. Otherwise, a new |
| * one is created. |
| * |
| * @param current the current {@code TreeData} object |
| * @param parent the parent node |
| * @param childName the name of the child node |
| * @param resolver the {@code NodeKeyResolver} |
| * @param refSelector here the newly created {@code NodeSelector} is |
| * returned |
| * @return the new {@code TreeData} instance |
| */ |
| private static TreeData createDataWithTrackedChildNode(final TreeData current, |
| final ImmutableNode parent, final String childName, |
| final NodeKeyResolver<ImmutableNode> resolver, |
| final MutableObject<NodeSelector> refSelector) |
| { |
| final TreeData newData; |
| final List<ImmutableNode> namedChildren = |
| current.getChildren(parent, childName); |
| if (!namedChildren.isEmpty()) |
| { |
| newData = |
| updateDataWithNewTrackedNode(current, namedChildren.get(0), |
| resolver, refSelector); |
| } |
| else |
| { |
| final ImmutableNode child = |
| new ImmutableNode.Builder().name(childName).create(); |
| final ModelTransaction tx = new ModelTransaction(current, null, resolver); |
| tx.addAddNodeOperation(parent, child); |
| newData = |
| updateDataWithNewTrackedNode(tx.execute(), child, resolver, |
| refSelector); |
| } |
| return newData; |
| } |
| |
| /** |
| * Checks whether the specified collection with values is not empty. |
| * |
| * @param values the collection with node values |
| * @return <b>true</b> if values are provided, <b>false</b> otherwise |
| */ |
| private static boolean valuesNotEmpty(final Iterable<?> values) |
| { |
| return values.iterator().hasNext(); |
| } |
| |
| /** |
| * Creates an exception referring to an invalid key for adding properties. |
| * Such an exception is thrown when an operation tries to add something to |
| * an attribute. |
| * |
| * @param key the invalid key causing this exception |
| * @return the exception |
| */ |
| private static RuntimeException attributeKeyException(final String key) |
| { |
| return new IllegalArgumentException( |
| "New nodes cannot be added to an attribute key: " + key); |
| } |
| |
| /** |
| * An interface used internally for handling concurrent updates. An |
| * implementation has to populate the passed in {@code ModelTransaction}. |
| * The transaction is then executed, and an atomic update of the model's |
| * {@code TreeData} is attempted. If this fails - because another update |
| * came across -, the whole operation has to be tried anew. |
| */ |
| private interface TransactionInitializer |
| { |
| /** |
| * Initializes the specified transaction for an update operation. The |
| * return value indicates whether the transaction should be executed. A |
| * result of <b>false</b> means that the update is to be aborted (maybe |
| * another update method was called). |
| * |
| * @param tx the transaction to be initialized |
| * @return a flag whether the update should continue |
| */ |
| boolean initTransaction(ModelTransaction tx); |
| } |
| } |