Separate out RootModificationApplyOperation
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / tree / InMemoryDataTreeModification.java
index 6ca6970637f249000a792bf796e4f635bd8dd531..f91873bd4848e4674dfddd133a98f215b015d9c5 100644 (file)
@@ -7,42 +7,47 @@
  */
 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Iterables;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
 import java.util.Collection;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
-import org.opendaylight.yangtools.yang.common.QName;
+import javax.annotation.Nonnull;
 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.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.CursorAwareDataTreeModification;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModificationCursor;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNodes;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider;
 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,
+        SchemaContextProvider {
+    private static final AtomicIntegerFieldUpdater<InMemoryDataTreeModification> SEALED_UPDATER =
             AtomicIntegerFieldUpdater.newUpdater(InMemoryDataTreeModification.class, "sealed");
     private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTreeModification.class);
 
-    private final RootModificationApplyOperation strategyTree;
+    private final RootApplyStrategy strategyTree;
     private final InMemoryDataTreeSnapshot snapshot;
     private final ModifiedNode rootNode;
     private final Version version;
 
     private volatile int sealed = 0;
 
-    InMemoryDataTreeModification(final InMemoryDataTreeSnapshot snapshot, final RootModificationApplyOperation resolver) {
-        this.snapshot = Preconditions.checkNotNull(snapshot);
-        this.strategyTree = Preconditions.checkNotNull(resolver).snapshot();
-        this.rootNode = ModifiedNode.createUnmodified(snapshot.getRootNode(), strategyTree.getChildPolicy());
+    InMemoryDataTreeModification(final InMemoryDataTreeSnapshot snapshot,
+            final RootApplyStrategy resolver) {
+        this.snapshot = requireNonNull(snapshot);
+        this.strategyTree = requireNonNull(resolver).snapshot();
+        this.rootNode = ModifiedNode.createUnmodified(snapshot.getRootNode(), getStrategy().getChildPolicy());
 
         /*
          * We could allocate version beforehand, since Version contract
@@ -61,7 +66,12 @@ final class InMemoryDataTreeModification implements DataTreeModification {
     }
 
     ModificationApplyOperation getStrategy() {
-        return strategyTree;
+        return strategyTree.delegate();
+    }
+
+    @Override
+    public SchemaContext getSchemaContext() {
+        return snapshot.getSchemaContext();
     }
 
     @Override
@@ -75,7 +85,7 @@ final class InMemoryDataTreeModification implements DataTreeModification {
     public void merge(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
         checkSealed();
         checkIdentifierReferencesData(path, data);
-        resolveModificationFor(path).merge(data);
+        resolveModificationFor(path).merge(data, version);
     }
 
     @Override
@@ -92,7 +102,8 @@ final class InMemoryDataTreeModification implements DataTreeModification {
          * the requested path which has been modified. If no such node exists,
          * we use the node itself.
          */
-        final Entry<YangInstanceIdentifier, ModifiedNode> entry = StoreTreeNodes.findClosestsOrFirstMatch(rootNode, path, ModifiedNode.IS_TERMINAL_PREDICATE);
+        final Entry<YangInstanceIdentifier, ModifiedNode> entry = StoreTreeNodes.findClosestsOrFirstMatch(rootNode,
+            path, ModifiedNode.IS_TERMINAL_PREDICATE);
         final YangInstanceIdentifier key = entry.getKey();
         final ModifiedNode mod = entry.getValue();
 
@@ -100,11 +111,12 @@ final class InMemoryDataTreeModification implements DataTreeModification {
         if (result.isPresent()) {
             final NormalizedNode<?, ?> data = result.get().getData();
             return NormalizedNodes.findNode(key, data, path);
-        } else {
-            return Optional.absent();
         }
+
+        return Optional.empty();
     }
 
+    @SuppressWarnings("checkstyle:illegalCatch")
     private Optional<TreeNode> resolveSnapshot(final YangInstanceIdentifier path, final ModifiedNode modification) {
         final Optional<TreeNode> potentialSnapshot = modification.getSnapshot();
         if (potentialSnapshot != null) {
@@ -112,15 +124,14 @@ final class InMemoryDataTreeModification implements DataTreeModification {
         }
 
         try {
-            return resolveModificationStrategy(path).apply(modification, modification.getOriginal(),
-                    version);
+            return resolveModificationStrategy(path).apply(modification, modification.getOriginal(), version);
         } catch (final Exception e) {
             LOG.error("Could not create snapshot for {}:{}", path, modification, e);
             throw e;
         }
     }
 
-    private void upgradeIfPossible() {
+    void upgradeIfPossible() {
         if (rootNode.getOperation() == LogicalOperation.NONE) {
             strategyTree.upgradeIfPossible();
         }
@@ -130,7 +141,7 @@ final class InMemoryDataTreeModification implements DataTreeModification {
         LOG.trace("Resolving modification apply strategy for {}", path);
 
         upgradeIfPossible();
-        return StoreTreeNodes.<ModificationApplyOperation>findNodeChecked(strategyTree, path);
+        return StoreTreeNodes.findNodeChecked(getStrategy(), path);
     }
 
     private OperationWithModification resolveModificationFor(final YangInstanceIdentifier path) {
@@ -146,27 +157,27 @@ final class InMemoryDataTreeModification implements DataTreeModification {
          * That is fine, as we will prune any empty TOUCH nodes in the last phase of the ready
          * process.
          */
-        ModificationApplyOperation operation = strategyTree;
+        ModificationApplyOperation operation = getStrategy();
         ModifiedNode modification = rootNode;
 
-        int i = 1;
-        for(final PathArgument pathArg : path.getPathArguments()) {
+        int depth = 1;
+        for (final PathArgument pathArg : path.getPathArguments()) {
             final Optional<ModificationApplyOperation> potential = operation.getChild(pathArg);
             if (!potential.isPresent()) {
                 throw new SchemaValidationFailedException(String.format("Child %s is not present in schema tree.",
-                        Iterables.toString(Iterables.limit(path.getPathArguments(), i))));
+                        path.getAncestor(depth)));
             }
             operation = potential.get();
-            ++i;
+            ++depth;
 
-            modification = modification.modifyChild(pathArg, operation.getChildPolicy());
+            modification = modification.modifyChild(pathArg, operation, version);
         }
 
         return OperationWithModification.from(operation, modification);
     }
 
     private void checkSealed() {
-        Preconditions.checkState(sealed == 0, "Data Tree is sealed. No further modifications allowed.");
+        checkState(sealed == 0, "Data Tree is sealed. No further modifications allowed.");
     }
 
     @Override
@@ -175,8 +186,8 @@ final class InMemoryDataTreeModification implements DataTreeModification {
     }
 
     @Override
-    public DataTreeModification newModification() {
-        Preconditions.checkState(sealed == 1, "Attempted to chain on an unsealed modification");
+    public InMemoryDataTreeModification newModification() {
+        checkState(sealed == 1, "Attempted to chain on an unsealed modification");
 
         if (rootNode.getOperation() == LogicalOperation.NONE) {
             // Simple fast case: just use the underlying modification
@@ -188,10 +199,11 @@ final class InMemoryDataTreeModification implements DataTreeModification {
          * have same version each time this method is called.
          */
         final TreeNode originalSnapshotRoot = snapshot.getRootNode();
-        final Optional<TreeNode> tempRoot = strategyTree.apply(rootNode, Optional.of(originalSnapshotRoot), version);
-        Preconditions.checkState(tempRoot.isPresent(), "Data tree root is not present, possibly removed by previous modification");
+        final Optional<TreeNode> tempRoot = getStrategy().apply(rootNode, Optional.of(originalSnapshotRoot), version);
+        checkState(tempRoot.isPresent(), "Data tree root is not present, possibly removed by previous modification");
 
-        final InMemoryDataTreeSnapshot tempTree = new InMemoryDataTreeSnapshot(snapshot.getSchemaContext(), tempRoot.get(), strategyTree);
+        final InMemoryDataTreeSnapshot tempTree = new InMemoryDataTreeSnapshot(snapshot.getSchemaContext(),
+            tempRoot.get(), strategyTree);
         return tempTree.newModification();
     }
 
@@ -199,6 +211,10 @@ final class InMemoryDataTreeModification implements DataTreeModification {
         return version;
     }
 
+    boolean isSealed() {
+        return sealed == 1;
+    }
+
     private static void applyChildren(final DataTreeModificationCursor cursor, final ModifiedNode node) {
         final Collection<ModifiedNode> children = node.getChildren();
         if (!children.isEmpty()) {
@@ -212,59 +228,71 @@ final class InMemoryDataTreeModification implements DataTreeModification {
 
     private static void applyNode(final DataTreeModificationCursor cursor, final ModifiedNode node) {
         switch (node.getOperation()) {
-        case NONE:
-            break;
-        case DELETE:
-            cursor.delete(node.getIdentifier());
-            break;
-        case MERGE:
-            cursor.merge(node.getIdentifier(), node.getWrittenValue());
-            applyChildren(cursor, node);
-            break;
-        case TOUCH:
-            // TODO: we could improve efficiency of cursor use if we could understand
-            //       nested TOUCH operations. One way of achieving that would be a proxy
-            //       cursor, which would keep track of consecutive enter and exit calls
-            //       and coalesce them.
-            applyChildren(cursor, node);
-            break;
-        case WRITE:
-            cursor.write(node.getIdentifier(), node.getWrittenValue());
-            applyChildren(cursor, node);
-            break;
-        default:
-            throw new IllegalArgumentException("Unhandled node operation " + node.getOperation());
+            case NONE:
+                break;
+            case DELETE:
+                cursor.delete(node.getIdentifier());
+                break;
+            case MERGE:
+                cursor.merge(node.getIdentifier(), node.getWrittenValue());
+                applyChildren(cursor, node);
+                break;
+            case TOUCH:
+                // TODO: we could improve efficiency of cursor use if we could understand
+                //       nested TOUCH operations. One way of achieving that would be a proxy
+                //       cursor, which would keep track of consecutive enter and exit calls
+                //       and coalesce them.
+                applyChildren(cursor, node);
+                break;
+            case WRITE:
+                cursor.write(node.getIdentifier(), node.getWrittenValue());
+                applyChildren(cursor, node);
+                break;
+            default:
+                throw new IllegalArgumentException("Unhandled node operation " + node.getOperation());
         }
     }
 
     @Override
-    public void applyToCursor(final DataTreeModificationCursor cursor) {
+    public void applyToCursor(@Nonnull final DataTreeModificationCursor cursor) {
         for (final ModifiedNode child : rootNode.getChildren()) {
             applyNode(cursor, child);
         }
     }
 
-    private static void checkIdentifierReferencesData(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
+    static void checkIdentifierReferencesData(final PathArgument arg, final NormalizedNode<?, ?> data) {
+        checkArgument(arg.equals(data.getIdentifier()),
+            "Instance identifier references %s but data identifier is %s", arg, data.getIdentifier());
+    }
+
+    private void checkIdentifierReferencesData(final YangInstanceIdentifier path,
+            final NormalizedNode<?, ?> data) {
+        final PathArgument arg;
+
         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());
+            arg = path.getLastPathArgument();
+            checkArgument(arg != null, "Instance identifier %s has invalid null path argument", path);
         } else {
-            final QName type = data.getNodeType();
-            Preconditions.checkArgument(SchemaContext.NAME.equals(type), "Incorrect name %s of root node", type);
+            arg = rootNode.getIdentifier();
         }
+
+        checkIdentifierReferencesData(arg, data);
+    }
+
+    @Override
+    public DataTreeModificationCursor createCursor(@Nonnull final YangInstanceIdentifier path) {
+        final OperationWithModification op = resolveModificationFor(path);
+        return openCursor(new InMemoryDataTreeModificationCursor(this, path, op));
     }
 
     @Override
     public void ready() {
-        final boolean wasRunning = UPDATER.compareAndSet(this, 0, 1);
-        Preconditions.checkState(wasRunning, "Attempted to seal an already-sealed Data Tree.");
+        final boolean wasRunning = SEALED_UPDATER.compareAndSet(this, 0, 1);
+        checkState(wasRunning, "Attempted to seal an already-sealed Data Tree.");
 
-        AbstractReadyIterator current = AbstractReadyIterator.create(rootNode, strategyTree);
+        AbstractReadyIterator current = AbstractReadyIterator.create(rootNode, getStrategy());
         do {
-            current = current.process();
+            current = current.process(version);
         } while (current != null);
     }
-
 }