/** * Adds a number of nodes to be tracked. For each node in the passed in * collection, a tracked node entry is created unless already one exists. * * @param selectors a collection with the {@code NodeSelector} objects * @param nodes a collection with the nodes to be tracked * @return the updated instance */ public NodeTracker trackNodes(final Collection<NodeSelector> selectors, final Collection<ImmutableNode> nodes) { final Map<NodeSelector, TrackedNodeData> newState = new HashMap<>(trackedNodes); final Iterator<ImmutableNode> itNodes = nodes.iterator(); for (final NodeSelector selector : selectors) { final ImmutableNode node = itNodes.next(); TrackedNodeData trackData = newState.get(selector); if (trackData == null) { trackData = new TrackedNodeData(node); } else { trackData = trackData.observerAdded(); } newState.put(selector, trackData); } return new NodeTracker(newState); }
/** * Marks all tracked nodes as detached. This method is called if there are * some drastic changes on the underlying node structure, e.g. if the root * node was replaced. * * @return the updated instance */ public NodeTracker detachAllTrackedNodes() { if (trackedNodes.isEmpty()) { // there is not state to be updated return this; } final Map<NodeSelector, TrackedNodeData> newState = new HashMap<>(); for (final Map.Entry<NodeSelector, TrackedNodeData> e : trackedNodes .entrySet()) { final TrackedNodeData newData = e.getValue().isDetached() ? e.getValue() : e.getValue() .detach(null); newState.put(e.getKey(), newData); } return new NodeTracker(newState); }
/** * Notifies this object that an observer was removed for the specified * tracked node. If this was the last observer, the track data for this * selector can be removed. * * @param selector the {@code NodeSelector} * @return the updated instance * @throws ConfigurationRuntimeException if no information about this node * is available */ public NodeTracker untrackNode(final NodeSelector selector) { final TrackedNodeData trackData = getTrackedNodeData(selector); final Map<NodeSelector, TrackedNodeData> newState = new HashMap<>(trackedNodes); final TrackedNodeData newTrackData = trackData.observerRemoved(); if (newTrackData == null) { newState.remove(selector); } else { newState.put(selector, newTrackData); } return new NodeTracker(newState); }
final Map.Entry<NodeSelector, TrackedNodeData> e) if (e.getValue().isDetached()) return e.getValue().updateNode(newTarget);
/** * Creates a {@code TrackedNodeData} object for a newly added observer for * the specified node selector. * * @param root the root node * @param selector the {@code NodeSelector} * @param resolver the {@code NodeKeyResolver} * @param handler the {@code NodeHandler} * @param trackData the current data for this selector * @return the updated {@code TrackedNodeData} * @throws ConfigurationRuntimeException if the selector does not select a * single node */ private static TrackedNodeData trackDataForAddedObserver( final ImmutableNode root, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver, final NodeHandler<ImmutableNode> handler, final TrackedNodeData trackData) { if (trackData != null) { return trackData.observerAdded(); } final ImmutableNode target = selector.select(root, resolver, handler); if (target == null) { throw new ConfigurationRuntimeException( "Selector does not select unique node: " + selector); } return new TrackedNodeData(target); }
/** * Creates a new {@code TrackedNodeData} object for a tracked node which * becomes detached within the current transaction. This method checks * whether the affected node is the root node of the current transaction. If * so, it is cleared. * * @param txTarget the {@code NodeSelector} referencing the target node of * the current transaction (may be <b>null</b>) * @param e the current selector and {@code TrackedNodeData} * @return the new {@code TrackedNodeData} object to be used for this * tracked node */ private static TrackedNodeData detachedTrackedNodeData( final NodeSelector txTarget, final Map.Entry<NodeSelector, TrackedNodeData> e) { final ImmutableNode newNode = e.getKey().equals(txTarget) ? createEmptyTrackedNode(e .getValue()) : null; return e.getValue().detach(newNode); }
/** * Returns an instance with the detached flag set to true. This method * is called if the selector of a tracked node does not match a single * node any more. It is possible to pass in a new node instance which * becomes the current tracked node. If this is <b>null</b>, the * previous node instance is used. * * @param newNode the new tracked node instance (may be <b>null</b>) * @return the updated instance */ public TrackedNodeData detach(final ImmutableNode newNode) { final ImmutableNode newTrackedNode = (newNode != null) ? newNode : getNode(); return new TrackedNodeData(newTrackedNode, observerCount, new InMemoryNodeModel(newTrackedNode)); } }
/** * Replaces a tracked node by another one. This operation causes the tracked * node to become detached. * * @param selector the {@code NodeSelector} * @param newNode the replacement node * @return the updated instance * @throws ConfigurationRuntimeException if the selector cannot be resolved */ public NodeTracker replaceAndDetachTrackedNode(final NodeSelector selector, final ImmutableNode newNode) { final Map<NodeSelector, TrackedNodeData> newState = new HashMap<>(trackedNodes); newState.put(selector, getTrackedNodeData(selector).detach(newNode)); return new NodeTracker(newState); }
/** * Returns the detached node model for the specified tracked node. When a * node becomes detached, operations on it are independent from the original * model. To implement this, a separate node model is created wrapping this * tracked node. This model can be queried by this method. If the node * affected is not detached, result is <b>null</b>. * * @param selector the {@code NodeSelector} * @return the detached node model for this node or <b>null</b> * @throws ConfigurationRuntimeException if no data for this selector is * available */ public InMemoryNodeModel getDetachedNodeModel(final NodeSelector selector) { return getTrackedNodeData(selector).getDetachedModel(); }
/** * Creates an empty node derived from the passed in {@code TrackedNodeData} * object. This method is called if a tracked node got cleared by a * transaction. * * @param data the {@code TrackedNodeData} * @return the new node instance for this tracked node */ private static ImmutableNode createEmptyTrackedNode(final TrackedNodeData data) { return new ImmutableNode.Builder().name(data.getNode().getNodeName()) .create(); }
/** * Returns the current {@code ImmutableNode} instance associated with the * given selector. * * @param selector the {@code NodeSelector} * @return the {@code ImmutableNode} selected by this selector * @throws ConfigurationRuntimeException if no data for this selector is * available */ public ImmutableNode getTrackedNode(final NodeSelector selector) { return getTrackedNodeData(selector).getNode(); }
/** * Returns a flag whether the represented tracked node is detached. * * @return the detached flag */ public boolean isDetached() { return getDetachedModel() != null; }
/** * Returns the tracked node. * * @return the tracked node */ public ImmutableNode getNode() { return (getDetachedModel() != null) ? getDetachedModel() .getRootNode() : node; }
/** * Updates the node reference. This method is called after an update of * the underlying node structure if the tracked node was replaced by * another instance. * * @param newNode the new tracked node instance * @return the updated instance */ public TrackedNodeData updateNode(final ImmutableNode newNode) { return new TrackedNodeData(newNode, observerCount, getDetachedModel()); }
/** * Another observer was added for this tracked node. This method returns * a new instance with an adjusted observer count. * * @return the updated instance */ public TrackedNodeData observerAdded() { return new TrackedNodeData(node, observerCount + 1, getDetachedModel()); }
/** * Returns a flag whether the specified tracked node is detached. * * @param selector the {@code NodeSelector} * @return a flag whether this node is detached * @throws ConfigurationRuntimeException if no data for this selector is * available */ public boolean isTrackedNodeDetached(final NodeSelector selector) { return getTrackedNodeData(selector).isDetached(); }
/** * An observer for this tracked node was removed. This method returns a * new instance with an adjusted observer count. If there are no more * observers, result is <b>null</b>. This means that this node is no * longer tracked and can be released. * * @return the updated instance or <b>null</b> */ public TrackedNodeData observerRemoved() { return (observerCount <= 1) ? null : new TrackedNodeData(node, observerCount - 1, getDetachedModel()); }