You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2014/03/09 22:06:46 UTC

svn commit: r1575770 - in /commons/proper/configuration/branches/immutableNodes/src: main/java/org/apache/commons/configuration/tree/ test/java/org/apache/commons/configuration/tree/

Author: oheger
Date: Sun Mar  9 21:06:46 2014
New Revision: 1575770

URL: http://svn.apache.org/r1575770
Log:
Implemented special treatment for transactions which detach their root node.

Per default, a tracked node that becomes detached is not changed; this means
that the last instance of this tracked node is used. However, if the node was
the root of the transaction which caused it to become detached, this behavior
is not desired. Rather, it can be assumed that the transaction cleared this
node or the sub tree it belongs to. Therefore, in this scenario the node is
replaced by a new empty node instance.

Modified:
    commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/ModelTransaction.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/ModelTransaction.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/ModelTransaction.java?rev=1575770&r1=1575769&r2=1575770&view=diff
==============================================================================
--- commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/ModelTransaction.java (original)
+++ commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/ModelTransaction.java Sun Mar  9 21:06:46 2014
@@ -83,6 +83,9 @@ class ModelTransaction
     /** The root node for query operations. */
     private final ImmutableNode queryRoot;
 
+    /** The selector to the root node of this transaction. */
+    private final NodeSelector rootNodeSelector;
+
     /** The {@code NodeKeyResolver} to be used for this transaction. */
     private final NodeKeyResolver<ImmutableNode> resolver;
 
@@ -130,6 +133,7 @@ class ModelTransaction
         addedNodes = new LinkedList<ImmutableNode>();
         removedNodes = new LinkedList<ImmutableNode>();
         queryRoot = initQueryRoot(treeData, selector);
+        rootNodeSelector = selector;
     }
 
     /**
@@ -258,7 +262,7 @@ class ModelTransaction
         updateParentMapping();
         return new TreeData(newRoot, parentMapping,
                 replacedNodes, currentData.getNodeTracker().update(newRoot,
-                        getResolver(), getCurrentData()));
+                rootNodeSelector, getResolver(), getCurrentData()));
     }
 
     /**

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=1575770&r1=1575769&r2=1575770&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 21:06:46 2014
@@ -188,14 +188,23 @@ class NodeTracker
      * 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
-     * previous node is reused; this tracked node is then detached.
+     * previous node is reused; this tracked node is then detached. The passed
+     * in {@code NodeSelector} is the selector of the tracked node which is the
+     * target of the current transaction. (It is <b>null</b> if the transaction
+     * is not executed on a tracked node.) This is used to handle a special
+     * case: if the tracked node becomes detached by an operation targeting
+     * itself, this means that the node has been cleared by this operation. In
+     * this case, the previous node instance is not used, but an empty node is
+     * created.
      *
      * @param root the root node
+     * @param txTarget the {@code NodeSelector} referencing the target node of
+     *        the current transaction (may be <b>null</b>)
      * @param resolver the {@code NodeKeyResolver}
      * @param handler the {@code NodeHandler}
      * @return the updated instance
      */
-    public NodeTracker update(ImmutableNode root,
+    public NodeTracker update(ImmutableNode root, NodeSelector txTarget,
             NodeKeyResolver<ImmutableNode> resolver,
             NodeHandler<ImmutableNode> handler)
     {
@@ -210,7 +219,10 @@ class NodeTracker
         for (Map.Entry<NodeSelector, TrackedNodeData> e : trackedNodes
                 .entrySet())
         {
-            newState.put(e.getKey(), determineUpdatedTrackedNodeData(root, resolver, handler, e));
+            newState.put(
+                    e.getKey(),
+                    determineUpdatedTrackedNodeData(root, txTarget, resolver,
+                            handler, e));
         }
 
         return new NodeTracker(newState);
@@ -238,7 +250,7 @@ class NodeTracker
         {
             TrackedNodeData newData =
                     e.getValue().isDetached() ? e.getValue() : e.getValue()
-                            .detach();
+                            .detach(null);
             newState.put(e.getKey(), newData);
         }
 
@@ -246,17 +258,39 @@ class NodeTracker
     }
 
     /**
+     * Obtains the {@code TrackedNodeData} object for the specified selector. If
+     * the selector cannot be resolved, an exception is thrown.
+     *
+     * @param selector the {@code NodeSelector}
+     * @return the {@code TrackedNodeData} object for this selector
+     * @throws ConfigurationRuntimeException if the selector cannot be resolved
+     */
+    private TrackedNodeData getTrackedNodeData(NodeSelector selector)
+    {
+        TrackedNodeData trackData = trackedNodes.get(selector);
+        if (trackData == null)
+        {
+            throw new ConfigurationRuntimeException("No tracked node found: "
+                    + selector);
+        }
+        return trackData;
+    }
+
+    /**
      * 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 txTarget the {@code NodeSelector} referencing the target node of
+     *        the current transaction (may be <b>null</b>)
      * @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,
+    private static TrackedNodeData determineUpdatedTrackedNodeData(
+            ImmutableNode root, NodeSelector txTarget,
             NodeKeyResolver<ImmutableNode> resolver,
             NodeHandler<ImmutableNode> handler,
             Map.Entry<NodeSelector, TrackedNodeData> e)
@@ -266,27 +300,46 @@ class NodeTracker
             return e.getValue();
         }
         ImmutableNode newTarget = e.getKey().select(root, resolver, handler);
-        return (newTarget != null) ? e.getValue().updateNode(newTarget) : e
-                .getValue().detach();
+        if (newTarget == null)
+        {
+            return detachedTrackedNodeData(txTarget, e);
+        }
+        return e.getValue().updateNode(newTarget);
     }
 
     /**
-     * Obtains the {@code TrackedNodeData} object for the specified selector. If
-     * the selector cannot be resolved, an exception is thrown.
+     * 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 selector the {@code NodeSelector}
-     * @return the {@code TrackedNodeData} object for this selector
-     * @throws ConfigurationRuntimeException if the selector cannot be resolved
+     * @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 TrackedNodeData getTrackedNodeData(NodeSelector selector)
+    private static TrackedNodeData detachedTrackedNodeData(
+            NodeSelector txTarget, Map.Entry<NodeSelector, TrackedNodeData> e)
     {
-        TrackedNodeData trackData = trackedNodes.get(selector);
-        if (trackData == null)
-        {
-            throw new ConfigurationRuntimeException("No tracked node found: "
-                    + selector);
-        }
-        return trackData;
+        ImmutableNode newNode =
+                e.getKey().equals(txTarget) ? createEmptyTrackedNode(e
+                        .getValue()) : null;
+        return e.getValue().detach(newNode);
+    }
+
+    /**
+     * 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(TrackedNodeData data)
+    {
+        return new ImmutableNode.Builder().name(data.getNode().getNodeName())
+                .create();
     }
 
     /**
@@ -437,14 +490,19 @@ class NodeTracker
         /**
          * 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.
+         * 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()
+        public TrackedNodeData detach(ImmutableNode newNode)
         {
-            return new TrackedNodeData(getNode(), observerCount,
-                    new InMemoryNodeModel(getNode()));
+            ImmutableNode newTrackedNode =
+                    (newNode != null) ? newNode : getNode();
+            return new TrackedNodeData(newTrackedNode, observerCount,
+                    new InMemoryNodeModel(newTrackedNode));
         }
     }
 }

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=1575770&r1=1575769&r2=1575770&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 21:06:46 2014
@@ -637,4 +637,22 @@ public class TestInMemoryNodeModelTracke
                 model.getRootNode());
         checkedForChangedField(fieldsNodeFromTrackedNode(), 0);
     }
+
+    /**
+     * Tests whether a tracked node is handled correctly if an operation is
+     * executed on this node which causes the node to be detached. In this case,
+     * the node should be cleared (it makes no sense to use the last defined
+     * node instance).
+     */
+    @Test
+    public void testTrackedNodeClearedInOperation()
+    {
+        NodeKeyResolver<ImmutableNode> resolver = createResolver();
+        model.trackNode(selector, resolver);
+        model.clearTree(null, selector, resolver);
+        assertTrue("Node not detached", model.isTrackedNodeDetached(selector));
+        ImmutableNode node = model.getTrackedNode(selector);
+        assertEquals("Name was changed", "table", node.getNodeName());
+        assertFalse("Node is defined", model.getNodeHandler().isDefined(node));
+    }
 }