Author: oheger Date: Sun Mar 9 20:56:55 2014 New Revision: 1575757 URL: http://svn.apache.org/r1575757 Log: NodeTracker now has the notion of detached nodes.
Tracked nodes can become detached after changes of the underlying nodes structure if their NodeSelector does no longer select a single node. Modified: commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModelTrackedNodes.java Modified: commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java?rev=1575757&r1=1575756&r2=1575757&view=diff ============================================================================== --- commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java (original) +++ commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java Sun Mar 9 20:56:55 2014 @@ -405,6 +405,27 @@ public class InMemoryNodeModel implement } /** + * Returns a flag whether the specified tracked node is detached. As long as + * the {@code NodeSelector} associated with that node returns a single + * instance, the tracked node is said to be <em>life</em>. If now an update + * of the model happens which invalidates the selector (maybe the target + * node was removed), the tracked node becomes detached. It is still + * possible to query the node; here the latest valid instance is returned. + * But further changes on the node model are no longer tracked for this + * node. So even if there are further changes which would make the + * {@code NodeSelector} valid again, the tracked node stays in detached + * state. + * + * @param selector the {@code NodeSelector} defining the desired node + * @return a flag whether this tracked node is in detached state + * @throws ConfigurationRuntimeException if the selector is unknown + */ + public boolean isTrackedNodeDetached(NodeSelector selector) + { + return structure.get().getNodeTracker().isTrackedNodeDetached(selector); + } + + /** * Removes a tracked node. This method is the opposite of * {@code trackNode()}. It has to be called if there is no longer the need * to track a specific node. Note that for each call of {@code trackNode()} @@ -513,7 +534,7 @@ public class InMemoryNodeModel implement { NodeTracker newTracker = (current != null) ? current.getNodeTracker() - : new NodeTracker(); + .detachAllTrackedNodes() : new NodeTracker(); return new TreeData(root, createParentMapping(root), Collections.<ImmutableNode, ImmutableNode> emptyMap(), newTracker); Modified: commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java?rev=1575757&r1=1575756&r2=1575757&view=diff ============================================================================== --- commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java (original) +++ commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java Sun Mar 9 20:56:55 2014 @@ -155,6 +155,19 @@ class NodeTracker } /** + * 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(NodeSelector selector) + { + return getTrackedNodeData(selector).isDetached(); + } + + /** * Updates tracking information after the node structure has been changed. * This method iterates over all tracked nodes. The selectors are evaluated * again to update the node reference. If this fails for a selector, the @@ -180,18 +193,67 @@ class NodeTracker for (Map.Entry<NodeSelector, TrackedNodeData> e : trackedNodes .entrySet()) { - ImmutableNode newTarget = - e.getKey().select(root, resolver, handler); - TrackedNodeData newTrackData = - (newTarget != null) ? e.getValue().updateNode(newTarget) - : e.getValue(); - newState.put(e.getKey(), newTrackData); + newState.put(e.getKey(), determineUpdatedTrackedNodeData(root, resolver, handler, e)); + } + + 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; + } + + Map<NodeSelector, TrackedNodeData> newState = + new HashMap<NodeSelector, TrackedNodeData>(); + for (Map.Entry<NodeSelector, TrackedNodeData> e : trackedNodes + .entrySet()) + { + TrackedNodeData newData = + e.getValue().isDetached() ? e.getValue() : e.getValue() + .detach(); + newState.put(e.getKey(), newData); } return new NodeTracker(newState); } /** + * Returns a {@code TrackedNodeData} object for an update operation. If the + * tracked node is still life, its selector is applied to the current root + * node. It may become detached if there is no match. + * + * @param root the root node + * @param resolver the {@code NodeKeyResolver} + * @param handler the {@code NodeHandler} + * @param e the current selector and {@code TrackedNodeData} + * @return the updated {@code TrackedNodeData} + */ + private TrackedNodeData determineUpdatedTrackedNodeData(ImmutableNode root, + NodeKeyResolver<ImmutableNode> resolver, + NodeHandler<ImmutableNode> handler, + Map.Entry<NodeSelector, TrackedNodeData> e) + { + if (e.getValue().isDetached()) + { + return e.getValue(); + } + ImmutableNode newTarget = e.getKey().select(root, resolver, handler); + return (newTarget != null) ? e.getValue().updateNode(newTarget) : e + .getValue().detach(); + } + + /** * Obtains the {@code TrackedNodeData} object for the specified selector. If * the selector cannot be resolved, an exception is thrown. * @@ -255,6 +317,9 @@ class NodeTracker /** The number of observers of this tracked node. */ private final int observerCount; + /** A flag whether the node is detached. */ + private final boolean detached; + /** * Creates a new instance of {@code TrackedNodeData} and initializes it * with the current reference to the tracked node. @@ -263,7 +328,7 @@ class NodeTracker */ public TrackedNodeData(ImmutableNode nd) { - this(nd, 1); + this(nd, 1, false); } /** @@ -272,11 +337,14 @@ class NodeTracker * * @param nd the tracked node * @param obsCount the observer count + * @param isDetached a flag whether the node is detached */ - private TrackedNodeData(ImmutableNode nd, int obsCount) + private TrackedNodeData(ImmutableNode nd, int obsCount, + boolean isDetached) { node = nd; observerCount = obsCount; + detached = isDetached; } /** @@ -290,6 +358,16 @@ class NodeTracker } /** + * Returns a flag whether the represented tracked node is detached. + * + * @return the detached flag + */ + public boolean isDetached() + { + return detached; + } + + /** * Another observer was added for this tracked node. This method returns * a new instance with an adjusted observer count. * @@ -297,7 +375,7 @@ class NodeTracker */ public TrackedNodeData observerAdded() { - return new TrackedNodeData(node, observerCount + 1); + return new TrackedNodeData(node, observerCount + 1, isDetached()); } /** @@ -311,7 +389,7 @@ class NodeTracker public TrackedNodeData observerRemoved() { return (observerCount <= 1) ? null : new TrackedNodeData(node, - observerCount - 1); + observerCount - 1, isDetached()); } /** @@ -324,7 +402,19 @@ class NodeTracker */ public TrackedNodeData updateNode(ImmutableNode newNode) { - return new TrackedNodeData(newNode, observerCount); + return new TrackedNodeData(newNode, observerCount, isDetached()); + } + + /** + * 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. + * + * @return the updated instance + */ + public TrackedNodeData detach() + { + return new TrackedNodeData(getNode(), observerCount, true); } } } Modified: commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModelTrackedNodes.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModelTrackedNodes.java?rev=1575757&r1=1575756&r2=1575757&view=diff ============================================================================== --- commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModelTrackedNodes.java (original) +++ commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModelTrackedNodes.java Sun Mar 9 20:56:55 2014 @@ -17,8 +17,10 @@ package org.apache.commons.configuration.tree; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.apache.commons.configuration.ex.ConfigurationRuntimeException; @@ -213,4 +215,67 @@ public class TestInMemoryNodeModelTracke model.untrackNode(selector); assertNotNull("No tracked node", model.getTrackedNode(selector)); } + + /** + * Tests isDetached() for a node which has just been tracked. + */ + @Test + public void testIsDetachedFalseNoUpdates() + { + NodeKeyResolver<ImmutableNode> resolver = createResolver(); + model.trackNode(selector, resolver); + assertFalse("Node is detached", model.isTrackedNodeDetached(selector)); + } + + /** + * Tests isDetached() for a life node. + */ + @Test + public void testIsDetachedFalseAfterUpdate() + { + NodeKeyResolver<ImmutableNode> resolver = createResolver(); + model.trackNode(selector, resolver); + model.clearProperty("tables.table(1).fields.field(1).name", resolver); + assertFalse("Node is detached", model.isTrackedNodeDetached(selector)); + } + + /** + * Tests isDetached() for an actually detached node. + */ + @Test + public void testIsDetachedTrue() + { + NodeKeyResolver<ImmutableNode> resolver = createResolver(); + model.trackNode(selector, resolver); + model.clearTree("tables.table(0)", resolver); + assertTrue("Node is not detached", + model.isTrackedNodeDetached(selector)); + } + + /** + * Tests whether a clear() operation causes nodes to be detached. + */ + @Test + public void testIsDetachedAfterClear() + { + NodeKeyResolver<ImmutableNode> resolver = createResolver(); + model.trackNode(selector, resolver); + model.clear(); + assertTrue("Node is not detached", + model.isTrackedNodeDetached(selector)); + } + + /** + * Tests whether tracked nodes become detached when a new root node is set. + */ + @Test + public void testIsDetachedAfterSetRoot() + { + NodeKeyResolver<ImmutableNode> resolver = createResolver(); + model.trackNode(selector, resolver); + model.clearProperty("tables.table(1).fields.field(1).name", resolver); + model.setRootNode(root); + assertTrue("Node is not detached", + model.isTrackedNodeDetached(selector)); + } }