BUG-2882: implement DataTreeModificationCursor 25/23825/1
authorRobert Varga <rovarga@cisco.com>
Wed, 17 Jun 2015 11:40:58 +0000 (13:40 +0200)
committerTony Tkacik <ttkacik@cisco.com>
Tue, 7 Jul 2015 12:45:20 +0000 (14:45 +0200)
This patch makes InMemoryDataTree's snapshots and modifications
implement CursorAware, allowing more efficient traversal.

Change-Id: I279fd6b7d2e14fdfa1827c500a6d323a53207ab5
Signed-off-by: Robert Varga <rovarga@cisco.com>
(cherry picked from commit 3eed7efd259a1285df1d9206a65579b2171a4007)

yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshotCursor.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractCursor.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractCursorAware.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModificationCursor.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshot.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshotCursor.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/OperationWithModification.java

index a2ebb4fa872e5c22d5e65a997557ad8aac8a663d..7692ca2b217a91b1dca813eb410aba58bb56f1ef 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.data.api.schema.tree;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Optional;
 import javax.annotation.Nonnull;
+import javax.annotation.concurrent.NotThreadSafe;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
@@ -20,12 +21,13 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
  * the tree.
  */
 @Beta
+@NotThreadSafe
 public interface DataTreeSnapshotCursor extends AutoCloseable {
     /**
      * Move the cursor to the specified child of the current position.
      *
      * @param child Child identifier
-     * @throws BackendFailedException when implementation-specific errors occurs
+     * @throws BackendFailedException when an implementation-specific error occurs
      *                                while servicing the request.
      * @throws IllegalArgumentException when specified identifier does not identify
      *                                  a valid child, or if that child is not an
@@ -36,10 +38,10 @@ public interface DataTreeSnapshotCursor extends AutoCloseable {
     /**
      * Move the cursor to the specified child of the current position. This is
      * the equivalent of multiple invocations of {@link #enter(PathArgument)},
-     * except the operation is performed atomically.
+     * except the operation is performed all at once.
      *
      * @param path Nested child identifier
-     * @throws BackendFailedException when implementation-specific errors occurs
+     * @throws BackendFailedException when an implementation-specific error occurs
      *                                while servicing the request.
      * @throws IllegalArgumentException when specified path does not identify
      *                                  a valid child, or if that child is not an
@@ -53,7 +55,7 @@ public interface DataTreeSnapshotCursor extends AutoCloseable {
      * argument.
      *
      * @param path Nested child identifier
-     * @throws BackendFailedException when implementation-specific errors occurs
+     * @throws BackendFailedException when an implementation-specific error occurs
      *                                while servicing the request.
      * @throws IllegalArgumentException when specified path does not identify
      *                                  a valid child, or if that child is not an
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractCursor.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractCursor.java
new file mode 100644 (file)
index 0000000..0eb71bd
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshotCursor;
+
+abstract class AbstractCursor<T extends AbstractCursorAware> implements DataTreeSnapshotCursor {
+    @SuppressWarnings("rawtypes")
+    private static final AtomicIntegerFieldUpdater<AbstractCursor> CLOSED_UPDATER =
+            AtomicIntegerFieldUpdater.newUpdater(AbstractCursor.class, "closed");
+    private final YangInstanceIdentifier rootPath;
+    private final T parent;
+    private volatile int closed;
+
+    AbstractCursor(final T parent, final YangInstanceIdentifier rootPath) {
+        this.rootPath = Preconditions.checkNotNull(rootPath);
+        this.parent = Preconditions.checkNotNull(parent);
+    }
+
+    final T getParent() {
+        return parent;
+    }
+
+    final YangInstanceIdentifier getRootPath() {
+        return rootPath;
+    }
+
+
+    final void ensureNotClosed() {
+        Preconditions.checkState(closed == 0, "Modification cursor has been closed");
+    }
+
+    @Override
+    public final void enter(final PathArgument... path) {
+        enter(Arrays.asList(path));
+    }
+
+    @Override
+    public final void exit() {
+        exit(1);
+    }
+
+    @Override
+    public final void close() {
+        if (CLOSED_UPDATER.compareAndSet(this, 0, 1)) {
+            parent.closeCursor(this);
+        }
+    }
+
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractCursorAware.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractCursorAware.java
new file mode 100644 (file)
index 0000000..7532406
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Preconditions;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class AbstractCursorAware {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractCursorAware.class);
+    @SuppressWarnings("rawtypes")
+    private static final AtomicReferenceFieldUpdater<AbstractCursorAware, AbstractCursor> CURSOR_UPDATER =
+            AtomicReferenceFieldUpdater.newUpdater(AbstractCursorAware.class, AbstractCursor.class, "cursor");
+    private volatile AbstractCursor<?> cursor = null;
+
+    protected <T extends AbstractCursor<?>> T openCursor(final T cursor) {
+        final boolean success = CURSOR_UPDATER.compareAndSet(this, null, cursor);
+        Preconditions.checkState(success, "Modification %s has cursor attached at path %s", this, this.cursor.getRootPath());
+        return cursor;
+    }
+
+    final void closeCursor(final AbstractCursor<?> cursor) {
+        final boolean success = CURSOR_UPDATER.compareAndSet(this, cursor, null);
+        if (!success) {
+            LOG.warn("Attempted to close cursor %s while %s is open", cursor, this.cursor);
+        }
+    }
+}
index b471279be8f150ab04e2012fe6917e605115a4f3..9bf2d3fdfc9072afa4cbadc43b2abca95e37d393 100644 (file)
@@ -18,6 +18,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.CursorAwareDataTreeModification;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModificationCursor;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNodes;
@@ -27,8 +28,8 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-final class InMemoryDataTreeModification implements DataTreeModification {
-    private static final AtomicIntegerFieldUpdater<InMemoryDataTreeModification> UPDATER =
+final class InMemoryDataTreeModification extends AbstractCursorAware implements CursorAwareDataTreeModification {
+    private static final AtomicIntegerFieldUpdater<InMemoryDataTreeModification> SEALED_UPDATER =
             AtomicIntegerFieldUpdater.newUpdater(InMemoryDataTreeModification.class, "sealed");
     private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTreeModification.class);
 
@@ -120,7 +121,7 @@ final class InMemoryDataTreeModification implements DataTreeModification {
         }
     }
 
-    private void upgradeIfPossible() {
+    void upgradeIfPossible() {
         if (rootNode.getOperation() == LogicalOperation.NONE) {
             strategyTree.upgradeIfPossible();
         }
@@ -248,12 +249,16 @@ final class InMemoryDataTreeModification implements DataTreeModification {
         }
     }
 
+    static void checkIdentifierReferencesData(final PathArgument arg, final NormalizedNode<?, ?> data) {
+        Preconditions.checkArgument(arg.equals(data.getIdentifier()),
+            "Instance identifier references %s but data identifier is %s", arg, data.getIdentifier());
+    }
+
     private static void checkIdentifierReferencesData(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
         if (!path.isEmpty()) {
             final PathArgument lastArg = path.getLastPathArgument();
             Preconditions.checkArgument(lastArg != null, "Instance identifier %s has invalid null path argument", path);
-            Preconditions.checkArgument(lastArg.equals(data.getIdentifier()),
-                    "Instance identifier references %s but data identifier is %s", lastArg, data.getIdentifier());
+            checkIdentifierReferencesData(lastArg, data);
         } else {
             final QName type = data.getNodeType();
             Preconditions.checkArgument(SchemaContext.NAME.equals(type), "Incorrect name %s of root node", type);
@@ -261,8 +266,13 @@ final class InMemoryDataTreeModification implements DataTreeModification {
     }
 
     @Override
+    public DataTreeModificationCursor createCursor(final YangInstanceIdentifier path) {
+        final OperationWithModification op = resolveModificationFor(path);
+        return openCursor(new InMemoryDataTreeModificationCursor(this, path, op));
+    }
+
     public void ready() {
-        final boolean wasRunning = UPDATER.compareAndSet(this, 0, 1);
+        final boolean wasRunning = SEALED_UPDATER.compareAndSet(this, 0, 1);
         Preconditions.checkState(wasRunning, "Attempted to seal an already-sealed Data Tree.");
 
         AbstractReadyIterator current = AbstractReadyIterator.create(rootNode, strategyTree);
@@ -270,5 +280,4 @@ final class InMemoryDataTreeModification implements DataTreeModification {
             current = current.process();
         } while (current != null);
     }
-
 }
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModificationCursor.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModificationCursor.java
new file mode 100644 (file)
index 0000000..17216ee
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Iterator;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModificationCursor;
+
+final class InMemoryDataTreeModificationCursor extends AbstractCursor<InMemoryDataTreeModification> implements DataTreeModificationCursor {
+    private final Deque<OperationWithModification> stack = new ArrayDeque<>();
+
+    InMemoryDataTreeModificationCursor(final InMemoryDataTreeModification parent, final YangInstanceIdentifier rootPath, final OperationWithModification rootOp) {
+        super(parent, rootPath);
+        stack.push(rootOp);
+    }
+
+    private OperationWithModification resolveChildModification(final PathArgument child) {
+        getParent().upgradeIfPossible();
+
+        final OperationWithModification op = stack.peek();
+        final Optional<ModificationApplyOperation> potential = op.getApplyOperation().getChild(child);
+        if (potential.isPresent()) {
+            final ModificationApplyOperation operation = potential.get();
+            final ModifiedNode modification = op.getModification().modifyChild(child, operation.getChildPolicy());
+
+            return OperationWithModification.from(operation, modification);
+        }
+
+        // Node not found, construct its path
+        final Collection<PathArgument> path = new ArrayList<>();
+        path.addAll(getRootPath().getPathArguments());
+
+        final Iterator<OperationWithModification> it = stack.descendingIterator();
+        // Skip the first entry, as it's already accounted for in rootPath
+        it.next();
+
+        while (it.hasNext()) {
+            path.add(it.next().getModification().getIdentifier());
+        }
+
+        throw new SchemaValidationFailedException(String.format("Child %s is not present in schema tree.", path));
+    }
+
+    @Override
+    public void enter(final PathArgument child) {
+        stack.push(resolveChildModification(child));
+    }
+
+    @Override
+    public void enter(final Iterable<PathArgument> path) {
+        int depth = 0;
+        for (PathArgument child : path) {
+            try {
+                stack.push(resolveChildModification(child));
+            } catch (Exception e) {
+                // Undo what we have done
+                for (int i = 0; i < depth; ++i) {
+                    stack.pop();
+                }
+                throw new IllegalArgumentException(e);
+            }
+            depth++;
+        }
+    }
+
+    @Override
+    public void exit(final int depth) {
+        Preconditions.checkArgument(depth >= 0);
+        Preconditions.checkState(depth < stack.size());
+
+        for (int i = 0; i < depth; i++) {
+            stack.pop();
+        }
+    }
+
+    @Override
+    public Optional<NormalizedNode<?, ?>> readNode(final PathArgument child) {
+        return stack.peek().read(child, getParent().getVersion());
+    }
+
+    @Override
+    public void delete(final PathArgument child) {
+        ensureNotClosed();
+        resolveChildModification(child).delete();
+    }
+
+    @Override
+    public void merge(final PathArgument child, final NormalizedNode<?, ?> data) {
+        ensureNotClosed();
+        InMemoryDataTreeModification.checkIdentifierReferencesData(child, data);
+        resolveChildModification(child).merge(data);
+    }
+
+    @Override
+    public void write(final PathArgument child, final NormalizedNode<?, ?> data) {
+        ensureNotClosed();
+        InMemoryDataTreeModification.checkIdentifierReferencesData(child, data);
+        resolveChildModification(child).write(data);
+    }
+}
index 8023c49ab339943aea40b5f7f57d014e075982f2..c4cec22944fd0a4fd8bb2c28d12f06b9b4505d4b 100644 (file)
@@ -11,12 +11,14 @@ import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.CursorAwareDataTreeSnapshot;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshotCursor;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
-final class InMemoryDataTreeSnapshot implements DataTreeSnapshot {
+final class InMemoryDataTreeSnapshot extends AbstractCursorAware implements CursorAwareDataTreeSnapshot {
     private final RootModificationApplyOperation applyOper;
     private final SchemaContext schemaContext;
     private final TreeNode rootNode;
@@ -46,9 +48,20 @@ final class InMemoryDataTreeSnapshot implements DataTreeSnapshot {
         return new InMemoryDataTreeModification(this, applyOper);
     }
 
+    @Override
+    public DataTreeSnapshotCursor createCursor(final YangInstanceIdentifier path) {
+        final Optional<NormalizedNode<?, ?>> maybeRoot = NormalizedNodes.findNode(rootNode.getData(), path);
+        if (!maybeRoot.isPresent()) {
+            return null;
+        }
+
+        final NormalizedNode<?, ?> root = maybeRoot.get();
+        Preconditions.checkArgument(root instanceof NormalizedNodeContainer, "Child %s is not a container", path);
+        return openCursor(new InMemoryDataTreeSnapshotCursor(this, path, (NormalizedNodeContainer<?, ?, ?>)root));
+    }
+
     @Override
     public String toString() {
         return rootNode.getSubtreeVersion().toString();
     }
-
 }
\ No newline at end of file
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshotCursor.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshotCursor.java
new file mode 100644 (file)
index 0000000..cb1faa3
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+
+final class InMemoryDataTreeSnapshotCursor extends AbstractCursor<InMemoryDataTreeSnapshot> {
+    private final Deque<NormalizedNodeContainer<?, ?, ?>> stack = new ArrayDeque<>();
+
+    InMemoryDataTreeSnapshotCursor(final InMemoryDataTreeSnapshot parent, final YangInstanceIdentifier rootPath, final NormalizedNodeContainer<?, ?, ?> normalizedNode) {
+        super(parent, rootPath);
+        stack.push(normalizedNode);
+    }
+
+    @Override
+    public void enter(final PathArgument child) {
+        final Optional<NormalizedNode<?, ?>> maybeChildNode = NormalizedNodes.getDirectChild(stack.peek(), child);
+        Preconditions.checkArgument(maybeChildNode.isPresent(), "Child %s not found", child);
+
+        final NormalizedNode<?, ?> childNode = maybeChildNode.get();
+        Preconditions.checkArgument(childNode instanceof NormalizedNodeContainer, "Child %s is not a container", child);
+        stack.push((NormalizedNodeContainer<?, ?, ?>) childNode);
+    }
+
+    @Override
+    public void enter(final Iterable<PathArgument> path) {
+        final Optional<NormalizedNode<?, ?>> maybeChildNode = NormalizedNodes.findNode(stack.peek(), path);
+        Preconditions.checkArgument(maybeChildNode.isPresent(), "Child %s not found", path);
+
+        final NormalizedNode<?, ?> childNode = maybeChildNode.get();
+        Preconditions.checkArgument(childNode instanceof NormalizedNodeContainer, "Child %s is not a container", path);
+
+        int depth = 0;
+        for (PathArgument arg : path) {
+            try {
+                enter(arg);
+            } catch (Exception e) {
+                for (int i = 0; i < depth; ++i) {
+                    stack.pop();
+                }
+                throw new IllegalArgumentException(e);
+            }
+
+            ++depth;
+        }
+    }
+
+    @Override
+    public void exit(final int depth) {
+        Preconditions.checkArgument(depth >= 0);
+        Preconditions.checkState(depth < stack.size());
+
+        for (int i = 0; i < depth; ++i) {
+            stack.pop();
+        }
+    }
+
+    @Override
+    public Optional<NormalizedNode<?, ?>> readNode(final PathArgument child) {
+        return NormalizedNodes.findNode(stack.peek(), child);
+    }
+}
index a375a90d74dcbca18df6ffb06ccb916727b7c09d..8b39808b2c3e903d47e62163b2f8ce1484a3deca 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
+import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
@@ -16,14 +17,19 @@ import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
 
 final class OperationWithModification {
+    private static final Function<TreeNode, NormalizedNode<?, ?>> READ_DATA = new Function<TreeNode, NormalizedNode<?, ?>>() {
+        @Override
+        public NormalizedNode<?, ?> apply(final TreeNode input) {
+            return input.getData();
+        }
+    };
 
     private final ModifiedNode modification;
-
     private final ModificationApplyOperation applyOperation;
 
     private OperationWithModification(final ModificationApplyOperation op, final ModifiedNode mod) {
-        this.modification = mod;
-        this.applyOperation = op;
+        this.modification = Preconditions.checkNotNull(mod);
+        this.applyOperation = Preconditions.checkNotNull(op);
     }
 
     void write(final NormalizedNode<?, ?> value) {
@@ -35,7 +41,7 @@ final class OperationWithModification {
     }
 
     private void recursiveMerge(final NormalizedNode<?,?> data) {
-        if (data instanceof NormalizedNodeContainer<?,?,?>) {
+        if (data instanceof NormalizedNodeContainer) {
             @SuppressWarnings({ "rawtypes", "unchecked" })
             final
             NormalizedNodeContainer<?,?,NormalizedNode<PathArgument, ?>> dataContainer = (NormalizedNodeContainer) data;
@@ -46,7 +52,7 @@ final class OperationWithModification {
              * retain children created by past write operation.
              * These writes will then be pushed down in the tree while there are merge modifications on these children
              */
-            if (modification.getOperation().equals(LogicalOperation.WRITE)) {
+            if (modification.getOperation() == LogicalOperation.WRITE) {
                 @SuppressWarnings({ "rawtypes", "unchecked" })
                 final
                 NormalizedNodeContainer<?,?,NormalizedNode<PathArgument, ?>> odlDataContainer =
@@ -83,6 +89,40 @@ final class OperationWithModification {
         modification.delete();
     }
 
+    /**
+     * Read a particular child. If the child has been modified and does not have a stable
+     * view, one will we instantiated with specified version.
+     *
+     * @param child
+     * @param version
+     * @return
+     */
+    Optional<NormalizedNode<?, ?>> read(final PathArgument child, final Version version) {
+        final Optional<ModifiedNode> maybeChild = modification.getChild(child);
+        if (maybeChild.isPresent()) {
+            final ModifiedNode childNode = maybeChild.get();
+
+            Optional<TreeNode> snapshot = childNode.getSnapshot();
+            if (snapshot == null) {
+                // Snapshot is not present, force instantiation
+                snapshot = applyOperation.getChild(child).get().apply(childNode, childNode.getOriginal(), version);
+            }
+
+            return snapshot.transform(READ_DATA);
+        }
+
+        Optional<TreeNode> snapshot = modification.getSnapshot();
+        if (snapshot == null) {
+            snapshot = apply(modification.getOriginal(), version);
+        }
+
+        if (snapshot.isPresent()) {
+            return snapshot.get().getChild(child).transform(READ_DATA);
+        }
+
+        return Optional.absent();
+    }
+
     public ModifiedNode getModification() {
         return modification;
     }