commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ohe...@apache.org
Subject svn commit: r1575756 - in /commons/proper/configuration/branches/immutableNodes/src: main/java/org/apache/commons/configuration/tree/ test/java/org/apache/commons/configuration/tree/
Date Sun, 09 Mar 2014 20:56:06 GMT
Author: oheger
Date: Sun Mar  9 20:56:06 2014
New Revision: 1575756

URL: http://svn.apache.org/r1575756
Log:
InMemoryNodeModel now provides support for tracking nodes.

Nodes can be tracked by specifying a corresponding NodeSelector. After updates
of the model, the node instances associated with this key are still available.

Added:
    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
    commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/ModelTransaction.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=1575756&r1=1575755&r2=1575756&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:06 2014
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.commons.configuration.ex.ConfigurationRuntimeException;
 import org.apache.commons.lang3.StringUtils;
 
 /**
@@ -75,7 +76,7 @@ public class InMemoryNodeModel implement
     {
         structure =
                 new AtomicReference<TreeData>(
-                        createTreeData(initialRootNode(root)));
+                        createTreeData(initialRootNode(root), null));
     }
 
     public ImmutableNode getRootNode()
@@ -197,7 +198,7 @@ public class InMemoryNodeModel implement
                     initializeAddTransaction(tx, key, values, resolver);
                     return true;
                 }
-            });
+            }, resolver);
         }
     }
 
@@ -240,7 +241,7 @@ public class InMemoryNodeModel implement
                     }
                     return true;
                 }
-            });
+            }, resolver);
         }
     }
 
@@ -270,7 +271,7 @@ public class InMemoryNodeModel implement
                                 updateData.getChangedValues());
                 return added || cleared || updated;
             }
-        });
+        }, resolver);
     }
 
     /**
@@ -310,7 +311,7 @@ public class InMemoryNodeModel implement
                 }
                 return true;
             }
-        });
+        }, resolver);
     }
 
     /**
@@ -327,7 +328,7 @@ public class InMemoryNodeModel implement
                 initializeClearTransaction(tx, results);
                 return true;
             }
-        });
+        }, resolver);
     }
 
     /**
@@ -341,9 +342,7 @@ public class InMemoryNodeModel implement
         ImmutableNode newRoot =
                 new ImmutableNode.Builder().name(getRootNode().getNodeName())
                         .create();
-        structure.set(new TreeData(newRoot, Collections
-                .<ImmutableNode, ImmutableNode>emptyMap(), Collections
-                .<ImmutableNode, ImmutableNode>emptyMap()));
+        setRootNode(newRoot);
     }
 
     /**
@@ -356,7 +355,78 @@ public class InMemoryNodeModel implement
      */
     public void setRootNode(ImmutableNode newRoot)
     {
-        structure.set(createTreeData(initialRootNode(newRoot)));
+        structure.set(createTreeData(initialRootNode(newRoot), structure.get()));
+    }
+
+    /**
+     * Adds a node to be tracked. After this method has been called with a
+     * specific {@code NodeSelector}, the node associated with this key can be
+     * always obtained using {@link #getTrackedNode(NodeSelector)} with the same
+     * selector. This is useful because during updates of a model parts of the
+     * structure are replaced. Therefore, it is not a good idea to simply hold a
+     * reference to a node; this might become outdated soon. Rather, the node
+     * should be tracked. This mechanism ensures that always the correct node
+     * reference can be obtained.
+     *
+     * @param selector the {@code NodeSelector} defining the desired node
+     * @param resolver the {@code NodeKeyResolver}
+     * @throws ConfigurationRuntimeException if the selector does not select a
+     *         single node
+     */
+    public void trackNode(NodeSelector selector,
+            NodeKeyResolver<ImmutableNode> resolver)
+    {
+        boolean done;
+        do
+        {
+            TreeData current = structure.get();
+            NodeTracker newTracker =
+                    current.getNodeTracker().trackNode(current.getRoot(),
+                            selector, resolver, this);
+            done =
+                    structure.compareAndSet(current,
+                            current.updateNodeTracker(newTracker));
+        } while (!done);
+    }
+
+    /**
+     * Returns the current {@code ImmutableNode} instance associated with the
+     * given {@code NodeSelector}. The node must be a tracked node, i.e.
+     * {@link #trackNode(NodeSelector, NodeKeyResolver)} must have been called
+     * before with the given selector.
+     *
+     * @param selector the {@code NodeSelector} defining the desired node
+     * @return the current {@code ImmutableNode} associated with this selector
+     * @throws ConfigurationRuntimeException if the selector is unknown
+     */
+    public ImmutableNode getTrackedNode(NodeSelector selector)
+    {
+        return structure.get().getNodeTracker().getTrackedNode(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()}
+     * there has to be a corresponding {@code untrackNode()} call. This ensures
+     * that multiple observers can track the same node.
+     *
+     * @param selector the {@code NodeSelector} defining the desired node
+     * @throws ConfigurationRuntimeException if the specified node is not
+     *         tracked
+     */
+    public void untrackNode(NodeSelector selector)
+    {
+        boolean done;
+        do
+        {
+            TreeData current = structure.get();
+            NodeTracker newTracker =
+                    current.getNodeTracker().untrackNode(selector);
+            done =
+                    structure.compareAndSet(current,
+                            current.updateNodeTracker(newTracker));
+        } while (!done);
     }
 
     /**
@@ -436,12 +506,17 @@ public class InMemoryNodeModel implement
      * Creates a {@code TreeData} object for the specified root node.
      *
      * @param root the root node of the current tree
+     * @param current the current {@code TreeData} object (may be <b>null</b>)
      * @return the {@code TreeData} describing the current tree
      */
-    private TreeData createTreeData(ImmutableNode root)
+    private TreeData createTreeData(ImmutableNode root, TreeData current)
     {
+        NodeTracker newTracker =
+                (current != null) ? current.getNodeTracker()
+                        : new NodeTracker();
         return new TreeData(root, createParentMapping(root),
-                new HashMap<ImmutableNode, ImmutableNode>());
+                Collections.<ImmutableNode, ImmutableNode> emptyMap(),
+                newTracker);
     }
 
     /**
@@ -675,14 +750,16 @@ public class InMemoryNodeModel implement
      * update was successful even if the model is concurrently accessed.
      *
      * @param txInit the {@code TransactionInitializer}
+     * @param resolver the {@code NodeKeyResolver}
      */
-    private void updateModel(TransactionInitializer txInit)
+    private void updateModel(TransactionInitializer txInit,
+            NodeKeyResolver<ImmutableNode> resolver)
     {
         boolean done;
 
         do
         {
-            ModelTransaction tx = new ModelTransaction(this);
+            ModelTransaction tx = new ModelTransaction(this, resolver);
             if (!txInit.initTransaction(tx))
             {
                 done = true;
@@ -744,6 +821,9 @@ public class InMemoryNodeModel implement
         /** An inverse replacement mapping. */
         private final Map<ImmutableNode, ImmutableNode> inverseReplacementMapping;
 
+        /** The node tracker. */
+        private final NodeTracker nodeTracker;
+
         /**
          * Creates a new instance of {@code TreeData} and initializes it with
          * all data to be stored.
@@ -751,15 +831,17 @@ public class InMemoryNodeModel implement
          * @param root the root node of the current tree
          * @param parentMapping the mapping to parent nodes
          * @param replacements the map with the nodes that have been replaced
+         * @param tracker the {@code NodeTracker}
          */
         public TreeData(ImmutableNode root,
-                Map<ImmutableNode, ImmutableNode> parentMapping,
-                Map<ImmutableNode, ImmutableNode> replacements)
+                        Map<ImmutableNode, ImmutableNode> parentMapping,
+                        Map<ImmutableNode, ImmutableNode> replacements, NodeTracker
tracker)
         {
             this.root = root;
             this.parentMapping = parentMapping;
             replacementMapping = replacements;
             inverseReplacementMapping = createInverseMapping(replacements);
+            nodeTracker = tracker;
         }
 
         /**
@@ -773,6 +855,16 @@ public class InMemoryNodeModel implement
         }
 
         /**
+         * Returns the {@code NodeTracker}
+         *
+         * @return the {@code NodeTracker}
+         */
+        public NodeTracker getNodeTracker()
+        {
+            return nodeTracker;
+        }
+
+        /**
          * Returns the parent node of the specified node. Result is <b>null</b>
          * for the root node. If the passed in node cannot be resolved, an
          * exception is thrown.
@@ -819,6 +911,20 @@ public class InMemoryNodeModel implement
         }
 
         /**
+         * Creates a new instance which uses the specified {@code NodeTracker}.
+         * This method is called when there are updates of the state of tracked
+         * nodes.
+         *
+         * @param newTracker the new {@code NodeTracker}
+         * @return the updated instance
+         */
+        public TreeData updateNodeTracker(NodeTracker newTracker)
+        {
+            return new TreeData(root, parentMapping, replacementMapping,
+                    newTracker);
+        }
+
+        /**
          * Checks whether the passed in node is subject of a replacement by
          * another one. If so, the other node is returned. This is done until a
          * node is found which had not been replaced. Updating the parent

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=1575756&r1=1575755&r2=1575756&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 20:56:06 2014
@@ -83,6 +83,9 @@ class ModelTransaction
     /** Stores the current tree data of the calling node model. */
     private final InMemoryNodeModel.TreeData currentData;
 
+    /** The {@code NodeKeyResolver} to be used for this transaction. */
+    private final NodeKeyResolver<ImmutableNode> resolver;
+
     /** A new replacement mapping. */
     private final Map<ImmutableNode, ImmutableNode> replacedNodes;
 
@@ -111,11 +114,14 @@ class ModelTransaction
      * data.
      *
      * @param nodeModel the owning {@code InMemoryNodeModel}
+     * @param resolver the {@code NodeKeyResolver}
      */
-    public ModelTransaction(InMemoryNodeModel nodeModel)
+    public ModelTransaction(InMemoryNodeModel nodeModel,
+            NodeKeyResolver<ImmutableNode> resolver)
     {
         model = nodeModel;
         currentData = model.getTreeData();
+        this.resolver = resolver;
         replacedNodes = getCurrentData().copyReplacementMapping();
         parentMapping = getCurrentData().copyParentMapping();
         operations = new TreeMap<Integer, Map<ImmutableNode, Operations>>();
@@ -124,6 +130,16 @@ class ModelTransaction
     }
 
     /**
+     * Returns the {@code NodeKeyResolver} used by this transaction.
+     *
+     * @return the {@code NodeKeyResolver}
+     */
+    public NodeKeyResolver<ImmutableNode> getResolver()
+    {
+        return resolver;
+    }
+
+    /**
      * Adds an operation for adding a number of new children to a given parent
      * node.
      *
@@ -225,7 +241,8 @@ class ModelTransaction
         executeOperations();
         updateParentMapping();
         return new InMemoryNodeModel.TreeData(newRoot, parentMapping,
-                replacedNodes);
+                replacedNodes, currentData.getNodeTracker().update(newRoot,
+                        getResolver(), model));
     }
 
     /**

Added: 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=1575756&view=auto
==============================================================================
--- commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModelTrackedNodes.java
(added)
+++ commons/proper/configuration/branches/immutableNodes/src/test/java/org/apache/commons/configuration/tree/TestInMemoryNodeModelTrackedNodes.java
Sun Mar  9 20:56:06 2014
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.apache.commons.configuration.ex.ConfigurationRuntimeException;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * A special test class for {@code InMemoryNodeModel} which tests the facilities
+ * for tracking nodes.
+ *
+ * @version $Id$
+ */
+public class TestInMemoryNodeModelTrackedNodes
+{
+    /** The root node for the test hierarchy. */
+    private static ImmutableNode root;
+
+    /** A default node selector initialized with a test key. */
+    private static NodeSelector selector;
+
+    /** The model to be tested. */
+    private InMemoryNodeModel model;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception
+    {
+        root =
+                new ImmutableNode.Builder(1).addChild(
+                        NodeStructureHelper.ROOT_TABLES_TREE).create();
+        selector = new NodeSelector("tables.table(1)");
+    }
+
+    @Before
+    public void setUp() throws Exception
+    {
+        model = new InMemoryNodeModel(root);
+    }
+
+    /**
+     * Creates a default resolver which supports arbitrary queries on a target
+     * node.
+     *
+     * @return the resolver
+     */
+    private static NodeKeyResolver<ImmutableNode> createResolver()
+    {
+        NodeKeyResolver<ImmutableNode> resolver =
+                NodeStructureHelper.createResolverMock();
+        NodeStructureHelper.expectResolveKeyForQueries(resolver);
+        EasyMock.replay(resolver);
+        return resolver;
+    }
+
+    /**
+     * Tries to call trackNode() with a key that does not yield any results.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testTrackNodeKeyNoResults()
+    {
+        model.trackNode(new NodeSelector("tables.unknown"), createResolver());
+    }
+
+    /**
+     * Tries to call trackNode() with a key that selects multiple results.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testTrackNodeKeyMultipleResults()
+    {
+        model.trackNode(new NodeSelector("tables.table.fields.field.name"),
+                createResolver());
+    }
+
+    /**
+     * Tests whether a tracked node can be queried.
+     */
+    @Test
+    public void testGetTrackedNodeExisting()
+    {
+        ImmutableNode node =
+                NodeStructureHelper.nodeForKey(model, "tables/table(1)");
+        model.trackNode(selector, createResolver());
+        assertSame("Wrong node", node, model.getTrackedNode(selector));
+    }
+
+    /**
+     * Tries to obtain a tracked node which is unknown.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testGetTrackedNodeNonExisting()
+    {
+        model.getTrackedNode(selector);
+    }
+
+    /**
+     * Tests whether a tracked node survives updates of the node model.
+     */
+    @Test
+    public void testGetTrackedNodeAfterUpdate()
+    {
+        NodeKeyResolver<ImmutableNode> resolver = createResolver();
+        model.trackNode(selector, resolver);
+        model.clearProperty("tables.table(1).fields.field(1).name", resolver);
+        ImmutableNode node = model.getTrackedNode(selector);
+        assertEquals("Wrong node", NodeStructureHelper.table(1), node
+                .getChildren().get(0).getValue());
+    }
+
+    /**
+     * Tests whether a tracked node can be queried even if it was removed from
+     * the structure.
+     */
+    @Test
+    public void testGetTrackedNodeAfterUpdateNoLongerExisting()
+    {
+        ImmutableNode node =
+                NodeStructureHelper.nodeForKey(model, "tables/table(1)");
+        NodeKeyResolver<ImmutableNode> resolver = createResolver();
+        model.trackNode(selector, resolver);
+        model.clearTree("tables.table(0)", resolver);
+        assertSame("Wrong node", node, model.getTrackedNode(selector));
+    }
+
+    /**
+     * Tests whether a tracked node can be queried even after the model was
+     * cleared.
+     */
+    @Test
+    public void testGetTrackedNodeAfterClear()
+    {
+        ImmutableNode node =
+                NodeStructureHelper.nodeForKey(model, "tables/table(1)");
+        NodeKeyResolver<ImmutableNode> resolver = createResolver();
+        model.trackNode(selector, resolver);
+        model.clear();
+        assertSame("Wrong node", node, model.getTrackedNode(selector));
+    }
+
+    /**
+     * Tests whether a tracked node can be queried after the root node was
+     * changed.
+     */
+    @Test
+    public void testGetTrackedNodeAfterSetRootNode()
+    {
+        ImmutableNode node =
+                NodeStructureHelper.nodeForKey(model, "tables/table(1)");
+        NodeKeyResolver<ImmutableNode> resolver = createResolver();
+        model.trackNode(selector, resolver);
+        model.setRootNode(root);
+        assertSame("Wrong node", node, model.getTrackedNode(selector));
+    }
+
+    /**
+     * Tries to stop tracking of a node which is not tracked.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testUntrackNodeNonExisting()
+    {
+        model.untrackNode(selector);
+    }
+
+    /**
+     * Tests whether tracking of a node can be stopped.
+     */
+    @Test
+    public void testUntrackNode()
+    {
+        model.trackNode(selector, createResolver());
+        model.untrackNode(selector);
+        try
+        {
+            model.getTrackedNode(selector);
+            fail("Could get untracked node!");
+        }
+        catch (ConfigurationRuntimeException crex)
+        {
+            // expected
+        }
+    }
+
+    /**
+     * Tests whether a single node can be tracked multiple times.
+     */
+    @Test
+    public void testTrackNodeMultipleTimes()
+    {
+        NodeKeyResolver<ImmutableNode> resolver = createResolver();
+        model.trackNode(selector, resolver);
+        model.trackNode(selector, resolver);
+        model.untrackNode(selector);
+        assertNotNull("No tracked node", model.getTrackedNode(selector));
+    }
+}



Mime
View raw message