Merge "Added tests for yang.model.util"
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / tree / SchemaAwareApplyOperation.java
index af0b9738f6c08d943b7d3a098500b28d4a113245..d7aa826c236f4c375ca9e1bd6c3dccf3f5dd7d42 100644 (file)
@@ -9,19 +9,24 @@ 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 org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+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.UnkeyedListEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.ConflictingModificationAppliedException;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.IncorrectDataStructureException;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.MutableTreeNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNodeFactory;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableUnkeyedListEntryNodeBuilder;
 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
@@ -74,7 +79,7 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
         return null;
     }
 
-    public static boolean checkConflicting(final InstanceIdentifier path, final boolean condition, final String message) throws ConflictingModificationAppliedException {
+    public static boolean checkConflicting(final YangInstanceIdentifier path, final boolean condition, final String message) throws ConflictingModificationAppliedException {
         if(!condition) {
             throw new ConflictingModificationAppliedException(path, message);
         }
@@ -101,7 +106,7 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
         }
     }
 
-    private static final void checkNotConflicting(final InstanceIdentifier path, final TreeNode original, final TreeNode current) throws ConflictingModificationAppliedException {
+    private static final void checkNotConflicting(final YangInstanceIdentifier path, final TreeNode original, final TreeNode current) throws ConflictingModificationAppliedException {
         checkConflicting(path, original.getVersion().equals(current.getVersion()),
                 "Node was replaced by other transaction.");
         checkConflicting(path, original.getSubtreeVersion().equals(current.getSubtreeVersion()),
@@ -122,7 +127,7 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
     }
 
     @Override
-    public final void checkApplicable(final InstanceIdentifier path,final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+    public final void checkApplicable(final YangInstanceIdentifier path,final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
         switch (modification.getType()) {
         case DELETE:
             checkDeleteApplicable(modification, current);
@@ -143,7 +148,7 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
 
     }
 
-    protected void checkMergeApplicable(final InstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+    protected void checkMergeApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
         Optional<TreeNode> original = modification.getOriginal();
         if (original.isPresent() && current.isPresent()) {
             /*
@@ -158,12 +163,25 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
         }
     }
 
-    protected void checkWriteApplicable(final InstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+    /**
+     * Checks if write operation can be applied to current TreeNode.
+     * The operation checks if original tree node to which the modification is going to be applied exists and if
+     * current node in TreeNode structure exists.
+     *
+     * @param path Path from current node in TreeNode
+     * @param modification modification to apply
+     * @param current current node in TreeNode for modification to apply
+     * @throws DataValidationFailedException
+     */
+    protected void checkWriteApplicable(final YangInstanceIdentifier path, final NodeModification modification,
+        final Optional<TreeNode> current) throws DataValidationFailedException {
         Optional<TreeNode> original = modification.getOriginal();
         if (original.isPresent() && current.isPresent()) {
             checkNotConflicting(path, original.get(), current.get());
         } else if(original.isPresent()) {
             throw new ConflictingModificationAppliedException(path,"Node was deleted by other transaction.");
+        } else if(current.isPresent()) {
+            throw new ConflictingModificationAppliedException(path,"Node was created by other transaction.");
         }
     }
 
@@ -174,6 +192,10 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
         }
     }
 
+    boolean isOrdered() {
+        return false;
+    }
+
     @Override
     public final Optional<TreeNode> apply(final ModifiedNode modification,
             final Optional<TreeNode> currentMeta, final Version version) {
@@ -189,7 +211,8 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
         case MERGE:
             if(currentMeta.isPresent()) {
                 return modification.storeSnapshot(Optional.of(applyMerge(modification,currentMeta.get(), version)));
-            } // Fallback to write is intentional - if node is not preexisting merge is same as write
+            }
+            // intentional fall-through: if the node does not exist a merge is same as a write
         case WRITE:
             return modification.storeSnapshot(Optional.of(applyWrite(modification, currentMeta, version)));
         case UNMODIFIED:
@@ -218,7 +241,7 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
      * @throws ConflictingModificationAppliedException If subtree was changed in conflicting way
      * @throws IncorrectDataStructureException If subtree modification is not applicable (e.g. leaf node).
      */
-    protected abstract void checkSubtreeModificationApplicable(InstanceIdentifier path, final NodeModification modification,
+    protected abstract void checkSubtreeModificationApplicable(YangInstanceIdentifier path, final NodeModification modification,
             final Optional<TreeNode> current) throws DataValidationFailedException;
 
     protected abstract void verifyWrittenStructure(NormalizedNode<?, ?> writtenValue);
@@ -231,6 +254,11 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
             entryStrategy = Optional.<ModificationApplyOperation> of(new DataNodeContainerModificationStrategy.UnkeyedListItemModificationStrategy(schema));
         }
 
+        @Override
+        boolean isOrdered() {
+            return true;
+        }
+
         @Override
         protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta,
                 final Version version) {
@@ -246,7 +274,65 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
         @Override
         protected TreeNode applyWrite(final ModifiedNode modification,
                 final Optional<TreeNode> currentMeta, final Version version) {
-            return TreeNodeFactory.createTreeNode(modification.getWrittenValue(), version);
+            final NormalizedNode<?, ?> newValue = modification.getWrittenValue();
+            final TreeNode newValueMeta = TreeNodeFactory.createTreeNode(newValue, version);
+
+            if (Iterables.isEmpty(modification.getChildren())) {
+                return newValueMeta;
+            }
+
+            /*
+             * This is where things get interesting. The user has performed a write and
+             * then she applied some more modifications to it. So we need to make sense
+             * of that an apply the operations on top of the written value. We could have
+             * done it during the write, but this operation is potentially expensive, so
+             * we have left it out of the fast path.
+             *
+             * As it turns out, once we materialize the written data, we can share the
+             * code path with the subtree change. So let's create an unsealed TreeNode
+             * and run the common parts on it -- which end with the node being sealed.
+             */
+            final MutableTreeNode mutable = newValueMeta.mutable();
+            mutable.setSubtreeVersion(version);
+
+            @SuppressWarnings("rawtypes")
+            final NormalizedNodeContainerBuilder dataBuilder = ImmutableUnkeyedListEntryNodeBuilder
+                .create((UnkeyedListEntryNode) newValue);
+
+            return mutateChildren(mutable, dataBuilder, version, modification.getChildren());
+        }
+
+        /**
+         * Applies write/remove diff operation for each modification child in modification subtree.
+         * Operation also sets the Data tree references for each Tree Node (Index Node) in meta (MutableTreeNode) structure.
+         *
+         * @param meta MutableTreeNode (IndexTreeNode)
+         * @param data DataBuilder
+         * @param nodeVersion Version of TreeNode
+         * @param modifications modification operations to apply
+         * @return Sealed immutable copy of TreeNode structure with all Data Node references set.
+         */
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        private TreeNode mutateChildren(final MutableTreeNode meta, final NormalizedNodeContainerBuilder data,
+            final Version nodeVersion, final Iterable<ModifiedNode> modifications) {
+
+            for (ModifiedNode mod : modifications) {
+                final PathArgument id = mod.getIdentifier();
+                final Optional<TreeNode> cm = meta.getChild(id);
+
+                Optional<TreeNode> result = resolveChildOperation(id).apply(mod, cm, nodeVersion);
+                if (result.isPresent()) {
+                    final TreeNode tn = result.get();
+                    meta.addChild(tn);
+                    data.addChild(tn.getData());
+                } else {
+                    meta.removeChild(id);
+                    data.removeChild(id);
+                }
+            }
+
+            meta.setData(data.build());
+            return meta.seal();
         }
 
         @Override
@@ -263,7 +349,7 @@ abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
         }
 
         @Override
-        protected void checkSubtreeModificationApplicable(final InstanceIdentifier path, final NodeModification modification,
+        protected void checkSubtreeModificationApplicable(final YangInstanceIdentifier path, final NodeModification modification,
                 final Optional<TreeNode> current) throws IncorrectDataStructureException {
             throw new IncorrectDataStructureException(path, "Subtree modification is not allowed.");
         }