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 21:56:06 UTC
svn commit: r1575756 - 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 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));
+ }
+}