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 }