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());
+ }
}