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