Author: oheger
Date: Sun Mar  9 21:02:47 2014
New Revision: 1575764

URL: http://svn.apache.org/r1575764
Log:
InMemoryModel now supports clearProperty() on a tracked node.

When executing update operations on tracked nodes it has to be distinguished
whether the node is detached or not. For Non-detached nodes the update
operation can be performed on the model's current node structure. Detached
nodes live in an independent node structure. This is implemented by storing
them in a separate node model.

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/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/InMemoryNodeModel.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/InMemoryNodeModel.java?rev=1575764&r1=1575763&r2=1575764&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 21:02:47 2014
@@ -114,7 +114,7 @@ public class InMemoryNodeModel implement
                     initializeAddTransaction(tx, key, values, resolver);
                     return true;
                 }
-            }, resolver);
+            }, null, resolver);
         }
     }
 
@@ -157,7 +157,7 @@ public class InMemoryNodeModel implement
                     }
                     return true;
                 }
-            }, resolver);
+            }, null, resolver);
         }
     }
 
@@ -187,7 +187,7 @@ public class InMemoryNodeModel implement
                                 updateData.getChangedValues());
                 return added || cleared || updated;
             }
-        }, resolver);
+        }, null, resolver);
     }
 
     /**
@@ -227,27 +227,44 @@ public class InMemoryNodeModel implement
                 }
                 return true;
             }
-        }, resolver);
+        }, null, resolver);
     }
 
     /**
      * {@inheritDoc} If this operation leaves an affected node in an undefined
      * state, it is removed from the model.
      */
-    public void clearProperty(final String key,
+    public void clearProperty(String key,
+            NodeKeyResolver<ImmutableNode> resolver)
+    {
+        clearProperty(key, null, resolver);
+    }
+
+    /**
+     * Clears a property using a tracked node as root node. This method works
+     * like the normal {@code clearProperty()} method, but the origin of the
+     * operation (also for the interpretation of the passed in key) is a 
tracked
+     * node identified by the passed in {@code NodeSelector}. The selector can
+     * be <b>null</b>, then the root node is assumed.
+     *
+     * @param key the key
+     * @param selector the {@code NodeSelector} defining the root node (or
+     *        <b>null</b>)
+     * @param resolver the {@code NodeKeyResolver}
+     * @throws ConfigurationRuntimeException if the selector cannot be resolved
+     */
+    public void clearProperty(final String key, NodeSelector selector,
             final NodeKeyResolver<ImmutableNode> resolver)
     {
-        updateModel(new TransactionInitializer()
-        {
-            public boolean initTransaction(ModelTransaction tx)
-            {
+        updateModel(new TransactionInitializer() {
+            public boolean initTransaction(ModelTransaction tx) {
                 List<QueryResult<ImmutableNode>> results =
-                        resolver.resolveKey(tx.getCurrentData().getRootNode(), 
key,
+                        resolver.resolveKey(tx.getQueryRoot(), key,
                                 tx.getCurrentData());
                 initializeClearTransaction(tx, results);
                 return true;
             }
-        }, resolver);
+        }, selector, resolver);
     }
 
     /**
@@ -389,7 +406,7 @@ public class InMemoryNodeModel implement
      * @param parents the map with parent nodes
      * @param root the root node of the current tree
      */
-    void updateParentMapping(final Map<ImmutableNode, ImmutableNode> parents,
+    static void updateParentMapping(final Map<ImmutableNode, ImmutableNode> 
parents,
             ImmutableNode root)
     {
         NodeTreeWalker.INSTANCE.walkBFS(root,
@@ -691,26 +708,86 @@ public class InMemoryNodeModel implement
      * update was successful even if the model is concurrently accessed.
      *
      * @param txInit the {@code TransactionInitializer}
+     * @param selector an optional {@code NodeSelector} defining the target 
node
+     *        of the transaction
      * @param resolver the {@code NodeKeyResolver}
      */
     private void updateModel(TransactionInitializer txInit,
-            NodeKeyResolver<ImmutableNode> resolver)
+            NodeSelector selector, NodeKeyResolver<ImmutableNode> resolver)
     {
         boolean done;
 
         do
         {
-            ModelTransaction tx = new ModelTransaction(this, resolver);
-            if (!txInit.initTransaction(tx))
-            {
-                done = true;
-            }
-            else
+            TreeData currentData = getTreeData();
+            done =
+                    executeTransactionOnDetachedTrackedNode(txInit, selector,
+                            currentData, resolver)
+                            || executeTransactionOnCurrentStructure(txInit,
+                                    selector, currentData, resolver);
+        } while (!done);
+    }
+
+    /**
+     * Executes a transaction on the current data of this model. This method is
+     * called if an operation is to be executed on the model's root node or a
+     * tracked node which is not yet detached.
+     *
+     * @param txInit the {@code TransactionInitializer}
+     * @param selector an optional {@code NodeSelector} defining the target 
node
+     * @param currentData the current data of the model
+     * @param resolver the {@code NodeKeyResolver}
+     * @return a flag whether the operation has been completed successfully
+     */
+    private boolean executeTransactionOnCurrentStructure(
+            TransactionInitializer txInit, NodeSelector selector,
+            TreeData currentData, NodeKeyResolver<ImmutableNode> resolver)
+    {
+        boolean done;
+        ModelTransaction tx =
+                new ModelTransaction(currentData, selector, resolver);
+        if (!txInit.initTransaction(tx))
+        {
+            done = true;
+        }
+        else
+        {
+            TreeData newData = tx.execute();
+            done = structure.compareAndSet(tx.getCurrentData(), newData);
+        }
+        return done;
+    }
+
+    /**
+     * Tries to execute a transaction on the model of a detached tracked node.
+     * This method checks whether the target node of the transaction is a
+     * tracked node and if this node is already detached. If this is the case,
+     * the update operation is independent on this model and has to be executed
+     * on the specific model for the detached node.
+     *
+     * @param txInit the {@code TransactionInitializer}
+     * @param selector an optional {@code NodeSelector} defining the target 
node
+     * @param currentData the current data of the model
+     * @param resolver the {@code NodeKeyResolver} @return a flag whether the
+     *        transaction could be executed
+     * @throws ConfigurationRuntimeException if the selector cannot be resolved
+     */
+    private boolean executeTransactionOnDetachedTrackedNode(
+            TransactionInitializer txInit, NodeSelector selector,
+            TreeData currentData, NodeKeyResolver<ImmutableNode> resolver)
+    {
+        if (selector != null)
+        {
+            InMemoryNodeModel detachedNodeModel =
+                    
currentData.getNodeTracker().getDetachedNodeModel(selector);
+            if (detachedNodeModel != null)
             {
-                TreeData newData = tx.execute();
-                done = structure.compareAndSet(tx.getCurrentData(), newData);
+                detachedNodeModel.updateModel(txInit, null, resolver);
+                return true;
             }
-        } while (!done);
+        }
+
+        return false;
     }
 
     /**

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=1575764&r1=1575763&r2=1575764&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:02:47 2014
@@ -77,12 +77,12 @@ class ModelTransaction
     /** Constant for an unknown level. */
     private static final int LEVEL_UNKNOWN = -1;
 
-    /** The model owning this transaction. */
-    private final InMemoryNodeModel model;
-
     /** Stores the current tree data of the calling node model. */
     private final TreeData currentData;
 
+    /** The root node for query operations. */
+    private final ImmutableNode queryRoot;
+
     /** The {@code NodeKeyResolver} to be used for this transaction. */
     private final NodeKeyResolver<ImmutableNode> resolver;
 
@@ -113,20 +113,23 @@ class ModelTransaction
      * Creates a new instance of {@code ModelTransaction} for the current tree
      * data.
      *
-     * @param nodeModel the owning {@code InMemoryNodeModel}
+     * @param treeData the current {@code TreeData} structure to operate on
+     * @param selector an optional {@code NodeSelector} defining the target 
root
+     *        node for this transaction; this can be used to perform operations
+     *        on tracked nodes
      * @param resolver the {@code NodeKeyResolver}
      */
-    public ModelTransaction(InMemoryNodeModel nodeModel,
+    public ModelTransaction(TreeData treeData, NodeSelector selector,
             NodeKeyResolver<ImmutableNode> resolver)
     {
-        model = nodeModel;
-        currentData = model.getTreeData();
+        currentData = treeData;
         this.resolver = resolver;
         replacedNodes = getCurrentData().copyReplacementMapping();
         parentMapping = getCurrentData().copyParentMapping();
         operations = new TreeMap<Integer, Map<ImmutableNode, Operations>>();
         addedNodes = new LinkedList<ImmutableNode>();
         removedNodes = new LinkedList<ImmutableNode>();
+        queryRoot = initQueryRoot(treeData, selector);
     }
 
     /**
@@ -140,6 +143,19 @@ class ModelTransaction
     }
 
     /**
+     * Returns the root node to be used within queries. This is not necessarily
+     * the current root node of the model. If the operation is executed on a
+     * tracked node, this node has to be passed as root nodes to the expression
+     * engine.
+     *
+     * @return the root node for queries and calls to the expression engine
+     */
+    public ImmutableNode getQueryRoot()
+    {
+        return queryRoot;
+    }
+
+    /**
      * Adds an operation for adding a number of new children to a given parent
      * node.
      *
@@ -295,6 +311,21 @@ class ModelTransaction
     }
 
     /**
+     * Initializes the root node to be used within queries. If a tracked node
+     * selector is provided, this node becomes the root node. Otherwise, the
+     * actual root node is used.
+     *
+     * @param treeData the current data of the model
+     * @param selector an optional {@code NodeSelector} defining the target 
root
+     * @return the query root node for this transaction
+     */
+    private ImmutableNode initQueryRoot(TreeData treeData, NodeSelector 
selector)
+    {
+        return (selector == null) ? treeData.getRootNode() : treeData
+                .getNodeTracker().getTrackedNode(selector);
+    }
+
+    /**
      * Determines the level of the specified node in the current hierarchy. The
      * level of the root node is 0, the children of the root have level 1 and 
so
      * on.
@@ -358,7 +389,7 @@ class ModelTransaction
     {
         replacedNodes.clear();
         parentMapping.clear();
-        model.updateParentMapping(parentMapping, newRoot);
+        InMemoryNodeModel.updateParentMapping(parentMapping, newRoot);
     }
 
     /**
@@ -368,7 +399,7 @@ class ModelTransaction
     {
         for (ImmutableNode node : addedNodes)
         {
-            model.updateParentMapping(parentMapping, node);
+            InMemoryNodeModel.updateParentMapping(parentMapping, node);
         }
     }
 

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=1575764&r1=1575763&r2=1575764&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:02:47 2014
@@ -168,6 +168,23 @@ class NodeTracker
     }
 
     /**
+     * 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(NodeSelector selector)
+    {
+        return getTrackedNodeData(selector).getDetachedModel();
+    }
+
+    /**
      * 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
@@ -317,8 +334,8 @@ 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;
+        /** A node model to be used when the tracked node is detached. */
+        private final InMemoryNodeModel detachedModel;
 
         /**
          * Creates a new instance of {@code TrackedNodeData} and initializes it
@@ -328,7 +345,7 @@ class NodeTracker
          */
         public TrackedNodeData(ImmutableNode nd)
         {
-            this(nd, 1, false);
+            this(nd, 1, null);
         }
 
         /**
@@ -337,14 +354,14 @@ class NodeTracker
          *
          * @param nd the tracked node
          * @param obsCount the observer count
-         * @param isDetached a flag whether the node is detached
+         * @param detachedNodeModel a model to be used in detached mode
          */
         private TrackedNodeData(ImmutableNode nd, int obsCount,
-                boolean isDetached)
+                InMemoryNodeModel detachedNodeModel)
         {
             node = nd;
             observerCount = obsCount;
-            detached = isDetached;
+            detachedModel = detachedNodeModel;
         }
 
         /**
@@ -354,7 +371,19 @@ class NodeTracker
          */
         public ImmutableNode getNode()
         {
-            return node;
+            return (getDetachedModel() != null) ? getDetachedModel()
+                    .getRootNode() : node;
+        }
+
+        /**
+         * Returns the node model to be used in detached mode. This is
+         * <b>null</b> if the represented tracked node is not detached.
+         *
+         * @return the node model in detached mode
+         */
+        public InMemoryNodeModel getDetachedModel()
+        {
+            return detachedModel;
         }
 
         /**
@@ -364,7 +393,7 @@ class NodeTracker
          */
         public boolean isDetached()
         {
-            return detached;
+            return getDetachedModel() != null;
         }
 
         /**
@@ -375,7 +404,7 @@ class NodeTracker
          */
         public TrackedNodeData observerAdded()
         {
-            return new TrackedNodeData(node, observerCount + 1, isDetached());
+            return new TrackedNodeData(node, observerCount + 1, 
getDetachedModel());
         }
 
         /**
@@ -389,7 +418,7 @@ class NodeTracker
         public TrackedNodeData observerRemoved()
         {
             return (observerCount <= 1) ? null : new TrackedNodeData(node,
-                    observerCount - 1, isDetached());
+                    observerCount - 1, getDetachedModel());
         }
 
         /**
@@ -402,7 +431,7 @@ class NodeTracker
          */
         public TrackedNodeData updateNode(ImmutableNode newNode)
         {
-            return new TrackedNodeData(newNode, observerCount, isDetached());
+            return new TrackedNodeData(newNode, observerCount, 
getDetachedModel());
         }
 
         /**
@@ -414,7 +443,8 @@ class NodeTracker
          */
         public TrackedNodeData detach()
         {
-            return new TrackedNodeData(getNode(), observerCount, true);
+            return new TrackedNodeData(getNode(), observerCount,
+                    new InMemoryNodeModel(getNode()));
         }
     }
 }

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=1575764&r1=1575763&r2=1575764&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:02:47 2014
@@ -140,9 +140,20 @@ public class TestInMemoryNodeModelTracke
         ImmutableNode node =
                 NodeStructureHelper.nodeForKey(model, "tables/table(1)");
         NodeKeyResolver<ImmutableNode> resolver = createResolver();
+        initDetachedNode(resolver);
+        assertSame("Wrong node", node, model.getTrackedNode(selector));
+    }
+
+    /**
+     * Produces a tracked node with the default selector and executes an
+     * operation which detaches this node.
+     *
+     * @param resolver the {@code NodeKeyResolver}
+     */
+    private void initDetachedNode(NodeKeyResolver<ImmutableNode> resolver)
+    {
         model.trackNode(selector, resolver);
         model.clearTree("tables.table(0)", resolver);
-        assertSame("Wrong node", node, model.getTrackedNode(selector));
     }
 
     /**
@@ -246,8 +257,7 @@ public class TestInMemoryNodeModelTracke
     public void testIsDetachedTrue()
     {
         NodeKeyResolver<ImmutableNode> resolver = createResolver();
-        model.trackNode(selector, resolver);
-        model.clearTree("tables.table(0)", resolver);
+        initDetachedNode(resolver);
         assertTrue("Node is not detached",
                 model.isTrackedNodeDetached(selector));
     }
@@ -278,4 +288,47 @@ public class TestInMemoryNodeModelTracke
         assertTrue("Node is not detached",
                 model.isTrackedNodeDetached(selector));
     }
+
+    /**
+     * Tests whether clearProperty() can operate on a tracked node.
+     */
+    @Test
+    public void testClearPropertyOnTrackedNode()
+    {
+        NodeKeyResolver<ImmutableNode> resolver = createResolver();
+        model.trackNode(selector, resolver);
+        model.clearProperty("fields.field(0).name", selector, resolver);
+        ImmutableNode nodeFields =
+                NodeStructureHelper.nodeForKey(model, 
"tables/table(1)/fields");
+        assertEquals("Field not removed",
+                NodeStructureHelper.fieldsLength(1) - 1, nodeFields
+                        .getChildren().size());
+        ImmutableNode nodeName =
+                NodeStructureHelper.nodeForKey(nodeFields, "field(0)/name");
+        assertEquals("Wrong node value cleared",
+                NodeStructureHelper.field(1, 1), nodeName.getValue());
+    }
+
+    /**
+     * Tests a clearProperty() on a tracked node which is detached.
+     */
+    @Test
+    public void testClearPropertyOnDetachedNode()
+    {
+        NodeKeyResolver<ImmutableNode> resolver = createResolver();
+        initDetachedNode(resolver);
+        ImmutableNode rootNode = model.getRootNode();
+        model.clearProperty("fields.field(0).name", selector, resolver);
+        assertSame("Model root was changed", rootNode, model.getRootNode());
+        ImmutableNode nodeFields =
+                NodeStructureHelper.nodeForKey(model.getTrackedNode(selector),
+                        "fields");
+        assertEquals("Field not removed",
+                NodeStructureHelper.fieldsLength(1) - 1, nodeFields
+                        .getChildren().size());
+        ImmutableNode nodeName =
+                NodeStructureHelper.nodeForKey(nodeFields, "field(0)/name");
+        assertEquals("Wrong node value cleared",
+                NodeStructureHelper.field(1, 1), nodeName.getValue());
+    }
 }


Reply via email to