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