View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.tree;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.concurrent.atomic.AtomicReference;
28  
29  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
30  import org.apache.commons.lang3.mutable.Mutable;
31  import org.apache.commons.lang3.mutable.MutableObject;
32  
33  /**
34   * <p>
35   * A specialized node model implementation which operates on {@link ImmutableNode} structures.
36   * </p>
37   * <p>
38   * This {@code NodeModel} implementation keeps all its data as a tree of {@link ImmutableNode} objects in memory. The
39   * managed structure can be manipulated in a thread-safe, non-blocking way. This is achieved by using atomic variables:
40   * The root of the tree is stored in an atomic reference variable. Each update operation causes a new structure to be
41   * constructed (which reuses as much from the original structure as possible). The old root node is then replaced by the
42   * new one using an atomic compare-and-set operation. If this fails, the manipulation has to be done anew on the updated
43   * structure.
44   * </p>
45   *
46   * @since 2.0
47   */
48  public class InMemoryNodeModel implements NodeModel<ImmutableNode> {
49      /**
50       * A dummy node handler instance used in operations which require only a limited functionality.
51       */
52      private static final NodeHandler<ImmutableNode> DUMMY_HANDLER = new TreeData(null, Collections.<ImmutableNode, ImmutableNode>emptyMap(),
53          Collections.<ImmutableNode, ImmutableNode>emptyMap(), null, new ReferenceTracker());
54  
55      /** Stores information about the current nodes structure. */
56      private final AtomicReference<TreeData> structure;
57  
58      /**
59       * Creates a new instance of {@code InMemoryNodeModel} which is initialized with an empty root node.
60       */
61      public InMemoryNodeModel() {
62          this(null);
63      }
64  
65      /**
66       * Creates a new instance of {@code InMemoryNodeModel} and initializes it from the given root node. If the passed in
67       * node is <b>null</b>, a new, empty root node is created.
68       *
69       * @param root the new root node for this model
70       */
71      public InMemoryNodeModel(final ImmutableNode root) {
72          structure = new AtomicReference<>(createTreeData(initialRootNode(root), null));
73      }
74  
75      /**
76       * Gets the root node of this mode. Note: This method should be used with care. The model may be updated concurrently
77       * which causes the root node to be replaced. If the root node is to be processed further (e.g. by executing queries on
78       * it), the model should be asked for its {@code NodeHandler}, and the root node should be obtained from there. The
79       * connection between a node handler and its root node remain constant because an update of the model causes the whole
80       * node handler to be replaced.
81       *
82       * @return the current root node
83       */
84      public ImmutableNode getRootNode() {
85          return getTreeData().getRootNode();
86      }
87  
88      /**
89       * {@inheritDoc} {@code InMemoryNodeModel} implements the {@code NodeHandler} interface itself. So this implementation
90       * just returns the <strong>this</strong> reference.
91       */
92      @Override
93      public NodeHandler<ImmutableNode> getNodeHandler() {
94          return getReferenceNodeHandler();
95      }
96  
97      @Override
98      public void addProperty(final String key, final Iterable<?> values, final NodeKeyResolver<ImmutableNode> resolver) {
99          addProperty(key, null, values, resolver);
100     }
101 
102     /**
103      * Adds new property values using a tracked node as root node. This method works like the normal {@code addProperty()}
104      * method, but the origin of the operation (also for the interpretation of the passed in key) is a tracked node
105      * identified by the passed in {@code NodeSelector}. The selector can be <b>null</b>, then the root node is assumed.
106      *
107      * @param key the key
108      * @param selector the {@code NodeSelector} defining the root node (or <b>null</b>)
109      * @param values the values to be added
110      * @param resolver the {@code NodeKeyResolver}
111      * @throws ConfigurationRuntimeException if the selector cannot be resolved
112      */
113     public void addProperty(final String key, final NodeSelector selector, final Iterable<?> values, final NodeKeyResolver<ImmutableNode> resolver) {
114         if (valuesNotEmpty(values)) {
115             updateModel(tx -> {
116                 initializeAddTransaction(tx, key, values, resolver);
117                 return true;
118             }, selector, resolver);
119         }
120     }
121 
122     @Override
123     public void addNodes(final String key, final Collection<? extends ImmutableNode> nodes, final NodeKeyResolver<ImmutableNode> resolver) {
124         addNodes(key, null, nodes, resolver);
125     }
126 
127     /**
128      * Adds new nodes using a tracked node as root node. This method works like the normal {@code addNodes()} method, but
129      * the origin of the operation (also for the interpretation of the passed in key) is a tracked node identified by the
130      * passed in {@code NodeSelector}. The selector can be <b>null</b>, then the root node is assumed.
131      *
132      * @param key the key
133      * @param selector the {@code NodeSelector} defining the root node (or <b>null</b>)
134      * @param nodes the collection of new nodes to be added
135      * @param resolver the {@code NodeKeyResolver}
136      * @throws ConfigurationRuntimeException if the selector cannot be resolved
137      */
138     public void addNodes(final String key, final NodeSelector selector, final Collection<? extends ImmutableNode> nodes,
139         final NodeKeyResolver<ImmutableNode> resolver) {
140         if (nodes != null && !nodes.isEmpty()) {
141             updateModel(tx -> {
142                 final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(tx.getQueryRoot(), key, tx.getCurrentData());
143                 if (results.size() == 1) {
144                     if (results.get(0).isAttributeResult()) {
145                         throw attributeKeyException(key);
146                     }
147                     tx.addAddNodesOperation(results.get(0).getNode(), nodes);
148                 } else {
149                     final NodeAddData<ImmutableNode> addData = resolver.resolveAddKey(tx.getQueryRoot(), key, tx.getCurrentData());
150                     if (addData.isAttribute()) {
151                         throw attributeKeyException(key);
152                     }
153                     final ImmutableNode newNode = new ImmutableNode.Builder(nodes.size()).name(addData.getNewNodeName()).addChildren(nodes).create();
154                     addNodesByAddData(tx, addData, Collections.singleton(newNode));
155                 }
156                 return true;
157             }, selector, resolver);
158         }
159     }
160 
161     @Override
162     public void setProperty(final String key, final Object value, final NodeKeyResolver<ImmutableNode> resolver) {
163         setProperty(key, null, value, resolver);
164     }
165 
166     /**
167      * Sets the value of a property using a tracked node as root node. This method works like the normal
168      * {@code setProperty()} method, but the origin of the operation (also for the interpretation of the passed in key) is a
169      * tracked node identified by the passed in {@code NodeSelector}. The selector can be <b>null</b>, then the root node is
170      * assumed.
171      *
172      * @param key the key
173      * @param selector the {@code NodeSelector} defining the root node (or <b>null</b>)
174      * @param value the new value for this property
175      * @param resolver the {@code NodeKeyResolver}
176      * @throws ConfigurationRuntimeException if the selector cannot be resolved
177      */
178     public void setProperty(final String key, final NodeSelector selector, final Object value, final NodeKeyResolver<ImmutableNode> resolver) {
179         updateModel(tx -> {
180             boolean added = false;
181             final NodeUpdateData<ImmutableNode> updateData = resolver.resolveUpdateKey(tx.getQueryRoot(), key, value, tx.getCurrentData());
182             if (!updateData.getNewValues().isEmpty()) {
183                 initializeAddTransaction(tx, key, updateData.getNewValues(), resolver);
184                 added = true;
185             }
186             final boolean cleared = initializeClearTransaction(tx, updateData.getRemovedNodes());
187             final boolean updated = initializeUpdateTransaction(tx, updateData.getChangedValues());
188             return added || cleared || updated;
189         }, selector, resolver);
190     }
191 
192     /**
193      * {@inheritDoc} This implementation checks whether nodes become undefined after subtrees have been removed. If this is
194      * the case, such nodes are removed, too. Return value is a collection with {@code QueryResult} objects for the elements
195      * to be removed from the model.
196      */
197     @Override
198     public List<QueryResult<ImmutableNode>> clearTree(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
199         return clearTree(key, null, resolver);
200     }
201 
202     /**
203      * Clears a whole sub tree using a tracked node as root node. This method works like the normal {@code clearTree()}
204      * method, but the origin of the operation (also for the interpretation of the passed in key) is a tracked node
205      * identified by the passed in {@code NodeSelector}. The selector can be <b>null</b>, then the root node is assumed.
206      *
207      * @param key the key
208      * @param selector the {@code NodeSelector} defining the root node (or <b>null</b>)
209      * @param resolver the {@code NodeKeyResolver}
210      * @return a list with the results to be removed
211      * @throws ConfigurationRuntimeException if the selector cannot be resolved
212      */
213     public List<QueryResult<ImmutableNode>> clearTree(final String key, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
214         final List<QueryResult<ImmutableNode>> removedElements = new LinkedList<>();
215         updateModel(tx -> {
216             boolean changes = false;
217             final TreeData currentStructure = tx.getCurrentData();
218             final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(tx.getQueryRoot(), key, currentStructure);
219             removedElements.clear();
220             removedElements.addAll(results);
221             for (final QueryResult<ImmutableNode> result : results) {
222                 if (result.isAttributeResult()) {
223                     tx.addRemoveAttributeOperation(result.getNode(), result.getAttributeName());
224                 } else {
225                     if (result.getNode() == currentStructure.getRootNode()) {
226                         // the whole model is to be cleared
227                         clear(resolver);
228                         return false;
229                     }
230                     tx.addRemoveNodeOperation(currentStructure.getParent(result.getNode()), result.getNode());
231                 }
232                 changes = true;
233             }
234             return changes;
235         }, selector, resolver);
236 
237         return removedElements;
238     }
239 
240     /**
241      * {@inheritDoc} If this operation leaves an affected node in an undefined state, it is removed from the model.
242      */
243     @Override
244     public void clearProperty(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
245         clearProperty(key, null, resolver);
246     }
247 
248     /**
249      * Clears a property using a tracked node as root node. This method works like the normal {@code clearProperty()}
250      * method, but the origin of the operation (also for the interpretation of the passed in key) is a tracked node
251      * identified by the passed in {@code NodeSelector}. The selector can be <b>null</b>, then the root node is assumed.
252      *
253      * @param key the key
254      * @param selector the {@code NodeSelector} defining the root node (or <b>null</b>)
255      * @param resolver the {@code NodeKeyResolver}
256      * @throws ConfigurationRuntimeException if the selector cannot be resolved
257      */
258     public void clearProperty(final String key, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
259         updateModel(tx -> {
260             final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(tx.getQueryRoot(), key, tx.getCurrentData());
261             return initializeClearTransaction(tx, results);
262         }, selector, resolver);
263     }
264 
265     /**
266      * {@inheritDoc} A new empty root node is created with the same name as the current root node. Implementation note:
267      * Because this is a hard reset the usual dance for dealing with concurrent updates is not required here.
268      *
269      * @param resolver the {@code NodeKeyResolver}
270      */
271     @Override
272     public void clear(final NodeKeyResolver<ImmutableNode> resolver) {
273         final ImmutableNode newRoot = new ImmutableNode.Builder().name(getRootNode().getNodeName()).create();
274         setRootNode(newRoot);
275     }
276 
277     /**
278      * {@inheritDoc} This implementation simply returns the current root node of this model.
279      */
280     @Override
281     public ImmutableNode getInMemoryRepresentation() {
282         return getTreeData().getRootNode();
283     }
284 
285     /**
286      * {@inheritDoc} All tracked nodes and reference objects managed by this model are cleared.Care has to be taken when
287      * this method is used and the model is accessed by multiple threads. It is not deterministic which concurrent
288      * operations see the old root and which see the new root node.
289      *
290      * @param newRoot the new root node to be set (can be <b>null</b>, then an empty root node is set)
291      */
292     @Override
293     public void setRootNode(final ImmutableNode newRoot) {
294         structure.set(createTreeData(initialRootNode(newRoot), structure.get()));
295     }
296 
297     /**
298      * Replaces the root node of this model. This method is similar to {@link #setRootNode(ImmutableNode)}; however, tracked
299      * nodes will not get lost. The model applies the selectors of all tracked nodes on the new nodes hierarchy, so that
300      * corresponding nodes are selected (this may cause nodes to become detached if a select operation fails). This
301      * operation is useful if the new nodes hierarchy to be set is known to be similar to the old one. Note that reference
302      * objects are lost; there is no way to automatically match nodes between the old and the new nodes hierarchy.
303      *
304      * @param newRoot the new root node to be set (must not be <b>null</b>)
305      * @param resolver the {@code NodeKeyResolver}
306      * @throws IllegalArgumentException if the new root node is <b>null</b>
307      */
308     public void replaceRoot(final ImmutableNode newRoot, final NodeKeyResolver<ImmutableNode> resolver) {
309         if (newRoot == null) {
310             throw new IllegalArgumentException("Replaced root node must not be null!");
311         }
312 
313         final TreeData current = structure.get();
314         // this step is needed to get a valid NodeHandler
315         final TreeData temp = createTreeDataForRootAndTracker(newRoot, current.getNodeTracker());
316         structure.set(temp.updateNodeTracker(temp.getNodeTracker().update(newRoot, null, resolver, temp)));
317     }
318 
319     /**
320      * Merges the root node of this model with the specified node. This method is typically caused by configuration
321      * implementations when a configuration source is loaded, and its data has to be added to the model. It is possible to
322      * define the new name of the root node and to pass in a map with reference objects.
323      *
324      * @param node the node to be merged with the root node
325      * @param rootName the new name of the root node; can be <b>null</b>, then the name of the root node is not changed
326      *        unless it is <b>null</b>
327      * @param references an optional map with reference objects
328      * @param rootRef an optional reference object for the new root node
329      * @param resolver the {@code NodeKeyResolver}
330      */
331     public void mergeRoot(final ImmutableNode node, final String rootName, final Map<ImmutableNode, ?> references, final Object rootRef,
332         final NodeKeyResolver<ImmutableNode> resolver) {
333         updateModel(tx -> {
334             final TreeData current = tx.getCurrentData();
335             final String newRootName = determineRootName(current.getRootNode(), node, rootName);
336             if (newRootName != null) {
337                 tx.addChangeNodeNameOperation(current.getRootNode(), newRootName);
338             }
339             tx.addAddNodesOperation(current.getRootNode(), node.getChildren());
340             tx.addAttributesOperation(current.getRootNode(), node.getAttributes());
341             if (node.getValue() != null) {
342                 tx.addChangeNodeValueOperation(current.getRootNode(), node.getValue());
343             }
344             if (references != null) {
345                 tx.addNewReferences(references);
346             }
347             if (rootRef != null) {
348                 tx.addNewReference(current.getRootNode(), rootRef);
349             }
350             return true;
351         }, null, resolver);
352     }
353 
354     /**
355      * Adds a node to be tracked. After this method has been called with a specific {@code NodeSelector}, the node
356      * associated with this key can be always obtained using {@link #getTrackedNode(NodeSelector)} with the same selector.
357      * This is useful because during updates of a model parts of the structure are replaced. Therefore, it is not a good
358      * idea to simply hold a reference to a node; this might become outdated soon. Rather, the node should be tracked. This
359      * mechanism ensures that always the correct node reference can be obtained.
360      *
361      * @param selector the {@code NodeSelector} defining the desired node
362      * @param resolver the {@code NodeKeyResolver}
363      * @throws ConfigurationRuntimeException if the selector does not select a single node
364      */
365     public void trackNode(final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
366         boolean done;
367         do {
368             final TreeData current = structure.get();
369             final NodeTracker newTracker = current.getNodeTracker().trackNode(current.getRootNode(), selector, resolver, current);
370             done = structure.compareAndSet(current, current.updateNodeTracker(newTracker));
371         } while (!done);
372     }
373 
374     /**
375      * Allows tracking all nodes selected by a key. This method evaluates the specified key on the current nodes structure.
376      * For all selected nodes corresponding {@code NodeSelector} objects are created, and they are tracked. The returned
377      * collection of {@code NodeSelector} objects can be used for interacting with the selected nodes.
378      *
379      * @param key the key for selecting the nodes to track
380      * @param resolver the {@code NodeKeyResolver}
381      * @return a collection with the {@code NodeSelector} objects for the new tracked nodes
382      */
383     public Collection<NodeSelector> selectAndTrackNodes(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
384         final Mutable<Collection<NodeSelector>> refSelectors = new MutableObject<>();
385         boolean done;
386         do {
387             final TreeData current = structure.get();
388             final List<ImmutableNode> nodes = resolver.resolveNodeKey(current.getRootNode(), key, current);
389             if (nodes.isEmpty()) {
390                 return Collections.emptyList();
391             }
392             done = structure.compareAndSet(current, createSelectorsForTrackedNodes(refSelectors, nodes, current, resolver));
393         } while (!done);
394         return refSelectors.getValue();
395     }
396 
397     /**
398      * Tracks all nodes which are children of the node selected by the passed in key. If the key selects exactly one node,
399      * for all children of this node {@code NodeSelector} objects are created, and they become tracked nodes. The returned
400      * collection of {@code NodeSelector} objects can be used for interacting with the selected nodes.
401      *
402      * @param key the key for selecting the parent node whose children are to be tracked
403      * @param resolver the {@code NodeKeyResolver}
404      * @return a collection with the {@code NodeSelector} objects for the new tracked nodes
405      */
406     public Collection<NodeSelector> trackChildNodes(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
407         final Mutable<Collection<NodeSelector>> refSelectors = new MutableObject<>();
408         boolean done;
409         do {
410             refSelectors.setValue(Collections.<NodeSelector>emptyList());
411             final TreeData current = structure.get();
412             final List<ImmutableNode> nodes = resolver.resolveNodeKey(current.getRootNode(), key, current);
413             if (nodes.size() == 1) {
414                 final ImmutableNode node = nodes.get(0);
415                 done = node.getChildren().isEmpty()
416                     || structure.compareAndSet(current, createSelectorsForTrackedNodes(refSelectors, node.getChildren(), current, resolver));
417             } else {
418                 done = true;
419             }
420         } while (!done);
421         return refSelectors.getValue();
422     }
423 
424     /**
425      * Tracks a node which is a child of another node selected by the passed in key. If the selected node has a child node
426      * with this name, it is tracked and its selector is returned. Otherwise, a new child node with this name is created
427      * first.
428      *
429      * @param key the key for selecting the parent node
430      * @param childName the name of the child node
431      * @param resolver the {@code NodeKeyResolver}
432      * @return the {@code NodeSelector} for the tracked child node
433      * @throws ConfigurationRuntimeException if the passed in key does not select a single node
434      */
435     public NodeSelector trackChildNodeWithCreation(final String key, final String childName, final NodeKeyResolver<ImmutableNode> resolver) {
436         final MutableObject<NodeSelector> refSelector = new MutableObject<>();
437         boolean done;
438 
439         do {
440             final TreeData current = structure.get();
441             final List<ImmutableNode> nodes = resolver.resolveNodeKey(current.getRootNode(), key, current);
442             if (nodes.size() != 1) {
443                 throw new ConfigurationRuntimeException("Key does not select a single node: " + key);
444             }
445 
446             final ImmutableNode parent = nodes.get(0);
447             final TreeData newData = createDataWithTrackedChildNode(current, parent, childName, resolver, refSelector);
448 
449             done = structure.compareAndSet(current, newData);
450         } while (!done);
451 
452         return refSelector.getValue();
453     }
454 
455     /**
456      * Gets the current {@code ImmutableNode} instance associated with the given {@code NodeSelector}. The node must be a
457      * tracked node, i.e. {@link #trackNode(NodeSelector, NodeKeyResolver)} must have been called before with the given
458      * selector.
459      *
460      * @param selector the {@code NodeSelector} defining the desired node
461      * @return the current {@code ImmutableNode} associated with this selector
462      * @throws ConfigurationRuntimeException if the selector is unknown
463      */
464     public ImmutableNode getTrackedNode(final NodeSelector selector) {
465         return structure.get().getNodeTracker().getTrackedNode(selector);
466     }
467 
468     /**
469      * Replaces a tracked node by another node. If the tracked node is not yet detached, it becomes now detached. The passed
470      * in node (which must not be <b>null</b>) becomes the new root node of an independent model for this tracked node.
471      * Further updates of this model do not affect the tracked node's model and vice versa.
472      *
473      * @param selector the {@code NodeSelector} defining the tracked node
474      * @param newNode the node replacing the tracked node (must not be <b>null</b>)
475      * @throws ConfigurationRuntimeException if the selector cannot be resolved
476      * @throws IllegalArgumentException if the replacement node is <b>null</b>
477      */
478     public void replaceTrackedNode(final NodeSelector selector, final ImmutableNode newNode) {
479         if (newNode == null) {
480             throw new IllegalArgumentException("Replacement node must not be null!");
481         }
482 
483         boolean done;
484         do {
485             final TreeData currentData = structure.get();
486             done = replaceDetachedTrackedNode(currentData, selector, newNode) || replaceActiveTrackedNode(currentData, selector, newNode);
487         } while (!done);
488     }
489 
490     /**
491      * Gets a {@code NodeHandler} for a tracked node. Such a handler may be required for operations on a sub tree of the
492      * model. The handler to be returned depends on the current state of the tracked node. If it is still active, a handler
493      * is used which shares some data (especially the parent mapping) with this model. Detached track nodes in contrast have
494      * their own separate model; in this case a handler associated with this model is returned.
495      *
496      * @param selector the {@code NodeSelector} defining the tracked node
497      * @return a {@code NodeHandler} for this tracked node
498      * @throws ConfigurationRuntimeException if the selector is unknown
499      */
500     public NodeHandler<ImmutableNode> getTrackedNodeHandler(final NodeSelector selector) {
501         final TreeData currentData = structure.get();
502         final InMemoryNodeModel detachedNodeModel = currentData.getNodeTracker().getDetachedNodeModel(selector);
503         return detachedNodeModel != null ? detachedNodeModel.getNodeHandler()
504             : new TrackedNodeHandler(currentData.getNodeTracker().getTrackedNode(selector), currentData);
505     }
506 
507     /**
508      * Returns a flag whether the specified tracked node is detached. As long as the {@code NodeSelector} associated with
509      * that node returns a single instance, the tracked node is said to be <em>life</em>. If now an update of the model
510      * happens which invalidates the selector (maybe the target node was removed), the tracked node becomes detached. It is
511      * still possible to query the node; here the latest valid instance is returned. But further changes on the node model
512      * are no longer tracked for this node. So even if there are further changes which would make the {@code NodeSelector}
513      * valid again, the tracked node stays in detached state.
514      *
515      * @param selector the {@code NodeSelector} defining the desired node
516      * @return a flag whether this tracked node is in detached state
517      * @throws ConfigurationRuntimeException if the selector is unknown
518      */
519     public boolean isTrackedNodeDetached(final NodeSelector selector) {
520         return structure.get().getNodeTracker().isTrackedNodeDetached(selector);
521     }
522 
523     /**
524      * Removes a tracked node. This method is the opposite of {@code trackNode()}. It has to be called if there is no longer
525      * the need to track a specific node. Note that for each call of {@code trackNode()} there has to be a corresponding
526      * {@code untrackNode()} call. This ensures that multiple observers can track the same node.
527      *
528      * @param selector the {@code NodeSelector} defining the desired node
529      * @throws ConfigurationRuntimeException if the specified node is not tracked
530      */
531     public void untrackNode(final NodeSelector selector) {
532         boolean done;
533         do {
534             final TreeData current = structure.get();
535             final NodeTracker newTracker = current.getNodeTracker().untrackNode(selector);
536             done = structure.compareAndSet(current, current.updateNodeTracker(newTracker));
537         } while (!done);
538     }
539 
540     /**
541      * Gets a {@code ReferenceNodeHandler} object for this model. This extended node handler can be used to query
542      * references objects stored for this model.
543      *
544      * @return the {@code ReferenceNodeHandler}
545      */
546     public ReferenceNodeHandler getReferenceNodeHandler() {
547         return getTreeData();
548     }
549 
550     /**
551      * Gets the current {@code TreeData} object. This object contains all information about the current node structure.
552      *
553      * @return the current {@code TreeData} object
554      */
555     TreeData getTreeData() {
556         return structure.get();
557     }
558 
559     /**
560      * Updates the mapping from nodes to their parents for the passed in hierarchy of nodes. This method traverses all
561      * children and grand-children of the passed in root node. For each node in the subtree the parent relation is added to
562      * the map.
563      *
564      * @param parents the map with parent nodes
565      * @param root the root node of the current tree
566      */
567     static void updateParentMapping(final Map<ImmutableNode, ImmutableNode> parents, final ImmutableNode root) {
568         NodeTreeWalker.INSTANCE.walkBFS(root, new ConfigurationNodeVisitorAdapter<ImmutableNode>() {
569             @Override
570             public void visitBeforeChildren(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
571                 node.forEach(c -> parents.put(c, node));
572             }
573         }, DUMMY_HANDLER);
574     }
575 
576     /**
577      * Checks if the passed in node is defined. Result is <b>true</b> if the node contains any data.
578      *
579      * @param node the node in question
580      * @return <b>true</b> if the node is defined, <b>false</b> otherwise
581      */
582     static boolean checkIfNodeDefined(final ImmutableNode node) {
583         return node.getValue() != null || !node.getChildren().isEmpty() || !node.getAttributes().isEmpty();
584     }
585 
586     /**
587      * Initializes a transaction for an add operation.
588      *
589      * @param tx the transaction to be initialized
590      * @param key the key
591      * @param values the collection with node values
592      * @param resolver the {@code NodeKeyResolver}
593      */
594     private void initializeAddTransaction(final ModelTransaction tx, final String key, final Iterable<?> values,
595         final NodeKeyResolver<ImmutableNode> resolver) {
596         final NodeAddData<ImmutableNode> addData = resolver.resolveAddKey(tx.getQueryRoot(), key, tx.getCurrentData());
597         if (addData.isAttribute()) {
598             addAttributeProperty(tx, addData, values);
599         } else {
600             addNodeProperty(tx, addData, values);
601         }
602     }
603 
604     /**
605      * Creates a {@code TreeData} object for the specified root node.
606      *
607      * @param root the root node of the current tree
608      * @param current the current {@code TreeData} object (may be <b>null</b>)
609      * @return the {@code TreeData} describing the current tree
610      */
611     private TreeData createTreeData(final ImmutableNode root, final TreeData current) {
612         final NodeTracker newTracker = current != null ? current.getNodeTracker().detachAllTrackedNodes() : new NodeTracker();
613         return createTreeDataForRootAndTracker(root, newTracker);
614     }
615 
616     /**
617      * Creates a {@code TreeData} object for the specified root node and {@code NodeTracker}. Other parameters are set to
618      * default values.
619      *
620      * @param root the new root node for this model
621      * @param newTracker the new {@code NodeTracker}
622      * @return the new {@code TreeData} object
623      */
624     private TreeData createTreeDataForRootAndTracker(final ImmutableNode root, final NodeTracker newTracker) {
625         return new TreeData(root, createParentMapping(root), Collections.<ImmutableNode, ImmutableNode>emptyMap(), newTracker, new ReferenceTracker());
626     }
627 
628     /**
629      * Handles an add property operation if the property to be added is a node.
630      *
631      * @param tx the transaction
632      * @param addData the {@code NodeAddData}
633      * @param values the collection with node values
634      */
635     private static void addNodeProperty(final ModelTransaction tx, final NodeAddData<ImmutableNode> addData, final Iterable<?> values) {
636         final Collection<ImmutableNode> newNodes = createNodesToAdd(addData.getNewNodeName(), values);
637         addNodesByAddData(tx, addData, newNodes);
638     }
639 
640     /**
641      * Initializes a transaction to add a collection of nodes as described by a {@code NodeAddData} object. If necessary,
642      * new path nodes are created. Eventually, the new nodes are added as children to the specified target node.
643      *
644      * @param tx the transaction
645      * @param addData the {@code NodeAddData}
646      * @param newNodes the collection of new child nodes
647      */
648     private static void addNodesByAddData(final ModelTransaction tx, final NodeAddData<ImmutableNode> addData, final Collection<ImmutableNode> newNodes) {
649         if (addData.getPathNodes().isEmpty()) {
650             tx.addAddNodesOperation(addData.getParent(), newNodes);
651         } else {
652             final ImmutableNode newChild = createNodeToAddWithPath(addData, newNodes);
653             tx.addAddNodeOperation(addData.getParent(), newChild);
654         }
655     }
656 
657     /**
658      * Handles an add property operation if the property to be added is an attribute.
659      *
660      * @param tx the transaction
661      * @param addData the {@code NodeAddData}
662      * @param values the collection with node values
663      */
664     private static void addAttributeProperty(final ModelTransaction tx, final NodeAddData<ImmutableNode> addData, final Iterable<?> values) {
665         if (addData.getPathNodes().isEmpty()) {
666             tx.addAttributeOperation(addData.getParent(), addData.getNewNodeName(), values.iterator().next());
667         } else {
668             final int pathNodeCount = addData.getPathNodes().size();
669             final ImmutableNode childWithAttribute = new ImmutableNode.Builder().name(addData.getPathNodes().get(pathNodeCount - 1))
670                 .addAttribute(addData.getNewNodeName(), values.iterator().next()).create();
671             final ImmutableNode newChild = pathNodeCount > 1
672                 ? createNodeOnPath(addData.getPathNodes().subList(0, pathNodeCount - 1).iterator(), Collections.singleton(childWithAttribute))
673                 : childWithAttribute;
674             tx.addAddNodeOperation(addData.getParent(), newChild);
675         }
676     }
677 
678     /**
679      * Creates a collection with new nodes with a given name and a value from a given collection.
680      *
681      * @param newNodeName the name of the new nodes
682      * @param values the collection with node values
683      * @return the newly created collection
684      */
685     private static Collection<ImmutableNode> createNodesToAdd(final String newNodeName, final Iterable<?> values) {
686         final Collection<ImmutableNode> nodes = new LinkedList<>();
687         values.forEach(value -> nodes.add(new ImmutableNode.Builder().name(newNodeName).value(value).create()));
688         return nodes;
689     }
690 
691     /**
692      * Creates a node structure consisting of the path nodes defined by the passed in {@code NodeAddData} instance and all
693      * new child nodes.
694      *
695      * @param addData the {@code NodeAddData}
696      * @param newNodes the collection of new child nodes
697      * @return the parent node of the newly created hierarchy
698      */
699     private static ImmutableNode createNodeToAddWithPath(final NodeAddData<ImmutableNode> addData, final Collection<ImmutableNode> newNodes) {
700         return createNodeOnPath(addData.getPathNodes().iterator(), newNodes);
701     }
702 
703     /**
704      * Recursive helper method for creating a path node for an add operation. All path nodes except for the last have a
705      * single child. The last path node has the new nodes as children.
706      *
707      * @param it the iterator over the names of the path nodes
708      * @param newNodes the collection of new child nodes
709      * @return the newly created path node
710      */
711     private static ImmutableNode createNodeOnPath(final Iterator<String> it, final Collection<ImmutableNode> newNodes) {
712         final String nodeName = it.next();
713         final ImmutableNode.Builder builder;
714         if (it.hasNext()) {
715             builder = new ImmutableNode.Builder(1);
716             builder.addChild(createNodeOnPath(it, newNodes));
717         } else {
718             builder = new ImmutableNode.Builder(newNodes.size());
719             builder.addChildren(newNodes);
720         }
721         return builder.name(nodeName).create();
722     }
723 
724     /**
725      * Initializes a transaction to clear the values of a property based on the passed in collection of affected results.
726      *
727      * @param tx the transaction to be initialized
728      * @param results a collection with results pointing to the nodes to be cleared
729      * @return a flag whether there are elements to be cleared
730      */
731     private static boolean initializeClearTransaction(final ModelTransaction tx, final Collection<QueryResult<ImmutableNode>> results) {
732         results.forEach(result -> {
733             if (result.isAttributeResult()) {
734                 tx.addRemoveAttributeOperation(result.getNode(), result.getAttributeName());
735             } else {
736                 tx.addClearNodeValueOperation(result.getNode());
737             }
738         });
739 
740         return !results.isEmpty();
741     }
742 
743     /**
744      * Initializes a transaction to change the values of some query results based on the passed in map.
745      *
746      * @param tx the transaction to be initialized
747      * @param changedValues the map defining the elements to be changed
748      * @return a flag whether there are elements to be updated
749      */
750     private static boolean initializeUpdateTransaction(final ModelTransaction tx, final Map<QueryResult<ImmutableNode>, Object> changedValues) {
751         changedValues.forEach((k, v) -> {
752             final ImmutableNode node = k.getNode();
753             if (k.isAttributeResult()) {
754                 tx.addAttributeOperation(node, k.getAttributeName(), v);
755             } else {
756                 tx.addChangeNodeValueOperation(node, v);
757             }
758         });
759 
760         return !changedValues.isEmpty();
761     }
762 
763     /**
764      * Determines the initial root node of this model. If a root node has been provided, it is used. Otherwise, an empty
765      * dummy root node is created.
766      *
767      * @param providedRoot the passed in root node
768      * @return the root node to be used
769      */
770     private static ImmutableNode initialRootNode(final ImmutableNode providedRoot) {
771         return providedRoot != null ? providedRoot : new ImmutableNode.Builder().create();
772     }
773 
774     /**
775      * Determines the name of the root node for a merge operation. If a root name is provided, it is used. Otherwise, if the
776      * 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
777      * name has to be set.
778      *
779      * @param rootNode the current root node
780      * @param node the node to be merged with the root node
781      * @param rootName the name of the resulting node
782      * @return the new name of the root node
783      */
784     private static String determineRootName(final ImmutableNode rootNode, final ImmutableNode node, final String rootName) {
785         if (rootName != null) {
786             return rootName;
787         }
788         if (rootNode.getNodeName() == null) {
789             return node.getNodeName();
790         }
791         return null;
792     }
793 
794     /**
795      * Creates the mapping to parent nodes for the nodes structured represented by the passed in root node. Each node is
796      * assigned its parent node. Here an iterative algorithm is used rather than a recursive one to avoid stack overflow for
797      * huge structures.
798      *
799      * @param root the root node of the structure
800      * @return the parent node mapping
801      */
802     private Map<ImmutableNode, ImmutableNode> createParentMapping(final ImmutableNode root) {
803         final Map<ImmutableNode, ImmutableNode> parents = new HashMap<>();
804         updateParentMapping(parents, root);
805         return parents;
806     }
807 
808     /**
809      * Performs a non-blocking, thread-safe update of this model based on a transaction initialized by the passed in
810      * initializer. This method uses the atomic reference for the model's current data to ensure that an update was
811      * successful even if the model is concurrently accessed.
812      *
813      * @param txInit the {@code TransactionInitializer}
814      * @param selector an optional {@code NodeSelector} defining the target node of the transaction
815      * @param resolver the {@code NodeKeyResolver}
816      */
817     private void updateModel(final TransactionInitializer txInit, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
818         boolean done;
819 
820         do {
821             final TreeData currentData = getTreeData();
822             done = executeTransactionOnDetachedTrackedNode(txInit, selector, currentData, resolver)
823                 || executeTransactionOnCurrentStructure(txInit, selector, currentData, resolver);
824         } while (!done);
825     }
826 
827     /**
828      * Executes a transaction on the current data of this model. This method is called if an operation is to be executed on
829      * the model's root node or a tracked node which is not yet detached.
830      *
831      * @param txInit the {@code TransactionInitializer}
832      * @param selector an optional {@code NodeSelector} defining the target node
833      * @param currentData the current data of the model
834      * @param resolver the {@code NodeKeyResolver}
835      * @return a flag whether the operation has been completed successfully
836      */
837     private boolean executeTransactionOnCurrentStructure(final TransactionInitializer txInit, final NodeSelector selector, final TreeData currentData,
838         final NodeKeyResolver<ImmutableNode> resolver) {
839         final boolean done;
840         final ModelTransaction tx = new ModelTransaction(currentData, selector, resolver);
841         if (!txInit.initTransaction(tx)) {
842             done = true;
843         } else {
844             final TreeData newData = tx.execute();
845             done = structure.compareAndSet(tx.getCurrentData(), newData);
846         }
847         return done;
848     }
849 
850     /**
851      * Tries to execute a transaction on the model of a detached tracked node. This method checks whether the target node of
852      * the transaction is a tracked node and if this node is already detached. If this is the case, the update operation is
853      * independent on this model and has to be executed on the specific model for the detached node.
854      *
855      * @param txInit the {@code TransactionInitializer}
856      * @param selector an optional {@code NodeSelector} defining the target node
857      * @param currentData the current data of the model
858      * @param resolver the {@code NodeKeyResolver} @return a flag whether the transaction could be executed
859      * @throws ConfigurationRuntimeException if the selector cannot be resolved
860      */
861     private boolean executeTransactionOnDetachedTrackedNode(final TransactionInitializer txInit, final NodeSelector selector, final TreeData currentData,
862         final NodeKeyResolver<ImmutableNode> resolver) {
863         if (selector != null) {
864             final InMemoryNodeModel detachedNodeModel = currentData.getNodeTracker().getDetachedNodeModel(selector);
865             if (detachedNodeModel != null) {
866                 detachedNodeModel.updateModel(txInit, null, resolver);
867                 return true;
868             }
869         }
870 
871         return false;
872     }
873 
874     /**
875      * Replaces a tracked node if it is already detached.
876      *
877      * @param currentData the current data of the model
878      * @param selector the {@code NodeSelector} defining the tracked node
879      * @param newNode the node replacing the tracked node
880      * @return a flag whether the operation was successful
881      */
882     private boolean replaceDetachedTrackedNode(final TreeData currentData, final NodeSelector selector, final ImmutableNode newNode) {
883         final InMemoryNodeModel detachedNodeModel = currentData.getNodeTracker().getDetachedNodeModel(selector);
884         if (detachedNodeModel != null) {
885             detachedNodeModel.setRootNode(newNode);
886             return true;
887         }
888 
889         return false;
890     }
891 
892     /**
893      * Replaces an active tracked node. The node then becomes detached.
894      *
895      * @param currentData the current data of the model
896      * @param selector the {@code NodeSelector} defining the tracked node
897      * @param newNode the node replacing the tracked node
898      * @return a flag whether the operation was successful
899      */
900     private boolean replaceActiveTrackedNode(final TreeData currentData, final NodeSelector selector, final ImmutableNode newNode) {
901         final NodeTracker newTracker = currentData.getNodeTracker().replaceAndDetachTrackedNode(selector, newNode);
902         return structure.compareAndSet(currentData, currentData.updateNodeTracker(newTracker));
903     }
904 
905     /**
906      * Creates tracked node entries for the specified nodes and creates the corresponding selectors.
907      *
908      * @param refSelectors the reference where to store the selectors
909      * @param nodes the nodes to be tracked
910      * @param current the current {@code TreeData} object
911      * @param resolver the {@code NodeKeyResolver}
912      * @return the updated {@code TreeData} object
913      */
914     private static TreeData createSelectorsForTrackedNodes(final Mutable<Collection<NodeSelector>> refSelectors, final List<ImmutableNode> nodes,
915             final TreeData current, final NodeKeyResolver<ImmutableNode> resolver) {
916         final List<NodeSelector> selectors = new ArrayList<>(nodes.size());
917         final Map<ImmutableNode, String> cache = new HashMap<>();
918         nodes.forEach(node -> selectors.add(new NodeSelector(resolver.nodeKey(node, cache, current))));
919         refSelectors.setValue(selectors);
920         final NodeTracker newTracker = current.getNodeTracker().trackNodes(selectors, nodes);
921         return current.updateNodeTracker(newTracker);
922     }
923 
924     /**
925      * Adds a tracked node that has already been resolved to the specified data object.
926      *
927      * @param current the current {@code TreeData} object
928      * @param node the node in question
929      * @param resolver the {@code NodeKeyResolver}
930      * @param refSelector here the newly created {@code NodeSelector} is returned
931      * @return the new {@code TreeData} instance
932      */
933     private static TreeData updateDataWithNewTrackedNode(final TreeData current, final ImmutableNode node, final NodeKeyResolver<ImmutableNode> resolver,
934         final MutableObject<NodeSelector> refSelector) {
935         final NodeSelector selector = new NodeSelector(resolver.nodeKey(node, new HashMap<>(), current));
936         refSelector.setValue(selector);
937         final NodeTracker newTracker = current.getNodeTracker().trackNodes(Collections.singleton(selector), Collections.singleton(node));
938         return current.updateNodeTracker(newTracker);
939     }
940 
941     /**
942      * Creates a new data object with a tracked child node of the given parent node. If such a child node already exists, it
943      * is used. Otherwise, a new one is created.
944      *
945      * @param current the current {@code TreeData} object
946      * @param parent the parent node
947      * @param childName the name of the child node
948      * @param resolver the {@code NodeKeyResolver}
949      * @param refSelector here the newly created {@code NodeSelector} is returned
950      * @return the new {@code TreeData} instance
951      */
952     private static TreeData createDataWithTrackedChildNode(final TreeData current, final ImmutableNode parent, final String childName,
953         final NodeKeyResolver<ImmutableNode> resolver, final MutableObject<NodeSelector> refSelector) {
954         final TreeData newData;
955         final List<ImmutableNode> namedChildren = current.getChildren(parent, childName);
956         if (!namedChildren.isEmpty()) {
957             newData = updateDataWithNewTrackedNode(current, namedChildren.get(0), resolver, refSelector);
958         } else {
959             final ImmutableNode child = new ImmutableNode.Builder().name(childName).create();
960             final ModelTransaction tx = new ModelTransaction(current, null, resolver);
961             tx.addAddNodeOperation(parent, child);
962             newData = updateDataWithNewTrackedNode(tx.execute(), child, resolver, refSelector);
963         }
964         return newData;
965     }
966 
967     /**
968      * Checks whether the specified collection with values is not empty.
969      *
970      * @param values the collection with node values
971      * @return <b>true</b> if values are provided, <b>false</b> otherwise
972      */
973     private static boolean valuesNotEmpty(final Iterable<?> values) {
974         return values.iterator().hasNext();
975     }
976 
977     /**
978      * Creates an exception referring to an invalid key for adding properties. Such an exception is thrown when an operation
979      * tries to add something to an attribute.
980      *
981      * @param key the invalid key causing this exception
982      * @return the exception
983      */
984     private static IllegalArgumentException attributeKeyException(final String key) {
985         return new IllegalArgumentException("New nodes cannot be added to an attribute key: " + key);
986     }
987 
988     /**
989      * An interface used internally for handling concurrent updates. An implementation has to populate the passed in
990      * {@code ModelTransaction}. The transaction is then executed, and an atomic update of the model's {@code TreeData} is
991      * attempted. If this fails - because another update came across -, the whole operation has to be tried anew.
992      */
993     private interface TransactionInitializer {
994         /**
995          * Initializes the specified transaction for an update operation. The return value indicates whether the transaction
996          * should be executed. A result of <b>false</b> means that the update is to be aborted (maybe another update method was
997          * called).
998          *
999          * @param tx the transaction to be initialized
1000          * @return a flag whether the update should continue
1001          */
1002         boolean initTransaction(ModelTransaction tx);
1003     }
1004 }