Merge "Update xtend and embed it"
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / tree / ModifiedNode.java
index a8739a6201dcd9bb280fb0de8b06eaaf7ca7b49c..bc6b46d46f6c05f186b5d9511cf9561bb532bec8 100644 (file)
@@ -8,18 +8,21 @@
 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.base.Predicate;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.NotThreadSafe;
 import org.opendaylight.yangtools.concepts.Identifiable;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+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.ModificationType;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
 
-import javax.annotation.concurrent.GuardedBy;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
 /**
  * Node Modification Node and Tree
  *
@@ -29,11 +32,13 @@ import java.util.Map;
  * This tree is lazily created and populated via {@link #modifyChild(PathArgument)}
  * and {@link StoreMetadataNode} which represents original state {@link #getOriginal()}.
  */
+@NotThreadSafe
 final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<PathArgument>, NodeModification {
 
     public static final Predicate<ModifiedNode> IS_TERMINAL_PREDICATE = new Predicate<ModifiedNode>() {
         @Override
-        public boolean apply(final ModifiedNode input) {
+        public boolean apply(final @Nonnull ModifiedNode input) {
+            Preconditions.checkNotNull(input);
             switch (input.getType()) {
             case DELETE:
             case MERGE:
@@ -48,16 +53,22 @@ final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<Pa
         }
     };
 
-    private final Map<PathArgument, ModifiedNode> children = new LinkedHashMap<>();
+    private final Map<PathArgument, ModifiedNode> children;
     private final Optional<TreeNode> original;
     private final PathArgument identifier;
     private ModificationType modificationType = ModificationType.UNMODIFIED;
     private Optional<TreeNode> snapshotCache;
     private NormalizedNode<?, ?> value;
 
-    private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original) {
+    private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original, final boolean isOrdered) {
         this.identifier = identifier;
         this.original = original;
+
+        if (isOrdered) {
+            children = new LinkedHashMap<>();
+        } else {
+            children = new HashMap<>();
+        }
     }
 
     /**
@@ -119,7 +130,7 @@ final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<Pa
      * @return {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.data.ModifiedNode} for specified child, with {@link #getOriginal()}
      *         containing child metadata if child was present in original data.
      */
-    public ModifiedNode modifyChild(final PathArgument child) {
+    public ModifiedNode modifyChild(final PathArgument child, final boolean isOrdered) {
         clearSnapshot();
         if (modificationType == ModificationType.UNMODIFIED) {
             updateModificationType(ModificationType.SUBTREE_MODIFIED);
@@ -137,7 +148,7 @@ final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<Pa
             currentMetadata = Optional.absent();
         }
 
-        ModifiedNode newlyCreated = new ModifiedNode(child, currentMetadata);
+        ModifiedNode newlyCreated = new ModifiedNode(child, currentMetadata, isOrdered);
         children.put(child, newlyCreated);
         return newlyCreated;
     }
@@ -159,10 +170,35 @@ final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<Pa
      *
      */
     public void delete() {
+        final ModificationType newType;
+
+        switch (modificationType) {
+        case DELETE:
+        case UNMODIFIED:
+            // We need to record this delete.
+            newType = ModificationType.DELETE;
+            break;
+        case MERGE:
+        case SUBTREE_MODIFIED:
+        case WRITE:
+            /*
+             * We are canceling a previous modification. This is a bit tricky,
+             * as the original write may have just introduced the data, or it
+             * may have modified it.
+             *
+             * As documented in BUG-2470, a delete of data introduced in this
+             * transaction needs to be turned into a no-op.
+             */
+            newType = original.isPresent() ? ModificationType.DELETE : ModificationType.UNMODIFIED;
+            break;
+        default:
+            throw new IllegalStateException("Unhandled deletion of node with " + modificationType);
+        }
+
         clearSnapshot();
-        updateModificationType(ModificationType.DELETE);
         children.clear();
         this.value = null;
+        updateModificationType(newType);
     }
 
     /**
@@ -185,10 +221,28 @@ final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<Pa
         this.value = data;
     }
 
+    /**
+     * Seal the modification node and prune any children which has not been
+     * modified.
+     */
     void seal() {
         clearSnapshot();
-        for (ModifiedNode child : children.values()) {
+
+        // Walk all child nodes and remove any children which have not
+        // been modified.
+        final Iterator<ModifiedNode> it = children.values().iterator();
+        while (it.hasNext()) {
+            final ModifiedNode child = it.next();
             child.seal();
+
+            if (child.modificationType == ModificationType.UNMODIFIED) {
+                it.remove();
+            }
+        }
+
+        // A SUBTREE_MODIFIED node without any children is a no-op
+        if (modificationType == ModificationType.SUBTREE_MODIFIED && children.isEmpty()) {
+            updateModificationType(ModificationType.UNMODIFIED);
         }
     }
 
@@ -205,7 +259,6 @@ final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<Pa
         return Optional.fromNullable(snapshotCache);
     }
 
-    @GuardedBy("this")
     private void updateModificationType(final ModificationType type) {
         modificationType = type;
         clearSnapshot();
@@ -217,7 +270,20 @@ final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<Pa
                 + modificationType + ", childModification=" + children + "]";
     }
 
-    public static ModifiedNode createUnmodified(final TreeNode metadataTree) {
-        return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree));
+    /**
+     * Create a node which will reflect the state of this node, except it will behave as newly-written
+     * value. This is useful only for merge validation.
+     *
+     * @param value Value associated with the node
+     * @return An isolated node. This node should never reach a datatree.
+     */
+    ModifiedNode asNewlyWritten(final NormalizedNode<?, ?> value) {
+        final ModifiedNode ret = new ModifiedNode(getIdentifier(), Optional.<TreeNode>absent(), false);
+        ret.write(value);
+        return ret;
+    }
+
+    public static ModifiedNode createUnmodified(final TreeNode metadataTree, final boolean isOrdered) {
+        return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree), isOrdered);
     }
 }