001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.configuration2.tree; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.concurrent.atomic.AtomicReference; 028 029import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 030import org.apache.commons.lang3.mutable.Mutable; 031import org.apache.commons.lang3.mutable.MutableObject; 032 033/** 034 * <p> 035 * A specialized node model implementation which operates on {@link ImmutableNode} structures. 036 * </p> 037 * <p> 038 * This {@code NodeModel} implementation keeps all its data as a tree of {@link ImmutableNode} objects in memory. The 039 * managed structure can be manipulated in a thread-safe, non-blocking way. This is achieved by using atomic variables: 040 * The root of the tree is stored in an atomic reference variable. Each update operation causes a new structure to be 041 * constructed (which reuses as much from the original structure as possible). The old root node is then replaced by the 042 * new one using an atomic compare-and-set operation. If this fails, the manipulation has to be done anew on the updated 043 * structure. 044 * </p> 045 * 046 * @since 2.0 047 */ 048public class InMemoryNodeModel implements NodeModel<ImmutableNode> { 049 050 /** 051 * An interface used internally for handling concurrent updates. An implementation has to populate the passed in 052 * {@code ModelTransaction}. The transaction is then executed, and an atomic update of the model's {@code TreeData} is 053 * attempted. If this fails - because another update came across -, the whole operation has to be tried anew. 054 */ 055 private interface TransactionInitializer { 056 057 /** 058 * Initializes the specified transaction for an update operation. The return value indicates whether the transaction 059 * should be executed. A result of <strong>false</strong> means that the update is to be aborted (maybe another update method was 060 * called). 061 * 062 * @param tx the transaction to be initialized 063 * @return a flag whether the update should continue 064 */ 065 boolean initTransaction(ModelTransaction tx); 066 } 067 068 /** 069 * A dummy node handler instance used in operations which require only a limited functionality. 070 */ 071 private static final NodeHandler<ImmutableNode> DUMMY_HANDLER = new TreeData(null, Collections.<ImmutableNode, ImmutableNode>emptyMap(), 072 Collections.<ImmutableNode, ImmutableNode>emptyMap(), null, new ReferenceTracker()); 073 074 /** 075 * Handles an add property operation if the property to be added is an attribute. 076 * 077 * @param tx the transaction 078 * @param addData the {@code NodeAddData} 079 * @param values the collection with node values 080 */ 081 private static void addAttributeProperty(final ModelTransaction tx, final NodeAddData<ImmutableNode> addData, final Iterable<?> values) { 082 if (addData.getPathNodes().isEmpty()) { 083 tx.addAttributeOperation(addData.getParent(), addData.getNewNodeName(), values.iterator().next()); 084 } else { 085 final int pathNodeCount = addData.getPathNodes().size(); 086 final ImmutableNode childWithAttribute = new ImmutableNode.Builder().name(addData.getPathNodes().get(pathNodeCount - 1)) 087 .addAttribute(addData.getNewNodeName(), values.iterator().next()).create(); 088 final ImmutableNode newChild = pathNodeCount > 1 089 ? createNodeOnPath(addData.getPathNodes().subList(0, pathNodeCount - 1).iterator(), Collections.singleton(childWithAttribute)) 090 : childWithAttribute; 091 tx.addAddNodeOperation(addData.getParent(), newChild); 092 } 093 } 094 095 /** 096 * Handles an add property operation if the property to be added is a node. 097 * 098 * @param tx the transaction 099 * @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}