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