From a8dcbbbdacb575707070eea2690c0b536e6b6c4b Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Wed, 17 Jun 2015 13:40:58 +0200 Subject: [PATCH] BUG-2882: implement DataTreeModificationCursor This patch makes InMemoryDataTree's snapshots and modifications implement CursorAware, allowing more efficient traversal. Change-Id: I279fd6b7d2e14fdfa1827c500a6d323a53207ab5 Signed-off-by: Robert Varga (cherry picked from commit 3eed7efd259a1285df1d9206a65579b2171a4007) --- .../schema/tree/DataTreeSnapshotCursor.java | 10 +- .../data/impl/schema/tree/AbstractCursor.java | 60 ++++++++++ .../impl/schema/tree/AbstractCursorAware.java | 34 ++++++ .../tree/InMemoryDataTreeModification.java | 23 ++-- .../InMemoryDataTreeModificationCursor.java | 113 ++++++++++++++++++ .../schema/tree/InMemoryDataTreeSnapshot.java | 19 ++- .../tree/InMemoryDataTreeSnapshotCursor.java | 75 ++++++++++++ .../tree/OperationWithModification.java | 50 +++++++- 8 files changed, 365 insertions(+), 19 deletions(-) create mode 100644 yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractCursor.java create mode 100644 yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractCursorAware.java create mode 100644 yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModificationCursor.java create mode 100644 yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshotCursor.java diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshotCursor.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshotCursor.java index a2ebb4fa87..7692ca2b21 100644 --- a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshotCursor.java +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshotCursor.java @@ -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 index 0000000000..0eb71bd2ad --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractCursor.java @@ -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 implements DataTreeSnapshotCursor { + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater 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 index 0000000000..7532406744 --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractCursorAware.java @@ -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 CURSOR_UPDATER = + AtomicReferenceFieldUpdater.newUpdater(AbstractCursorAware.class, AbstractCursor.class, "cursor"); + private volatile AbstractCursor cursor = null; + + protected > 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); + } + } +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java index b471279be8..9bf2d3fdfc 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java @@ -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 UPDATER = +final class InMemoryDataTreeModification extends AbstractCursorAware implements CursorAwareDataTreeModification { + private static final AtomicIntegerFieldUpdater 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 index 0000000000..17216ee92f --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModificationCursor.java @@ -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 implements DataTreeModificationCursor { + private final Deque 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 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 path = new ArrayList<>(); + path.addAll(getRootPath().getPathArguments()); + + final Iterator 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 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> 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); + } +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshot.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshot.java index 8023c49ab3..c4cec22944 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshot.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshot.java @@ -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> 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 index 0000000000..cb1faa3048 --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshotCursor.java @@ -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 { + private final Deque> 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> 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 path) { + final Optional> 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> readNode(final PathArgument child) { + return NormalizedNodes.findNode(stack.peek(), child); + } +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/OperationWithModification.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/OperationWithModification.java index a375a90d74..8b39808b2c 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/OperationWithModification.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/OperationWithModification.java @@ -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> READ_DATA = new Function>() { + @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> 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> 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> read(final PathArgument child, final Version version) { + final Optional maybeChild = modification.getChild(child); + if (maybeChild.isPresent()) { + final ModifiedNode childNode = maybeChild.get(); + + Optional 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 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; } -- 2.36.6