HONEYCOMB-350 - APPEAR/DISAPPEAR modification handling 77/6177/5
authorJan Srnicek <jsrnicek@cisco.com>
Tue, 9 May 2017 13:28:14 +0000 (15:28 +0200)
committerMarek Gradzki <mgradzki@cisco.com>
Tue, 9 May 2017 15:39:43 +0000 (15:39 +0000)
Allows these types of modifications to check in depth,
to see if some of their children nodes were not modified

Change-Id: Ice2f988732c2d9ecad8e960c4f10d01863fb0cfd
Signed-off-by: Jan Srnicek <jsrnicek@cisco.com>
22 files changed:
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/AugmentationRewriteDeleteProducer.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/CaseRewriteDeleteProducer.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ChoiceRewriteDeleteProducer.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ContainerRewriteDeleteProducer.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/DelegatingRewriteDeleteProducer.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafListRewriteDeleteProducer.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafRewriteDeleteProducer.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ListRewriteDeleteProducer.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegator.java
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/Modification.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeRewriteDeleteRegistry.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeUpdate.java [new file with mode: 0644]
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/RewriteDeleteProducer.java [new file with mode: 0644]
infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegatorTest.java
infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationBaseTest.java [new file with mode: 0644]
infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffAugRewriteDeleteTest.java [new file with mode: 0644]
infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffNestedAugRewriteDeleteTest.java [new file with mode: 0644]
infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffRewriteDeleteTest.java [new file with mode: 0644]
infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffTest.java
infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationMetadata.java [new file with mode: 0644]
infra/data-impl/src/test/resources/test-diff.yang

diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/AugmentationRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/AugmentationRewriteDeleteProducer.java
new file mode 100644 (file)
index 0000000..1a9397e
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class AugmentationRewriteDeleteProducer extends DelegatingRewriteDeleteProducer implements RewriteDeleteProducer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AugmentationRewriteDeleteProducer.class);
+
+    AugmentationRewriteDeleteProducer(
+            @Nonnull final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer) {
+        super(baseRewriteProducer);
+    }
+
+    @Override
+    public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+                                                              @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        LOG.debug("Processing {} as augmentation", topLevelIdentifier);
+        return super.normalizedUpdates(topLevelIdentifier, entry);
+    }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/CaseRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/CaseRewriteDeleteProducer.java
new file mode 100644 (file)
index 0000000..37fdef0
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class CaseRewriteDeleteProducer extends DelegatingRewriteDeleteProducer implements RewriteDeleteProducer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CaseRewriteDeleteProducer.class);
+
+    CaseRewriteDeleteProducer(@Nonnull final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer) {
+        super(baseRewriteProducer);
+    }
+
+    @Override
+    public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+                                                              @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        // just delegates to lower level, there is nothing to detect on case level
+        LOG.debug("Processing {} as case", topLevelIdentifier);
+        return super.normalizedUpdates(topLevelIdentifier, entry);
+    }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ChoiceRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ChoiceRewriteDeleteProducer.java
new file mode 100644 (file)
index 0000000..4c249e0
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ChoiceRewriteDeleteProducer extends DelegatingRewriteDeleteProducer implements RewriteDeleteProducer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ChoiceRewriteDeleteProducer.class);
+
+    ChoiceRewriteDeleteProducer(@Nonnull final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer) {
+        super(baseRewriteProducer);
+    }
+
+    @Override
+    public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+                                                              @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        // just delegates to lower level, there is nothing to detect on choice level
+        LOG.debug("Processing {} as choice", topLevelIdentifier);
+        return super.normalizedUpdates(topLevelIdentifier, entry);
+    }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ContainerRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ContainerRewriteDeleteProducer.java
new file mode 100644 (file)
index 0000000..7a2b31e
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ContainerRewriteDeleteProducer extends DelegatingRewriteDeleteProducer implements RewriteDeleteProducer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ContainerRewriteDeleteProducer.class);
+
+    private final SchemaContext ctx;
+
+    ContainerRewriteDeleteProducer(@Nonnull final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer,
+                                   @Nonnull final SchemaContext ctx) {
+        super(baseRewriteProducer);
+        this.ctx = ctx;
+    }
+
+    @Override
+    public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+                                                              @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        final ContainerSchemaNode containerSchemaNode =
+                (ContainerSchemaNode) SchemaContextUtil
+                        .findDataSchemaNode(ctx, getSchemaPath(topLevelIdentifier, entry));
+
+        if (containerSchemaNode.isPresenceContainer()) {
+            LOG.debug("Processing {} as presence container", topLevelIdentifier);
+            // if presence container  - create delete right away
+            return ((ContainerNode) entry.getValue()).getValue().stream()
+                    .map(containerNode -> new NormalizedNodeUpdate(
+                            YangInstanceIdentifier.builder(topLevelIdentifier)
+                                    .node(containerNode.getIdentifier()).build(), containerNode, null))
+                    .collect(Collectors.toList());
+        } else {
+            LOG.debug("Processing {} as non-presence container", topLevelIdentifier);
+            // if non-presence - goes deep with base logic
+            return super.normalizedUpdates(topLevelIdentifier, entry);
+        }
+    }
+
+    private static SchemaPath getSchemaPath(final YangInstanceIdentifier topLevelIdentifier,
+                                            final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        return SchemaPath.create(extractSchemaPathQNames(topLevelIdentifier, entry), true);
+    }
+
+    private static List<QName> extractSchemaPathQNames(final YangInstanceIdentifier topLevelIdentifier,
+                                                       final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        // must be filtered out of augmentation and keyed NodeIdentifiers
+        return Stream.concat(topLevelIdentifier.getPathArguments().stream(), Stream.of(entry.getKey()))
+                .filter(pathArgument -> pathArgument instanceof YangInstanceIdentifier.NodeIdentifier)
+                .map(YangInstanceIdentifier.PathArgument::getNodeType)
+                .collect(Collectors.toList());
+    }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/DelegatingRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/DelegatingRewriteDeleteProducer.java
new file mode 100644 (file)
index 0000000..e3b3c74
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+
+/**
+ * Basic implementation for nodes that are not considered modifications but their children could be
+ */
+abstract class DelegatingRewriteDeleteProducer implements RewriteDeleteProducer {
+
+    private final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer;
+
+    DelegatingRewriteDeleteProducer(@Nonnull final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer) {
+        this.baseRewriteProducer = baseRewriteProducer;
+    }
+
+    @Override
+    public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+                                                              @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        // just delegates to lower level
+        checkState(entry.getValue() instanceof DataContainerNode, "Unable to extract children");
+        checkState(entry.getValue() instanceof DataContainerChild, "Unable to extract identifier");
+        final Collection<DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> value =
+                DataContainerNode.class.cast(entry.getValue()).getValue();
+        return value.stream()
+                .map(DataContainerChild.class::cast)
+                .flatMap(node -> baseRewriteProducer
+                        .normalizedUpdates(childYangId(topLevelIdentifier, entry), childMapEntry(node)).stream())
+                .collect(Collectors.toList());
+    }
+
+    private static AbstractMap.SimpleEntry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> childMapEntry(
+            final DataContainerChild node) {
+        return new HashMap.SimpleEntry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>>(
+                node.getIdentifier(), node);
+    }
+
+    private static YangInstanceIdentifier childYangId(final @Nonnull YangInstanceIdentifier topLevelIdentifier,
+                                                      final @Nonnull Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        return YangInstanceIdentifier.builder(topLevelIdentifier)
+                .node(entry.getKey()).build();
+    }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafListRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafListRewriteDeleteProducer.java
new file mode 100644 (file)
index 0000000..ca78996
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class LeafListRewriteDeleteProducer implements RewriteDeleteProducer{
+
+    private static final Logger LOG = LoggerFactory.getLogger(LeafListRewriteDeleteProducer.class);
+
+    @Override
+    public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+                                                              @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        // if leaf-list,just builds delete right away
+        LOG.debug("Processing {} as leaf-list", topLevelIdentifier);
+        return Collections.singletonList(new NormalizedNodeUpdate(YangInstanceIdentifier.builder(topLevelIdentifier)
+                .node(entry.getKey()).build(), entry.getValue(), null));
+    }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafRewriteDeleteProducer.java
new file mode 100644 (file)
index 0000000..ddf0d64
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class LeafRewriteDeleteProducer implements RewriteDeleteProducer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(LeafRewriteDeleteProducer.class);
+
+    @Override
+    public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+                                                              @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        // if leaf,just builds delete right away
+        LOG.debug("Processing {} as leaf", topLevelIdentifier);
+        return Collections.singletonList(new NormalizedNodeUpdate(YangInstanceIdentifier.builder(topLevelIdentifier)
+                .node(entry.getKey()).build(), entry.getValue(), null));
+    }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ListRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ListRewriteDeleteProducer.java
new file mode 100644 (file)
index 0000000..793cab8
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ListRewriteDeleteProducer implements RewriteDeleteProducer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ListRewriteDeleteProducer.class);
+
+    @Override
+    public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+                                                              @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        // identifier has different format : parent_node/list_node/list_node_with_key
+        LOG.debug("Processing {} as list", topLevelIdentifier);
+        return ((MapNode) entry.getValue()).getValue().stream()
+                .map(mapEntryNode -> new NormalizedNodeUpdate(YangInstanceIdentifier.builder(topLevelIdentifier)
+                        .node(mapEntryNode.getNodeType())
+                        .node(mapEntryNode.getIdentifier()).build(), mapEntryNode, null))
+                .collect(Collectors.toList());
+    }
+}
index f4989aa..ccc4057 100644 (file)
@@ -119,8 +119,8 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
             LOG.trace("ConfigDataTree.modify() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}",
                 rootPath, rootNode, rootNode.getDataBefore(), rootNode.getDataAfter());
 
-            final ModificationDiff modificationDiff =
-                    ModificationDiff.recursivelyFromCandidateRoot(rootNode, schema);
+            final ModificationDiff modificationDiff = new ModificationDiff.ModificationDiffBuilder()
+                    .setCtx(schema).build(rootNode);
             LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff);
 
             // Distinguish between updates (create + update) and deletes
@@ -194,25 +194,25 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
         }
 
         private WriterRegistry.DataObjectUpdates toBindingAware(
-                final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biNodes) {
+                final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes) {
             return ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
         }
     }
 
     @VisibleForTesting
     static WriterRegistry.DataObjectUpdates toBindingAware(
-            final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biNodes,
+            final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes,
             final BindingNormalizedNodeSerializer serializer) {
 
         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> dataObjectUpdates = HashMultimap.create();
         final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> dataObjectDeletes =
                 HashMultimap.create();
 
-        for (Map.Entry<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biEntry : biNodes.entrySet()) {
+        for (Map.Entry<YangInstanceIdentifier, NormalizedNodeUpdate> biEntry : biNodes.entrySet()) {
             final InstanceIdentifier<?> unkeyedIid =
                     RWUtils.makeIidWildcarded(serializer.fromYangInstanceIdentifier(biEntry.getKey()));
 
-            ModificationDiff.NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
+            NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
             final DataObjectUpdate dataObjectUpdate = toDataObjectUpdate(normalizedNodeUpdate, serializer);
             if (dataObjectUpdate != null) {
                 if (dataObjectUpdate instanceof DataObjectUpdate.DataObjectDelete) {
@@ -227,7 +227,7 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
 
     @Nullable
     private static DataObjectUpdate toDataObjectUpdate(
-            final ModificationDiff.NormalizedNodeUpdate normalizedNodeUpdate,
+            final NormalizedNodeUpdate normalizedNodeUpdate,
             final BindingNormalizedNodeSerializer serializer) {
 
         InstanceIdentifier<?> baId = serializer.fromYangInstanceIdentifier(normalizedNodeUpdate.getId());
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/Modification.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/Modification.java
new file mode 100644 (file)
index 0000000..88823c5
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.MixinNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+/**
+ * Intermediate representation of a modification + its schema.
+ */
+final class Modification {
+    private final YangInstanceIdentifier id;
+    private final DataTreeCandidateNode dataCandidate;
+    // Using Object as type for schema node since it's the only type that's a parent to all schema node types from
+    // yangtools. The hierarchy does not use e.g. SchemaNode class for all types
+    private final Object parentNode;
+    private final Object schemaNode;
+    private final boolean updateParentNode;
+
+    private Modification(final YangInstanceIdentifier id,
+                         final DataTreeCandidateNode dataCandidate,
+                         final Object parentNode,
+                         final Object schemaNode,
+                         final boolean updateParentNode) {
+        this.id = id;
+        this.dataCandidate = dataCandidate;
+        this.parentNode = parentNode;
+        this.schemaNode = schemaNode;
+        // controls process of updating parent node while moving down the schema tree:
+        this.updateParentNode = updateParentNode;
+    }
+
+    Modification(final YangInstanceIdentifier id,
+                 final DataTreeCandidateNode dataCandidate,
+                 final Object parentNode,
+                 final Object schemaNode) {
+        this(id, dataCandidate, parentNode, schemaNode, true);
+    }
+
+    Modification(final YangInstanceIdentifier id,
+                 final DataTreeCandidateNode dataCandidate,
+                 final Object schemaNode) {
+        this(id, dataCandidate, schemaNode, schemaNode);
+    }
+
+    List<Modification> getChildNodes() {
+        return streamChildren().collect(Collectors.toList());
+    }
+
+    YangInstanceIdentifier getId() {
+        return id;
+    }
+
+    ModificationType getModificationType() {
+        return dataCandidate.getModificationType();
+    }
+
+    com.google.common.base.Optional<NormalizedNode<?, ?>> getDataBefore() {
+        return dataCandidate.getDataBefore();
+    }
+
+    com.google.common.base.Optional<NormalizedNode<?, ?>> getDataAfter() {
+        return dataCandidate.getDataAfter();
+    }
+
+    Object getSchemaNode() {
+        return schemaNode;
+    }
+
+    boolean is(final Class<?> schemaType) {
+        return schemaType.isAssignableFrom(schemaNode.getClass());
+    }
+
+    boolean isMixin() {
+        // Checking whether node is a mixin is not performed on schema, but on data since mixin is
+        // only a NormalizedNode concept, not a schema concept
+        return dataCandidate.getDataBefore().orNull() instanceof MixinNode ||
+                dataCandidate.getDataAfter().orNull() instanceof MixinNode;
+    }
+
+    boolean isBeforeAndAfterDifferent() {
+        if (dataCandidate.getDataBefore().isPresent()) {
+            return !dataCandidate.getDataBefore().get().equals(dataCandidate.getDataAfter().orNull());
+        }
+
+        // considering not a modification if data after is also null
+        return dataCandidate.getDataAfter().isPresent();
+    }
+
+    private AugmentationSchema findAugmentation(Object currentNode,
+                                                final YangInstanceIdentifier.AugmentationIdentifier identifier) {
+        if (currentNode != null) {
+            // check if identifier points to some augmentation of currentNode
+            if (currentNode instanceof AugmentationTarget) {
+                Optional<AugmentationSchema> augmentationSchema =
+                    ((AugmentationTarget) currentNode).getAvailableAugmentations().stream()
+                        .filter(aug -> identifier.equals(new YangInstanceIdentifier.AugmentationIdentifier(
+                            aug.getChildNodes().stream()
+                                .map(SchemaNode::getQName)
+                                .collect(Collectors.toSet()))))
+                        .findFirst();
+                if (augmentationSchema.isPresent()) {
+                    return augmentationSchema.get();
+                }
+            }
+
+            // continue search:
+            Collection<DataSchemaNode> childNodes = Collections.emptyList();
+            if (currentNode instanceof DataNodeContainer) {
+                childNodes = ((DataNodeContainer) currentNode).getChildNodes();
+            } else if (currentNode instanceof ChoiceSchemaNode) {
+                childNodes = ((ChoiceSchemaNode) currentNode).getCases().stream()
+                    .flatMap(cas -> cas.getChildNodes().stream()).collect(Collectors.toList());
+            }
+            return childNodes.stream().map(n -> findAugmentation(n, identifier)).filter(n -> n != null).findFirst()
+                .orElse(null);
+        } else {
+            return null;
+        }
+    }
+
+    Stream<Modification> streamChildren() {
+        return dataCandidate.getChildNodes().stream()
+            .map(child -> {
+                final YangInstanceIdentifier childId = id.node(child.getIdentifier());
+                final Object schemaChild = schemaChild(schemaNode, child.getIdentifier());
+
+                // An augment cannot change other augment, so we do not update parent node if we are streaming
+                // children of AugmentationSchema (otherwise we would fail to find schema for nested augmentations):
+                if (updateParentNode) {
+                    if (schemaNode instanceof AugmentationSchema) {
+                        // child nodes would not have nested augmentations, so we stop moving parentNode:
+                        return new Modification(childId, child, parentNode, schemaChild, false);
+                    } else {
+                        // update parent node:
+                        return new Modification(childId, child, schemaNode, schemaChild, true);
+                    }
+                }
+                return new Modification(childId, child, parentNode, schemaChild, updateParentNode);
+            });
+    }
+
+    /**
+     * Find next schema node in hierarchy.
+     */
+    private Object schemaChild(final Object schemaNode, final YangInstanceIdentifier.PathArgument identifier) {
+        Object found = null;
+
+        if (identifier instanceof YangInstanceIdentifier.AugmentationIdentifier) {
+            if (schemaNode instanceof AugmentationTarget) {
+                // Find matching augmentation
+                found = ((AugmentationTarget) schemaNode).getAvailableAugmentations().stream()
+                    .filter(aug -> identifier.equals(new YangInstanceIdentifier.AugmentationIdentifier(
+                        aug.getChildNodes().stream()
+                            .map(SchemaNode::getQName)
+                            .collect(Collectors.toSet()))))
+                    .findFirst()
+                    .orElse(null);
+
+                if (found == null) {
+                    // An augment cannot change other augment, but all augments only change their targets (data nodes).
+                    //
+                    // As a consequence, if nested augmentations are present,
+                    // AugmentationSchema might reference child schema node instances that do not include changes
+                    // from nested augments.
+                    //
+                    // But schemaNode, as mentioned earlier, contains all the changes introduced by augments.
+                    //
+                    // On the other hand, in case of augments which introduce leaves,
+                    // we need to address AugmentationSchema node directly so we can't simply do
+                    // found = schemaNode;
+                    //
+                    found =
+                        findAugmentation(parentNode, (YangInstanceIdentifier.AugmentationIdentifier) identifier);
+                }
+            }
+        } else if (schemaNode instanceof DataNodeContainer) {
+            // Special handling for list aggregator nodes. If we are at list aggregator node e.g. MapNode and
+            // we are searching for schema for a list entry e.g. MapEntryNode just return the same schema
+            if (schemaNode instanceof ListSchemaNode &&
+                ((SchemaNode) schemaNode).getQName().equals(identifier.getNodeType())) {
+                found = schemaNode;
+            } else {
+                found = ((DataNodeContainer) schemaNode).getDataChildByName(identifier.getNodeType());
+            }
+        } else if (schemaNode instanceof ChoiceSchemaNode) {
+            // For choices, iterate through all the cases
+            final Optional<DataSchemaNode> maybeChild = ((ChoiceSchemaNode) schemaNode).getCases().stream()
+                    .flatMap(cas -> cas.getChildNodes().stream())
+                    .filter(child -> child.getQName().equals(identifier.getNodeType()))
+                    .findFirst();
+            if (maybeChild.isPresent()) {
+                found = maybeChild.get();
+            }
+            // Special handling for leaf-list nodes. Basically the same as is for list mixin nodes
+        } else if (schemaNode instanceof LeafListSchemaNode &&
+            ((SchemaNode) schemaNode).getQName().equals(identifier.getNodeType())) {
+            found = schemaNode;
+        }
+
+        return checkNotNull(found, "Unable to find child node in: %s identifiable by: %s", schemaNode, identifier);
+    }
+
+    @Override
+    public String toString() {
+        return "Modification{" +
+                "id=" + id +
+                '}';
+    }
+}
index 863f8ab..74e21df 100644 (file)
 
 package io.fd.honeycomb.data.impl;
 
-import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
-import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.APPEARED;
 import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.DELETE;
-import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.DISAPPEARED;
 import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.WRITE;
 
 import com.google.common.collect.ImmutableMap;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.MixinNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.impl.schema.nodes.AbstractImmutableDataContainerNode;
 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
-import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -63,7 +50,6 @@ final class ModificationDiff {
 
     private static final ModificationDiff EMPTY_DIFF = new ModificationDiff(Collections.emptyMap());
     private static final EnumSet VALID_MODIFICATIONS = EnumSet.of(WRITE, DELETE);
-    private static final EnumSet IGNORED_MODIFICATIONS = EnumSet.of(APPEARED, DISAPPEARED);
 
     private final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates;
 
@@ -105,391 +91,166 @@ final class ModificationDiff {
         return new ModificationDiff(ImmutableMap.of(modification.getId(), NormalizedNodeUpdate.create(modification)));
     }
 
-    /**
-     * Produce an aggregated diff from a candidate node recursively. MixinNodes are ignored as modifications and so
-     * are complex nodes which direct leaves were not modified.
-     */
-    @Nonnull
-    static ModificationDiff recursivelyFromCandidate(@Nonnull final Modification modification) {
-        // recursively process child nodes for exact modifications
-        return recursivelyChildrenFromCandidate(modification)
-                // also add modification on current level, if elligible
-                .merge(isModification(modification)
-                        ? ModificationDiff.create(modification)
-                        : EMPTY_DIFF);
-    }
-
-    /**
-     * Same as {@link #recursivelyFromCandidate(Modification)} but does
-     * not process the root node for modifications, since it's the artificial data root, that has no child leaves but
-     * always is marked as SUBTREE_MODIFIED.
-     */
-    @Nonnull
-    static ModificationDiff recursivelyFromCandidateRoot(@Nonnull final DataTreeCandidateNode currentCandidate,
-                                                         @Nonnull final SchemaContext ctx) {
-        return recursivelyChildrenFromCandidate(new Modification(YangInstanceIdentifier.EMPTY, currentCandidate, ctx));
-    }
-
-    /**
-     * Check whether current node was modified. {@link MixinNode}s are ignored
-     * and only nodes which direct leaves(or choices) are modified are considered a modification.
-     */
-    private static Boolean isModification(@Nonnull final Modification modification) {
-        // Disappear is not a modification
-        if (IGNORED_MODIFICATIONS.contains(modification.getModificationType())) {
-            return false;
-        // Mixin nodes are not considered modifications
-        } else if (modification.isMixin() && !modification.is(AugmentationSchema.class)) {
-            return false;
-        } else {
-            return isCurrentModified(modification);
-        }
-    }
-
-    private static Boolean isCurrentModified(@Nonnull final Modification modification) {
-        // First check if it's an empty presence node
-        final boolean emptyPresenceNode = isEmptyPresenceNode(modification);
-
-        // Check if there are any modified leaves and if so, consider current node as modified
-        final Boolean directLeavesModified = emptyPresenceNode
-                || modification.streamChildren()
-                // Checking leaf or leaf-lists children for direct modification, which means that leafs of leaf lists
-                // trigger a modification on parent node
-                .filter(child -> child.is(LeafSchemaNode.class) || child.is(LeafListSchemaNode.class))
-                // For some reason, we get modifications on unmodified list keys
-                // and that messes up our modifications collection here, so we need to skip
-                .filter(Modification::isBeforeAndAfterDifferent)
-                .filter(child -> VALID_MODIFICATIONS.contains(child.getModificationType()))
-                .findFirst()
-                .isPresent();
-
-        // Also as fallback check choices (choices do not exist in BA world and if anything within a choice was modified,
-        // consider its parent as being modified)
-        final boolean modified = directLeavesModified
-                || modification.streamChildren()
-                .filter(child -> child.is(ChoiceSchemaNode.class))
-                // Recursively check each choice if there was any change to it
-                .filter(ModificationDiff::isCurrentModified)
-                .findFirst()
-                .isPresent();
-
-        if (modified) {
-            LOG.debug("Modification detected as {} at {}",
-                    modification.getModificationType(), modification.getId());
-        }
-
-        return modified;
-    }
-
-    /**
-     * Check if new data are empty but still to be considered as a modification, meaning it's presence has a meaning
-     * e.g. containers with presence statement.
-     */
-    private static boolean isEmptyPresenceNode(@Nonnull final Modification modification) {
-        return modification.is(ContainerSchemaNode.class)
-                && ((ContainerSchemaNode) modification.getSchemaNode()).isPresenceContainer()
-                && modification.getChildNodes().isEmpty()
-                && VALID_MODIFICATIONS.contains(modification.getModificationType());
-    }
-
-    /**
-     * Process all non-leaf child nodes recursively, creating aggregated {@link ModificationDiff}.
-     */
-    private static ModificationDiff recursivelyChildrenFromCandidate(@Nonnull final Modification modification) {
-        // recursively process child nodes for specific modifications
-        return modification.streamChildren()
-                .filter(child -> !child.is(LeafSchemaNode.class))
-                .map(ModificationDiff::recursivelyFromCandidate)
-                .reduce(ModificationDiff::merge)
-                .orElse(EMPTY_DIFF);
-    }
-
     @Override
     public String toString() {
         return "ModificationDiff{updates=" + updates + '}';
     }
 
-    /**
-     * Update to a normalized node identifiable by its {@link YangInstanceIdentifier}.
-     */
-    static final class NormalizedNodeUpdate {
+    static final class ModificationDiffBuilder {
+        private NormalizedNodeRewriteDeleteRegistry registry;
+        private SchemaContext ctx;
 
-        @Nonnull
-        private final YangInstanceIdentifier id;
-        @Nullable
-        private final NormalizedNode<?, ?> dataBefore;
-        @Nullable
-        private final NormalizedNode<?, ?> dataAfter;
-
-        private NormalizedNodeUpdate(@Nonnull final YangInstanceIdentifier id,
-                                     @Nullable final NormalizedNode<?, ?> dataBefore,
-                                     @Nullable final NormalizedNode<?, ?> dataAfter) {
-            this.id = checkNotNull(id);
-            this.dataAfter = dataAfter;
-            this.dataBefore = dataBefore;
+        ModificationDiffBuilder setCtx(final SchemaContext ctx) {
+            this.ctx = ctx;
+            registry = new NormalizedNodeRewriteDeleteRegistry(ctx);
+            return this;
         }
 
-        @Nullable
-        public NormalizedNode<?, ?> getDataBefore() {
-            return dataBefore;
-        }
+        ModificationDiff build(@Nonnull final DataTreeCandidateNode currentCandidate) {
+            checkNotNull(currentCandidate, "Data tree candidate cannot be null");
+            checkNotNull(ctx, "Schema ctx cannot be null");
 
-        @Nullable
-        public NormalizedNode<?, ?> getDataAfter() {
-            return dataAfter;
+            return recursivelyFromCandidateRoot(currentCandidate, ctx);
         }
 
+        /**
+         * Produce an aggregated diff from a candidate node recursively. MixinNodes are ignored as modifications and so
+         * are complex nodes which direct leaves were not modified.
+         */
         @Nonnull
-        public YangInstanceIdentifier getId() {
-            return id;
-        }
-
-        static NormalizedNodeUpdate create(@Nonnull final Modification modification) {
-            final com.google.common.base.Optional<NormalizedNode<?, ?>> beforeData =
-                    modification.getDataBefore();
-            final com.google.common.base.Optional<NormalizedNode<?, ?>> afterData =
-                    modification.getDataAfter();
-            checkArgument(beforeData.isPresent() || afterData.isPresent(),
-                    "Both before and after data are null for %s", modification.getId());
-            return NormalizedNodeUpdate.create(modification.getId(), beforeData.orNull(), afterData.orNull());
-        }
-
-        static NormalizedNodeUpdate create(@Nonnull final YangInstanceIdentifier id,
-                                           @Nullable final NormalizedNode<?, ?> dataBefore,
-                                           @Nullable final NormalizedNode<?, ?> dataAfter) {
-            return new NormalizedNodeUpdate(id, dataBefore, dataAfter);
-        }
-
-        @Override
-        public boolean equals(final Object other) {
-            if (this == other) {
-                return true;
-            }
-            if (other == null || getClass() != other.getClass()) {
-                return false;
-            }
-
-            final NormalizedNodeUpdate that = (NormalizedNodeUpdate) other;
-
-            return id.equals(that.id);
-
-        }
-
-        @Override
-        public int hashCode() {
-            return id.hashCode();
-        }
-
-        @Override
-        public String toString() {
-            return "NormalizedNodeUpdate{" + "id=" + id
-                    + ", dataBefore=" + dataBefore
-                    + ", dataAfter=" + dataAfter
-                    + '}';
-        }
-    }
-
-    /**
-     * Intermediate representation of a modification + its schema.
-     */
-    private static final class Modification {
-        private final YangInstanceIdentifier id;
-        private final DataTreeCandidateNode dataCandidate;
-        // Using Object as type for schema node since it's the only type that's a parent to all schema node types from
-        // yangtools. The hierarchy does not use e.g. SchemaNode class for all types
-        private final Object parentNode;
-        private final Object schemaNode;
-        private final boolean updateParentNode;
-
-        private Modification(final YangInstanceIdentifier id,
-                             final DataTreeCandidateNode dataCandidate,
-                             final Object parentNode,
-                             final Object schemaNode,
-                             final boolean updateParentNode) {
-            this.id = id;
-            this.dataCandidate = dataCandidate;
-            this.parentNode = parentNode;
-            this.schemaNode = schemaNode;
-            // controls process of updating parent node while moving down the schema tree:
-            this.updateParentNode = updateParentNode;
+        ModificationDiff recursivelyFromCandidate(@Nonnull final Modification modification) {
+            // recursively process child nodes for exact modifications
+            return recursivelyChildrenFromCandidate(modification)
+                    // also add modification on current level, if elligible
+                    .merge(isModification(modification)
+                            ? ModificationDiff.create(modification)
+                            // Modification that writes only non-presence container to override nested nodes wont have
+                            // child nodes(in data tree candidate) so logic before will not detected such change, so checking directly
+                            : isNonPresenceOverride(modification)
+                                    ? detectUnderDisappearedNonPresenceContainer(modification)
+                                    : EMPTY_DIFF);
         }
 
-        Modification(final YangInstanceIdentifier id,
-                     final DataTreeCandidateNode dataCandidate,
-                     final Object parentNode,
-                     final Object schemaNode) {
-            this(id, dataCandidate, parentNode, schemaNode, true);
-        }
+        private ModificationDiff detectUnderDisappearedNonPresenceContainer(
+                @Nonnull final Modification modification) {
+            final com.google.common.base.Optional<NormalizedNode<?, ?>> dataBefore = modification.getDataBefore();
 
-        Modification(final YangInstanceIdentifier id,
-                     final DataTreeCandidateNode dataCandidate,
-                     final Object schemaNode) {
-            this(id, dataCandidate, schemaNode, schemaNode);
-        }
+            // is disappear case
+            if (dataBefore.isPresent()) {
+                final NormalizedNode<?, ?> parentData = dataBefore.get();
 
-        List<Modification> getChildNodes() {
-            return streamChildren().collect(Collectors.toList());
-        }
+                // have potential to extract children
+                if (parentData instanceof AbstractImmutableDataContainerNode) {
+                    final AbstractImmutableDataContainerNode<YangInstanceIdentifier.PathArgument> parentContainerNode =
+                            (AbstractImmutableDataContainerNode) parentData;
 
-        YangInstanceIdentifier getId() {
-            return id;
-        }
+                    final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates =
+                            parentContainerNode.getChildren().entrySet().stream()
+                                    .flatMap(entry -> registry.normalizedUpdates(modification.getId(), entry).stream())
+                                    .collect(Collectors.toMap(NormalizedNodeUpdate::getId, update -> update));
 
-        ModificationType getModificationType() {
-            return dataCandidate.getModificationType();
-        }
-
-        com.google.common.base.Optional<NormalizedNode<?, ?>> getDataBefore() {
-            return dataCandidate.getDataBefore();
-        }
-
-        com.google.common.base.Optional<NormalizedNode<?, ?>> getDataAfter() {
-            return dataCandidate.getDataAfter();
-        }
-
-        Object getSchemaNode() {
-            return schemaNode;
-        }
-
-        boolean is(final Class<?> schemaType) {
-            return schemaType.isAssignableFrom(schemaNode.getClass());
+                    return new ModificationDiff(updates);
+                }
+            }
+            return EMPTY_DIFF;
         }
 
-        boolean isMixin() {
-            // Checking whether node is a mixin is not performed on schema, but on data since mixin is
-            // only a NormalizedNode concept, not a schema concept
-            return dataCandidate.getDataBefore().orNull() instanceof MixinNode ||
-                    dataCandidate.getDataAfter().orNull() instanceof MixinNode;
+        /**
+         * Same as {@link #recursivelyFromCandidate(Modification)} but does not process the root node for modifications,
+         * since it's the artificial data root, that has no child leaves but always is marked as SUBTREE_MODIFIED.
+         */
+        @Nonnull
+        ModificationDiff recursivelyFromCandidateRoot(@Nonnull final DataTreeCandidateNode currentCandidate,
+                                                      @Nonnull final SchemaContext ctx) {
+            return recursivelyChildrenFromCandidate(
+                    new Modification(YangInstanceIdentifier.EMPTY, currentCandidate, ctx));
         }
 
-        private boolean isBeforeAndAfterDifferent() {
-            if (dataCandidate.getDataBefore().isPresent()) {
-                return !dataCandidate.getDataBefore().get().equals(dataCandidate.getDataAfter().orNull());
+        /**
+         * Check whether current node was modified. {@link MixinNode}s are ignored
+         * and only nodes which direct leaves(or choices) are modified are considered a modification.
+         */
+        private Boolean isModification(@Nonnull final Modification modification) {
+            // APPEAR/DISAPPEAR are not valid modifications, but some of the children can be modified
+            // aka. list entry added to nested list under non-presence container, which would be resolved as APPEAR for
+            // that container, but MERGE for nested list
+            if (modification.isMixin() && !modification.is(AugmentationSchema.class)) {
+                return false;
+            } else {
+                return isCurrentModified(modification);
             }
-
-            // considering not a modification if data after is also null
-            return dataCandidate.getDataAfter().isPresent();
         }
 
-        private AugmentationSchema findAugmentation(Object currentNode,
-                                                    final YangInstanceIdentifier.AugmentationIdentifier identifier) {
-            if (currentNode != null) {
-                // check if identifier points to some augmentation of currentNode
-                if (currentNode instanceof AugmentationTarget) {
-                    Optional<AugmentationSchema> augmentationSchema =
-                        ((AugmentationTarget) currentNode).getAvailableAugmentations().stream()
-                            .filter(aug -> identifier.equals(new YangInstanceIdentifier.AugmentationIdentifier(
-                                aug.getChildNodes().stream()
-                                    .map(SchemaNode::getQName)
-                                    .collect(Collectors.toSet()))))
-                            .findFirst();
-                    if (augmentationSchema.isPresent()) {
-                        return augmentationSchema.get();
-                    }
-                }
-
-                // continue search:
-                Collection<DataSchemaNode> childNodes = Collections.emptyList();
-                if (currentNode instanceof DataNodeContainer) {
-                    childNodes = ((DataNodeContainer) currentNode).getChildNodes();
-                } else if (currentNode instanceof ChoiceSchemaNode) {
-                    childNodes = ((ChoiceSchemaNode) currentNode).getCases().stream()
-                        .flatMap(cas -> cas.getChildNodes().stream()).collect(Collectors.toList());
-                }
-                return childNodes.stream().map(n -> findAugmentation(n, identifier)).filter(n -> n != null).findFirst()
-                    .orElse(null);
-            } else {
-                return null;
+        private Boolean isCurrentModified(@Nonnull final Modification modification) {
+            // First check if it's an empty presence node
+            final boolean emptyPresenceNode = isEmptyPresenceNode(modification);
+
+            // Check if there are any modified leaves and if so, consider current node as modified
+            final Boolean directLeavesModified = emptyPresenceNode
+                    || modification.streamChildren()
+                    // Checking leaf or leaf-lists children for direct modification, which means that leafs of leaf lists
+                    // trigger a modification on parent node
+                    .filter(child -> child.is(LeafSchemaNode.class) || child.is(LeafListSchemaNode.class))
+                    // For some reason, we get modifications on unmodified list keys
+                    // and that messes up our modifications collection here, so we need to skip
+                    .filter(Modification::isBeforeAndAfterDifferent)
+                    .filter(child -> VALID_MODIFICATIONS.contains(child.getModificationType()))
+                    .findFirst()
+                    .isPresent();
+
+            // Also as fallback check choices (choices do not exist in BA world and if anything within a choice was modified,
+            // consider its parent as being modified)
+            final boolean modified = directLeavesModified
+                    || modification.streamChildren()
+                    .filter(child -> child.is(ChoiceSchemaNode.class))
+                    // Recursively check each choice if there was any change to it
+                    .filter(this::isCurrentModified)
+                    .findFirst()
+                    .isPresent();
+
+            if (modified) {
+                LOG.debug("Modification detected as {} at {}",
+                        modification.getModificationType(), modification.getId());
             }
-        }
 
-        Stream<Modification> streamChildren() {
-            return dataCandidate.getChildNodes().stream()
-                .map(child -> {
-                    final YangInstanceIdentifier childId = id.node(child.getIdentifier());
-                    final Object schemaChild = schemaChild(schemaNode, child.getIdentifier());
-
-                    // An augment cannot change other augment, so we do not update parent node if we are streaming
-                    // children of AugmentationSchema (otherwise we would fail to find schema for nested augmentations):
-                    if (updateParentNode) {
-                        if (schemaNode instanceof AugmentationSchema) {
-                            // child nodes would not have nested augmentations, so we stop moving parentNode:
-                            return new Modification(childId, child, parentNode, schemaChild, false);
-                        } else {
-                            // update parent node:
-                            return new Modification(childId, child, schemaNode, schemaChild, true);
-                        }
-                    }
-                    return new Modification(childId, child, parentNode, schemaChild, updateParentNode);
-                });
+            return modified;
         }
 
         /**
-         * Find next schema node in hierarchy.
+         * Check if new data are empty but still to be considered as a modification, meaning it's presence has a meaning
+         * e.g. containers with presence statement.
          */
-        private Object schemaChild(final Object schemaNode, final YangInstanceIdentifier.PathArgument identifier) {
-            Object found = null;
-
-            if (identifier instanceof YangInstanceIdentifier.AugmentationIdentifier) {
-                if (schemaNode instanceof AugmentationTarget) {
-                    // Find matching augmentation
-                    found = ((AugmentationTarget) schemaNode).getAvailableAugmentations().stream()
-                        .filter(aug -> identifier.equals(new YangInstanceIdentifier.AugmentationIdentifier(
-                            aug.getChildNodes().stream()
-                                .map(SchemaNode::getQName)
-                                .collect(Collectors.toSet()))))
-                        .findFirst()
-                        .orElse(null);
-
-                    if (found == null) {
-                        // An augment cannot change other augment, but all augments only change their targets (data nodes).
-                        //
-                        // As a consequence, if nested augmentations are present,
-                        // AugmentationSchema might reference child schema node instances that do not include changes
-                        // from nested augments.
-                        //
-                        // But schemaNode, as mentioned earlier, contains all the changes introduced by augments.
-                        //
-                        // On the other hand, in case of augments which introduce leaves,
-                        // we need to address AugmentationSchema node directly so we can't simply do
-                        // found = schemaNode;
-                        //
-                        found =
-                            findAugmentation(parentNode, (YangInstanceIdentifier.AugmentationIdentifier) identifier);
-                    }
-                }
-            } else if (schemaNode instanceof DataNodeContainer) {
-                // Special handling for list aggregator nodes. If we are at list aggregator node e.g. MapNode and
-                // we are searching for schema for a list entry e.g. MapEntryNode just return the same schema
-                if (schemaNode instanceof ListSchemaNode &&
-                    ((SchemaNode) schemaNode).getQName().equals(identifier.getNodeType())) {
-                    found = schemaNode;
-                } else {
-                    found = ((DataNodeContainer) schemaNode).getDataChildByName(identifier.getNodeType());
-                }
-            } else if (schemaNode instanceof ChoiceSchemaNode) {
-                // For choices, iterate through all the cases
-                final Optional<DataSchemaNode> maybeChild = ((ChoiceSchemaNode) schemaNode).getCases().stream()
-                        .flatMap(cas -> cas.getChildNodes().stream())
-                        .filter(child -> child.getQName().equals(identifier.getNodeType()))
-                        .findFirst();
-                if (maybeChild.isPresent()) {
-                    found = maybeChild.get();
-                }
-                // Special handling for leaf-list nodes. Basically the same as is for list mixin nodes
-            } else if (schemaNode instanceof LeafListSchemaNode &&
-                ((SchemaNode) schemaNode).getQName().equals(identifier.getNodeType())) {
-                found = schemaNode;
-            }
+        private static boolean isEmptyPresenceNode(@Nonnull final Modification modification) {
+            return modification.is(ContainerSchemaNode.class)
+                    && ((ContainerSchemaNode) modification.getSchemaNode()).isPresenceContainer()
+                    && modification.getChildNodes().isEmpty()
+                    && VALID_MODIFICATIONS.contains(modification.getModificationType());
+        }
 
-            return checkNotNull(found, "Unable to find child node in: %s identifiable by: %s", schemaNode, identifier);
+        /**
+         * Checks whether node is non-presence container but with changed nested data
+         */
+        private static boolean isNonPresenceOverride(@Nonnull final Modification modification) {
+            return modification.is(ContainerSchemaNode.class)// must be container
+                    && !((ContainerSchemaNode) modification.getSchemaNode()).isPresenceContainer()
+                    // must be non-presence
+                    && modification.getChildNodes().isEmpty() // is override to empty
+                    && modification.isBeforeAndAfterDifferent()// to detect that it is modification
+                    &&
+                    modification.getDataBefore().isPresent(); // to ensure the case when overriding previously existing
         }
 
-        @Override
-        public String toString() {
-            return "Modification{" +
-                    "id=" + id +
-                    '}';
+        /**
+         * Process all non-leaf child nodes recursively, creating aggregated {@link ModificationDiff}.
+         */
+        private ModificationDiff recursivelyChildrenFromCandidate(@Nonnull final Modification modification) {
+            // recursively process child nodes for specific modifications
+            return modification.streamChildren()
+                    .filter(child -> !child.is(LeafSchemaNode.class))
+                    .map(this::recursivelyFromCandidate)
+                    .reduce(ModificationDiff::merge)
+                    .orElse(EMPTY_DIFF);
         }
     }
+
 }
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeRewriteDeleteRegistry.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeRewriteDeleteRegistry.java
new file mode 100644 (file)
index 0000000..c7832ac
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Basic logic for creating {@link NormalizedNodeUpdate} for changed that delete by rewrite
+ */
+final class NormalizedNodeRewriteDeleteRegistry implements RewriteDeleteProducer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NormalizedNodeRewriteDeleteRegistry.class);
+
+    private final RewriteDeleteProducer leafDeleteProducer;
+    private final RewriteDeleteProducer leafListDeleteProducer;
+    private final RewriteDeleteProducer listDeleteProducer;
+    private final RewriteDeleteProducer containerDeleteProducer;
+    private final RewriteDeleteProducer choiceDeleteProducer;
+    private final RewriteDeleteProducer caseDeleteProducer;
+    private final RewriteDeleteProducer augmentationDeleteProducer;
+
+    NormalizedNodeRewriteDeleteRegistry(@Nonnull final SchemaContext ctx) {
+        leafDeleteProducer = new LeafRewriteDeleteProducer();
+        leafListDeleteProducer = new LeafListRewriteDeleteProducer();
+        listDeleteProducer = new ListRewriteDeleteProducer();
+        containerDeleteProducer = new ContainerRewriteDeleteProducer(this, ctx);
+        choiceDeleteProducer = new ChoiceRewriteDeleteProducer(this);
+        caseDeleteProducer = new CaseRewriteDeleteProducer(this);
+        augmentationDeleteProducer = new AugmentationRewriteDeleteProducer(this);
+    }
+
+    @Override
+    public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+                                                              @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+        if (entry.getValue() instanceof LeafNode) {
+            LOG.debug("Processing leaf node {}", topLevelIdentifier);
+            return leafDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+        }
+
+        if (entry.getValue() instanceof LeafSetNode) {
+            LOG.debug("Processing leaf-list node {}", topLevelIdentifier);
+            return leafListDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+        }
+
+        if (entry.getValue() instanceof MapNode) {
+            LOG.debug("Processing list {}", topLevelIdentifier);
+            return listDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+        }
+
+        if (entry.getValue() instanceof ContainerNode) {
+            LOG.debug("Processing container {}", topLevelIdentifier);
+            return containerDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+        }
+
+        if (entry.getValue() instanceof ChoiceNode) {
+            LOG.debug("Processing choice {}", topLevelIdentifier);
+            return choiceDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+        }
+
+        if (entry.getValue() instanceof ChoiceCaseNode) {
+            LOG.debug("Processing case {}", topLevelIdentifier);
+            return caseDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+        }
+
+        if (entry.getValue() instanceof AugmentationNode) {
+            LOG.debug("Processing augmentation {}", topLevelIdentifier);
+            return augmentationDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+        }
+
+        return Collections.emptyList();
+    }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeUpdate.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeUpdate.java
new file mode 100644 (file)
index 0000000..bfc8a1e
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+/**
+ * Update to a normalized node identifiable by its {@link YangInstanceIdentifier}.
+ */
+final class NormalizedNodeUpdate {
+
+    @Nonnull
+    private final YangInstanceIdentifier id;
+    @Nullable
+    private final NormalizedNode<?, ?> dataBefore;
+    @Nullable
+    private final NormalizedNode<?, ?> dataAfter;
+
+    NormalizedNodeUpdate(@Nonnull final YangInstanceIdentifier id,
+                         @Nullable final NormalizedNode<?, ?> dataBefore,
+                         @Nullable final NormalizedNode<?, ?> dataAfter) {
+        this.id = checkNotNull(id);
+        this.dataAfter = dataAfter;
+        this.dataBefore = dataBefore;
+    }
+
+    @Nullable
+    public NormalizedNode<?, ?> getDataBefore() {
+        return dataBefore;
+    }
+
+    @Nullable
+    public NormalizedNode<?, ?> getDataAfter() {
+        return dataAfter;
+    }
+
+    @Nonnull
+    public YangInstanceIdentifier getId() {
+        return id;
+    }
+
+    static NormalizedNodeUpdate create(@Nonnull final Modification modification) {
+        final com.google.common.base.Optional<NormalizedNode<?, ?>> beforeData =
+                modification.getDataBefore();
+        final com.google.common.base.Optional<NormalizedNode<?, ?>> afterData =
+                modification.getDataAfter();
+        checkArgument(beforeData.isPresent() || afterData.isPresent(),
+                "Both before and after data are null for %s", modification.getId());
+        return NormalizedNodeUpdate.create(modification.getId(), beforeData.orNull(), afterData.orNull());
+    }
+
+    static NormalizedNodeUpdate create(@Nonnull final YangInstanceIdentifier id,
+                                       @Nullable final NormalizedNode<?, ?> dataBefore,
+                                       @Nullable final NormalizedNode<?, ?> dataAfter) {
+        return new NormalizedNodeUpdate(id, dataBefore, dataAfter);
+    }
+
+    @Override
+    public boolean equals(final Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other == null || getClass() != other.getClass()) {
+            return false;
+        }
+
+        final NormalizedNodeUpdate that = (NormalizedNodeUpdate) other;
+
+        return id.equals(that.id);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "NormalizedNodeUpdate{" + "id=" + id
+                + ", dataBefore=" + dataBefore
+                + ", dataAfter=" + dataAfter
+                + '}';
+    }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/RewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/RewriteDeleteProducer.java
new file mode 100644 (file)
index 0000000..e2c5f4a
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+
+interface RewriteDeleteProducer {
+
+    Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+                                                       @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry);
+}
\ No newline at end of file
index 26a936f..432833b 100644 (file)
@@ -64,7 +64,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
 
-public class ModifiableDataTreeDelegatorTest {
+public class ModifiableDataTreeDelegatorTest extends ModificationBaseTest {
 
     @Mock
     private WriterRegistry writer;
@@ -90,7 +90,7 @@ public class ModifiableDataTreeDelegatorTest {
     @Before
     public void setUp() throws Exception {
         initMocks(this);
-        dataTree = ModificationDiffTest.getDataTree();
+        dataTree = getDataTree();
         when(contextBroker.newReadWriteTransaction()).thenReturn(tx);
         when(tx.submit()).thenReturn(Futures.immediateCheckedFuture(null));
 
@@ -98,39 +98,39 @@ public class ModifiableDataTreeDelegatorTest {
         final Map.Entry<InstanceIdentifier<?>, DataObject> parsed = new AbstractMap.SimpleEntry<>(DEFAULT_ID, DEFAULT_DATA_OBJECT);
         when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), any(NormalizedNode.class))).thenReturn(parsed);
 
-        configDataTree = new ModifiableDataTreeDelegator(serializer, dataTree, ModificationDiffTest.getSchemaCtx(), writer, contextBroker);
+        configDataTree = new ModifiableDataTreeDelegator(serializer, dataTree, getSchemaCtx(), writer, contextBroker);
     }
 
     @Test
     public void testRead() throws Exception {
-        final ContainerNode topContainer = ModificationDiffTest.getTopContainer("topContainer");
-        ModificationDiffTest.addNodeToTree(dataTree, topContainer, ModificationDiffTest.TOP_CONTAINER_ID);
+        final ContainerNode topContainer = getTopContainer("topContainer");
+        addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID);
         final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read =
-                configDataTree.read(ModificationDiffTest.TOP_CONTAINER_ID);
+                configDataTree.read(TOP_CONTAINER_ID);
         final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read2 =
-                configDataTree.newModification().read(ModificationDiffTest.TOP_CONTAINER_ID);
+                configDataTree.newModification().read(TOP_CONTAINER_ID);
         final Optional<NormalizedNode<?, ?>> normalizedNodeOptional = read.get();
         final Optional<NormalizedNode<?, ?>> normalizedNodeOptional2 = read2.get();
 
         assertEquals(normalizedNodeOptional, normalizedNodeOptional2);
         assertTrue(normalizedNodeOptional.isPresent());
         assertEquals(topContainer, normalizedNodeOptional.get());
-        assertEquals(dataTree.takeSnapshot().readNode(ModificationDiffTest.TOP_CONTAINER_ID), normalizedNodeOptional);
+        assertEquals(dataTree.takeSnapshot().readNode(TOP_CONTAINER_ID), normalizedNodeOptional);
     }
 
     @Test
     public void testCommitSuccessful() throws Exception {
-        final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
+        final MapNode nestedList = getNestedList("listEntry", "listValue");
 
         final DataModification dataModification = configDataTree.newModification();
-        dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+        dataModification.write(NESTED_LIST_ID, nestedList);
         dataModification.validate();
         dataModification.commit();
 
         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> map = HashMultimap.create();
         map.put(DEFAULT_ID, DataObjectUpdate.create(DEFAULT_ID, DEFAULT_DATA_OBJECT, DEFAULT_DATA_OBJECT));
         verify(writer).update(eq(new WriterRegistry.DataObjectUpdates(map, ImmutableMultimap.of())), any(WriteContext.class));
-        assertEquals(nestedList, dataTree.takeSnapshot().readNode(ModificationDiffTest.NESTED_LIST_ID).get());
+        assertEquals(nestedList, dataTree.takeSnapshot().readNode(NESTED_LIST_ID).get());
     }
 
     private static DataObject mockDataObject(final String name, final Class<? extends DataObject> classToMock) {
@@ -141,7 +141,7 @@ public class ModifiableDataTreeDelegatorTest {
 
     @Test
     public void testCommitUndoSuccessful() throws Exception {
-        final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
+        final MapNode nestedList = getNestedList("listEntry", "listValue");
 
         // Fail on update:
         final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
@@ -152,7 +152,7 @@ public class ModifiableDataTreeDelegatorTest {
         try {
             // Run the test
             final DataModification dataModification = configDataTree.newModification();
-            dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+            dataModification.write(NESTED_LIST_ID, nestedList);
             dataModification.validate();
             dataModification.commit();
             fail("WriterRegistry.RevertSuccessException was expected");
@@ -165,7 +165,7 @@ public class ModifiableDataTreeDelegatorTest {
 
     @Test
     public void testCommitUndoFailed() throws Exception {
-        final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
+        final MapNode nestedList = getNestedList("listEntry", "listValue");
 
         // Fail on update:
         final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
@@ -182,7 +182,7 @@ public class ModifiableDataTreeDelegatorTest {
         try {
             // Run the test
             final DataModification dataModification = configDataTree.newModification();
-            dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+            dataModification.write(NESTED_LIST_ID, nestedList);
             dataModification.validate();
             dataModification.commit();
             fail("WriterRegistry.Reverter.RevertFailedException was expected");
@@ -201,14 +201,14 @@ public class ModifiableDataTreeDelegatorTest {
     public void testToBindingAware() throws Exception {
         when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(null))).thenReturn(null);
 
-        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biNodes = new HashMap<>();
+        final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes = new HashMap<>();
         // delete
         final QName nn1 = QName.create("namespace", "nn1");
         final YangInstanceIdentifier yid1 = mockYid(nn1);
         final InstanceIdentifier iid1 = mockIid(yid1, DataObject1.class);
         final NormalizedNode nn1B = mockNormalizedNode(nn1);
         final DataObject1 do1B = mockDataObject(yid1, iid1, nn1B, DataObject1.class);
-        biNodes.put(yid1, ModificationDiff.NormalizedNodeUpdate.create(yid1, nn1B, null));
+        biNodes.put(yid1, NormalizedNodeUpdate.create(yid1, nn1B, null));
 
         // create
         final QName nn2 = QName.create("namespace", "nn1");
@@ -216,7 +216,7 @@ public class ModifiableDataTreeDelegatorTest {
         final InstanceIdentifier iid2 = mockIid(yid2, DataObject2.class);;
         final NormalizedNode nn2A = mockNormalizedNode(nn2);
         final DataObject2 do2A = mockDataObject(yid2, iid2, nn2A, DataObject2.class);
-        biNodes.put(yid2, ModificationDiff.NormalizedNodeUpdate.create(yid2, null, nn2A));
+        biNodes.put(yid2, NormalizedNodeUpdate.create(yid2, null, nn2A));
 
         // update
         final QName nn3 = QName.create("namespace", "nn1");
@@ -226,7 +226,7 @@ public class ModifiableDataTreeDelegatorTest {
         final DataObject3 do3B = mockDataObject(yid3, iid3, nn3B, DataObject3.class);
         final NormalizedNode nn3A = mockNormalizedNode(nn3);
         final DataObject3 do3A = mockDataObject(yid3, iid3, nn3A, DataObject3.class);;
-        biNodes.put(yid3, ModificationDiff.NormalizedNodeUpdate.create(yid3, nn3B, nn3A));
+        biNodes.put(yid3, NormalizedNodeUpdate.create(yid3, nn3B, nn3A));
 
         final WriterRegistry.DataObjectUpdates dataObjectUpdates =
                 ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationBaseTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationBaseTest.java
new file mode 100644 (file)
index 0000000..c7d78fa
--- /dev/null
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
+
+abstract class ModificationBaseTest extends ModificationMetadata {
+
+    void addNodeToTree(final DataTree dataTree, final NormalizedNode<?, ?> node,
+                       final YangInstanceIdentifier id)
+            throws DataValidationFailedException {
+        DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+        DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+        dataTreeModification.write(id, node);
+        dataTreeModification.ready();
+        dataTree.validate(dataTreeModification);
+        DataTreeCandidate prepare = dataTree.prepare(dataTreeModification);
+        dataTree.commit(prepare);
+    }
+
+    protected TipProducingDataTree getDataTree() throws ReactorException {
+        final TipProducingDataTree dataTree = InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION);
+        dataTree.setSchemaContext(getSchemaCtx());
+        return dataTree;
+    }
+
+    ContainerNode getTopContainer(final String stringValue) {
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(ImmutableNodes.leafNode(STRING_LEAF_QNAME, stringValue))
+                .build();
+    }
+
+    ContainerNode getTopContainerWithLeafList(final String... stringValue) {
+        final ListNodeBuilder<String, LeafSetEntryNode<String>> leafSetBuilder = Builders.leafSetBuilder();
+        for (final String value : stringValue) {
+            leafSetBuilder.withChild(Builders.<String>leafSetEntryBuilder()
+                    .withNodeIdentifier(new YangInstanceIdentifier.NodeWithValue<>(NESTED_LEAF_LIST_QNAME, value))
+                    .withValue(value)
+                    .build());
+        }
+
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(Builders.containerBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(FOR_LEAF_LIST_QNAME))
+                        .withChild(leafSetBuilder
+                                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LEAF_LIST_QNAME))
+                                .build())
+                        .build())
+                .build();
+    }
+
+    MapNode getNestedList(final String listItemName, final String text) {
+        return Builders.mapBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME))
+                .withChild(
+                        Builders.mapEntryBuilder()
+                                .withNodeIdentifier(
+                                        new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME,
+                                                NAME_LEAF_QNAME, listItemName))
+                                .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
+                                .withChild(ImmutableNodes.leafNode(TEXT_LEAF_QNAME, text))
+                                .build()
+                )
+                .build();
+    }
+
+    MapNode getDeepList(final String listItemName) {
+        return Builders.mapBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME))
+                .withChild(
+                        Builders.mapEntryBuilder()
+                                .withNodeIdentifier(
+                                        new YangInstanceIdentifier.NodeIdentifierWithPredicates(DEEP_LIST_QNAME,
+                                                NAME_LEAF_QNAME, listItemName))
+                                .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
+                                .build()
+                )
+                .build();
+    }
+
+    SchemaContext getSchemaCtx() throws ReactorException {
+        final CrossSourceStatementReactor.BuildAction buildAction = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+        buildAction.addSource(
+                new YangStatementSourceImpl(ModificationDiffTest.class.getResourceAsStream("/test-diff.yang")));
+        return buildAction.buildEffective();
+    }
+
+    DataTreeModification getModification(final TipProducingDataTree dataTree) {
+        final DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+        return dataTreeSnapshot.newModification();
+    }
+
+
+    DataTreeCandidateTip prepareModification(final TipProducingDataTree dataTree,
+                                             final DataTreeModification dataTreeModification)
+            throws DataValidationFailedException {
+        dataTreeModification.ready();
+        dataTree.validate(dataTreeModification);
+        return dataTree.prepare(dataTreeModification);
+    }
+
+    ModificationDiff getModificationDiff(final DataTreeCandidateTip prepare) throws ReactorException {
+        return new ModificationDiff.ModificationDiffBuilder()
+                .setCtx(getSchemaCtx())
+                .build(prepare.getRootNode());
+    }
+
+    NormalizedNodeUpdate getNormalizedNodeUpdateForAfterType(
+            final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates,
+            final Class<? extends NormalizedNode<?, ?>> containerNodeClass) {
+        return updates.values().stream()
+                .filter(update -> containerNodeClass.isAssignableFrom(update.getDataAfter().getClass()))
+                .findFirst().get();
+    }
+
+    NormalizedNodeUpdate getNormalizedNodeUpdateForBeforeType(
+            final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates,
+            final Class<? extends NormalizedNode<?, ?>> containerNodeClass) {
+        return updates.values().stream()
+                .filter(update -> containerNodeClass.isAssignableFrom(update.getDataBefore().getClass()))
+                .findFirst().get();
+    }
+
+    void assertUpdate(final NormalizedNodeUpdate update,
+                      final YangInstanceIdentifier idExpected,
+                      final NormalizedNode<?, ?> beforeExpected,
+                      final NormalizedNode<?, ?> afterExpected) {
+        assertThat(update.getId(), is(idExpected));
+        assertThat(update.getDataBefore(), is(beforeExpected));
+        assertThat(update.getDataAfter(), is(afterExpected));
+    }
+
+
+    ContainerNode getNestedContainerWithLeafList(final String... stringValue) {
+        final ListNodeBuilder<String, LeafSetEntryNode<String>> leafSetBuilder = Builders.leafSetBuilder();
+        for (final String value : stringValue) {
+            leafSetBuilder.withChild(Builders.<String>leafSetEntryBuilder()
+                    .withNodeIdentifier(new YangInstanceIdentifier.NodeWithValue<>(NESTED_CONTAINER_LEAF_LIST, value))
+                    .withValue(value)
+                    .build());
+        }
+
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                .withChild(leafSetBuilder
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_LEAF_LIST))
+                        .build())
+                .build();
+    }
+
+    ContainerNode getNestedContainerWithChoice(final String caseLeafValue) {
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                .withChild(Builders.choiceBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CHOICE))
+                        .withChild(Builders.leafBuilder()
+                                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(UNDER_NESTED_CASE))
+                                .withValue(caseLeafValue)
+                                .build()).build())
+                .build();
+    }
+
+    MapEntryNode getNestedListEntry(final String listItemName, final String text) {
+        return Builders.mapEntryBuilder()
+                .withNodeIdentifier(
+                        new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME,
+                                NAME_LEAF_QNAME, listItemName))
+                .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
+                .withChild(ImmutableNodes.leafNode(TEXT_LEAF_QNAME, text))
+                .build();
+    }
+
+    MapEntryNode getNestedListInContainerEntry(final String listItemName) {
+        return Builders.mapEntryBuilder()
+                .withNodeIdentifier(
+                        new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_IN_CONTAINER_QNAME,
+                                IN_CONTAINER_NAME_LEAF_QNAME, listItemName))
+                .withChild(ImmutableNodes.leafNode(IN_CONTAINER_NAME_LEAF_QNAME, listItemName))
+                .build();
+    }
+
+    MapNode getNestedList(MapEntryNode... entries) {
+        return Builders.mapBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME))
+                .withValue(Arrays.asList(entries))
+                .build();
+    }
+
+    MapNode getNestedListInContainer(MapEntryNode... entries) {
+        return Builders.mapBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_IN_CONTAINER_QNAME))
+                .withValue(Arrays.asList(entries))
+                .build();
+    }
+
+    ContainerNode getNestedContWithLeafUnderAug(String value) {
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                .withChild(Builders.leafBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_LEAF))
+                        .withValue(value).build()).build();
+    }
+
+    ContainerNode getNestedContWithListUnderAug(String value) {
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                .withChild(Builders.mapBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_LIST))
+                        .withChild(Builders.mapEntryBuilder()
+                                .withNodeIdentifier(
+                                        new YangInstanceIdentifier.NodeIdentifierWithPredicates(AUG_LIST, AUG_LIST_KEY,
+                                                value))
+                                .build())
+                        .build()).build();
+    }
+
+    ContainerNode getNestedContWithContUnderAug(String value) {
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                .withChild(Builders.containerBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER))
+                        .withChild(Builders.leafBuilder()
+                                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER_LEAF))
+                                .withValue(value)
+                                .build())
+                        .build()).build();
+    }
+
+    ContainerNode getNestedContWithLeafListUnderAug(String value) {
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                .withChild(Builders.leafSetBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_LEAFLIST))
+                        .withChildValue(value)
+                        .build()).build();
+    }
+
+    ContainerNode getNestedContWithContainerUnderNestedAug(String value) {
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                .withChild(Builders.containerBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER))
+                        .withChild(Builders.containerBuilder()
+                                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_AUG_CONTAINER))
+                                .withChild(Builders.leafBuilder()
+                                        .withNodeIdentifier(
+                                                new YangInstanceIdentifier.NodeIdentifier(NESTED_AUG_CONTAINER_LEAF))
+                                        .withValue(value)
+                                        .build()).build()).build()).build();
+    }
+
+    ContainerNode getNestedContWithLeafListUnderNestedAug(String value) {
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                .withChild(Builders.containerBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER))
+                        .withChild(Builders.leafSetBuilder()
+                                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_AUG_LEAF_LIST))
+                                .withChildValue(value)
+                                .build()).build()).build();
+    }
+
+    ContainerNode getNestedContWithLeafUnderNestedAug(String value) {
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                .withChild(Builders.containerBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER))
+                        .withChild(Builders.leafBuilder()
+                                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_AUG_LEAF))
+                                .withValue(value)
+                                .build()).build()).build();
+    }
+
+    ContainerNode getNestedContWithListUnderNestedAug(String value) {
+        return Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                .withChild(Builders.containerBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER))
+                        .withChild(Builders.mapBuilder()
+                                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_AUG_LIST))
+                                .withChild(Builders.mapEntryBuilder()
+                                        .withNodeIdentifier(
+                                                new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_AUG_LIST,
+                                                        NESTED_AUG_LIST_KEY, value)).build())
+                                .build()).build()).build();
+    }
+
+    TipProducingDataTree prepareStateBeforeWithTopContainer(final NormalizedNode<?, ?> topContainerData)
+            throws ReactorException, DataValidationFailedException {
+        final TipProducingDataTree dataTree = getDataTree();
+        final DataTreeModification dataTreeModificationOriginal = getModification(dataTree);
+        // non presence, but with valid child list
+        dataTreeModificationOriginal.write(TOP_CONTAINER_ID, topContainerData);
+        final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModificationOriginal);
+        dataTree.commit(prepare);
+        return dataTree;
+    }
+
+    DataTreeCandidateTip prepareStateAfterEmpty(final TipProducingDataTree dataTree)
+            throws DataValidationFailedException {
+        final NormalizedNode<?, ?> topContainerModified = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .build();
+        final DataTreeModification dataTreeModificationModified = getModification(dataTree);
+        dataTreeModificationModified.write(TOP_CONTAINER_ID, topContainerModified);
+        return prepareModification(dataTree, dataTreeModificationModified);
+    }
+
+    void assertCollectionContainsOnlyDeletes(final ModificationDiff modificationDiff) {
+        assertTrue(modificationDiff.getUpdates().entrySet().stream().map(Map.Entry::getValue)
+                .map(NormalizedNodeUpdate::getDataAfter)
+                .filter(Objects::nonNull).collect(Collectors.toList()).isEmpty());
+    }
+
+    void assertNodeModificationPresent(final ModificationDiff modificationDiff, final Set<QName> expected) {
+        assertTrue(modificationDiff.getUpdates().values().stream()
+                .allMatch(update -> expected.contains(update.getId().getLastPathArgument().getNodeType())));
+    }
+}
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffAugRewriteDeleteTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffAugRewriteDeleteTest.java
new file mode 100644 (file)
index 0000000..c2b9598
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ModificationDiffAugRewriteDeleteTest extends ModificationBaseTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ModificationDiffAugRewriteDeleteTest.class);
+
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerAugWithLeaf()
+            throws ReactorException, DataValidationFailedException {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedContWithLeafUnderAug("val"))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(AUG_LEAF));
+    }
+
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerAugWithList()
+            throws ReactorException, DataValidationFailedException {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedContWithListUnderAug("val"))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(AUG_LIST));
+    }
+
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerAugWithNonPresenceContainer()
+            throws ReactorException, DataValidationFailedException {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedContWithContUnderAug("val"))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(AUG_CONTAINER_LEAF));
+    }
+
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerAugWithLeafList()
+            throws ReactorException, DataValidationFailedException {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedContWithLeafListUnderAug("val"))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(AUG_LEAFLIST));
+    }
+}
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffNestedAugRewriteDeleteTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffNestedAugRewriteDeleteTest.java
new file mode 100644 (file)
index 0000000..9c92b4f
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ModificationDiffNestedAugRewriteDeleteTest extends ModificationBaseTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ModificationDiffNestedAugRewriteDeleteTest.class);
+
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerNestedAugWithContainer()
+            throws DataValidationFailedException, ReactorException {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedContWithContainerUnderNestedAug("val"))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_AUG_CONTAINER_LEAF));
+    }
+
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerNestedAugWithLeafList()
+            throws DataValidationFailedException, ReactorException {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedContWithLeafListUnderNestedAug("val"))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_AUG_LEAF_LIST));
+    }
+
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerNestedAugWithList()
+            throws DataValidationFailedException, ReactorException {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedContWithListUnderNestedAug("val"))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_AUG_LIST));
+    }
+
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerNestedAugWithLeaf()
+            throws DataValidationFailedException, ReactorException {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedContWithLeafUnderNestedAug("val"))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_AUG_LEAF));
+    }
+}
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffRewriteDeleteTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffRewriteDeleteTest.java
new file mode 100644 (file)
index 0000000..a1ede96
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ModificationDiffRewriteDeleteTest extends ModificationBaseTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ModificationDiffRewriteDeleteTest.class);
+
+    /**
+     * Covers case when non-presence container was already filled with some data,
+     * and modification removes data by overriding by empty list
+     */
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerPreviousDataOverrideByEmpty() throws Exception {
+        final MapEntryNode alreadyPresent = getNestedListEntry("value", "txt");
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedList(alreadyPresent))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_LIST_QNAME));
+    }
+
+    /**
+     * Covers case when non-presence container was already filled with some data,
+     * and modification removes data by overriding by empty list. This case tests
+     * when there are multiple nested non-presence containers
+     */
+    @Test
+    public void testWriteNonPresenceMultipleNonEmptyContainerPreviousDataOverrideByEmpty() throws Exception {
+        final MapEntryNode alreadyPresent = getNestedListInContainerEntry("key");
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                // another non-presence container with list entry
+                .withChild(Builders.containerBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                        .withChild(getNestedListInContainer(alreadyPresent))
+                        .build())
+                // direct list entry
+                .withChild(getNestedList(alreadyPresent))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect two new entries
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(2));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff,
+                ImmutableSet.of(NESTED_LIST_IN_CONTAINER_QNAME, NESTED_LIST_QNAME));
+    }
+
+    /**
+     * Covers case when non-presence container was already filled with some data,
+     * and modification removes data by overriding. Tests case with leaf
+     */
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerLeaf() throws Exception {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                // another non-presence container with leaf
+                .withChild(Builders.containerBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+                        .withChild(Builders.leafBuilder()
+                                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_VAL))
+                                .withValue("val").build())
+                        .build())
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_CONTAINER_VAL));
+    }
+
+    /**
+     * Covers case when non-presence container was already filled with some data,
+     * and modification removes data by overriding. Tests case with leaf-list
+     */
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerLeafList() throws Exception {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                // another non-presence container with leaf
+                .withChild(getNestedContainerWithLeafList())
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_CONTAINER_LEAF_LIST));
+    }
+
+    /**
+     * Covers case when non-presence container was already filled with some data,
+     * and modification removes data by overriding. Tests case with choice/case
+     */
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerWithChoice() throws Exception {
+        final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                // another non-presence container with leaf
+                .withChild(getNestedContainerWithChoice("val"))
+                .build());
+        // now we have state with some data
+
+        // just empty non presence container
+        final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        // is delete
+        assertCollectionContainsOnlyDeletes(modificationDiff);
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(UNDER_NESTED_CASE));
+    }
+}
index 664b573..cd980dd 100644 (file)
@@ -23,59 +23,26 @@ import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
 import java.util.Map;
 import org.junit.Test;
-import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
-import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
-import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
-import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
-import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-public class ModificationDiffTest {
+public class ModificationDiffTest extends ModificationBaseTest {
 
-    static final QName TOP_CONTAINER_QNAME =
-            QName.create("urn:opendaylight:params:xml:ns:yang:test:diff", "2015-01-05", "top-container");
-    static final QName STRING_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "string");
-    static final QName NAME_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "name");
-    static final QName TEXT_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "text");
-    static final QName NESTED_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-list");
-    static final QName DEEP_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "deep-list");
-    static final QName EMPTY_QNAME = QName.create(TOP_CONTAINER_QNAME, "empty");
-    static final QName IN_EMPTY_QNAME = QName.create(TOP_CONTAINER_QNAME, "in-empty");
-
-    static final QName FOR_LEAF_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "for-leaf-list");
-    static final QName NESTED_LEAF_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-leaf-list");
-
-
-    static final QName WITH_CHOICE_CONTAINER_QNAME =
-            QName.create("urn:opendaylight:params:xml:ns:yang:test:diff", "2015-01-05", "with-choice");
-    static final QName CHOICE_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "choice");
-    static final QName IN_CASE1_LEAF_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "in-case1");
-    static final QName IN_CASE2_LEAF_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "in-case2");
-
-    static final QName PRESENCE_CONTAINER_QNAME = QName.create(TOP_CONTAINER_QNAME, "presence");
-
-    static final YangInstanceIdentifier TOP_CONTAINER_ID = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME);
-    static final YangInstanceIdentifier NESTED_LIST_ID = TOP_CONTAINER_ID.node(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+    private static final Logger LOG = LoggerFactory.getLogger(ModificationDiffTest.class);
 
     @Test
     public void testInitialWrite() throws Exception {
@@ -150,17 +117,14 @@ public class ModificationDiffTest {
         dataTreeModification.write(WITH_CHOICE_CONTAINER_ID, containerWithChoice);
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+        final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
 
         assertThat(updates.size(), is(1));
         assertUpdate(getNormalizedNodeUpdateForAfterType(updates, ContainerNode.class),
                 WITH_CHOICE_CONTAINER_ID, null, containerWithChoice);
     }
 
-    private DataTreeModification getModification(final TipProducingDataTree dataTree) {
-        final DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
-        return dataTreeSnapshot.newModification();
-    }
+
 
     @Test
     public void testWriteNonPresenceEmptyContainer() throws Exception {
@@ -175,6 +139,65 @@ public class ModificationDiffTest {
         assertThat(modificationDiff.getUpdates().size(), is(0));
     }
 
+    /**
+     * Covers case when both non-presence and nested is not present
+     */
+    @Test
+    public void testWriteNonPresenceNonEmptyContainer() throws Exception {
+        final TipProducingDataTree dataTree = getDataTree();
+        final DataTreeModification dataTreeModification = getModification(dataTree);
+        // non presence ,but with valid child list
+        final NormalizedNode<?, ?> topContainer = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedList("value","txt"))
+                .build();
+
+        dataTreeModification.write(TOP_CONTAINER_ID, topContainer);
+        final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepare);
+
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_LIST_QNAME));
+    }
+
+    /**
+     * Covers case when non-presence container was already filled with some data,
+     * and modification just ads some other nested data
+     */
+    @Test
+    public void testWriteNonPresenceNonEmptyContainerPreviousData() throws Exception {
+        final TipProducingDataTree dataTree = getDataTree();
+        final DataTreeModification dataTreeModificationOriginal = getModification(dataTree);
+        // non presence, but with valid child list
+        final MapEntryNode alreadyPresent = getNestedListEntry("value", "txt");
+        final NormalizedNode<?, ?> topContainerOriginal = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedList(alreadyPresent))
+                .build();
+
+        dataTreeModificationOriginal.write(TOP_CONTAINER_ID, topContainerOriginal);
+        final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModificationOriginal);
+        dataTree.commit(prepare);
+        // now we have state with some data
+
+        final MapEntryNode newEntry = getNestedListEntry("value2", "txt2");
+        final NormalizedNode<?, ?> topContainerModified = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+                .withChild(getNestedList(alreadyPresent, newEntry))
+                .build();
+        final DataTreeModification dataTreeModificationModified = getModification(dataTree);
+        dataTreeModificationModified.write(TOP_CONTAINER_ID, topContainerModified);
+        final DataTreeCandidateTip prepareModified = prepareModification(dataTree, dataTreeModificationModified);
+
+        final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+        // should detect one new entry
+        LOG.debug(modificationDiff.toString());
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_LIST_QNAME));
+    }
+
+
     @Test
     public void testWriteNonPresenceEmptyNestedContainer() throws Exception {
         final TipProducingDataTree dataTree = getDataTree();
@@ -195,14 +218,6 @@ public class ModificationDiffTest {
         assertThat(modificationDiff.getUpdates().size(), is(1));
     }
 
-    private DataTreeCandidateTip prepareModification(final TipProducingDataTree dataTree,
-                                                     final DataTreeModification dataTreeModification)
-            throws DataValidationFailedException {
-        dataTreeModification.ready();
-        dataTree.validate(dataTreeModification);
-        return dataTree.prepare(dataTreeModification);
-    }
-
     @Test
     public void testUpdateWrite() throws Exception {
         final TipProducingDataTree dataTree = getDataTree();
@@ -214,16 +229,13 @@ public class ModificationDiffTest {
         dataTreeModification.write(TOP_CONTAINER_ID, topContainerAfter);
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+        final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
 
         assertThat(updates.size(), is(1));
         assertThat(updates.values().size(), is(1));
         assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, topContainerAfter);
     }
 
-    private ModificationDiff getModificationDiff(final DataTreeCandidateTip prepare) throws ReactorException {
-        return ModificationDiff.recursivelyFromCandidateRoot(prepare.getRootNode(), getSchemaCtx());
-    }
 
     @Test
     public void testUpdateMerge() throws Exception {
@@ -236,7 +248,7 @@ public class ModificationDiffTest {
         dataTreeModification.merge(TOP_CONTAINER_ID, topContainerAfter);
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+        final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
         assertThat(updates.size(), is(1));
         assertThat(updates.values().size(), is(1));
         assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, topContainerAfter);
@@ -252,7 +264,7 @@ public class ModificationDiffTest {
         dataTreeModification.delete(TOP_CONTAINER_ID);
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+        final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
         assertThat(updates.size(), is(1));
         assertThat(updates.values().size(), is(1));
         assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, null);
@@ -276,7 +288,7 @@ public class ModificationDiffTest {
         dataTree.validate(dataTreeModification);
         DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
 
-        Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+        Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
 
         assertThat(updates.size(), is(1));
         assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class),
@@ -301,14 +313,6 @@ public class ModificationDiffTest {
         assertThat(updates.size(), is(1 /*Actual list entry*/));
     }
 //
-    private void assertUpdate(final ModificationDiff.NormalizedNodeUpdate update,
-                              final YangInstanceIdentifier idExpected,
-                              final NormalizedNode<?, ?> beforeExpected,
-                              final NormalizedNode<?, ?> afterExpected) {
-        assertThat(update.getId(), is(idExpected));
-        assertThat(update.getDataBefore(), is(beforeExpected));
-        assertThat(update.getDataAfter(), is(afterExpected));
-    }
 
     @Test
     public void testWriteTopContainerAndInnerList() throws Exception {
@@ -332,7 +336,7 @@ public class ModificationDiffTest {
 
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+        final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
 
         assertThat(updates.size(), is(2));
         assertThat(updates.values().size(), is(2));
@@ -345,21 +349,7 @@ public class ModificationDiffTest {
                 listEntryId));
     }
 
-    private ModificationDiff.NormalizedNodeUpdate getNormalizedNodeUpdateForAfterType(
-            final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates,
-            final Class<? extends NormalizedNode<?, ?>> containerNodeClass) {
-        return updates.values().stream()
-                    .filter(update -> containerNodeClass.isAssignableFrom(update.getDataAfter().getClass()))
-                    .findFirst().get();
-    }
 
-    private ModificationDiff.NormalizedNodeUpdate getNormalizedNodeUpdateForBeforeType(
-            final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates,
-            final Class<? extends NormalizedNode<?, ?>> containerNodeClass) {
-        return updates.values().stream()
-                    .filter(update -> containerNodeClass.isAssignableFrom(update.getDataBefore().getClass()))
-                    .findFirst().get();
-    }
 
     @Test
     public void testWriteDeepList() throws Exception {
@@ -415,7 +405,7 @@ public class ModificationDiffTest {
         prepare = dataTree.prepare(dataTreeModification);
         dataTree.commit(prepare);
 
-        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+        final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
         assertThat(updates.size(), is(1));
         assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class), deepListEntryId, null, deepListEntry);
     }
@@ -450,88 +440,10 @@ public class ModificationDiffTest {
         dataTree.validate(dataTreeModification);
         prepare = dataTree.prepare(dataTreeModification);
 
-        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+        final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
         assertThat(updates.size(), is(1));
         assertUpdate(getNormalizedNodeUpdateForBeforeType(updates, MapEntryNode.class), listItemId, mapNode.getValue().iterator().next(), null);
     }
 
-    static void addNodeToTree(final DataTree dataTree, final NormalizedNode<?, ?> node,
-                                              final YangInstanceIdentifier id)
-            throws DataValidationFailedException {
-        DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
-        DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
-        dataTreeModification.write(id, node);
-        dataTreeModification.ready();
-        dataTree.validate(dataTreeModification);
-        DataTreeCandidate prepare = dataTree.prepare(dataTreeModification);
-        dataTree.commit(prepare);
-    }
-
-    static TipProducingDataTree getDataTree() throws ReactorException {
-        final TipProducingDataTree dataTree = InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION);
-        dataTree.setSchemaContext(getSchemaCtx());
-        return dataTree;
-    }
-
-    static ContainerNode getTopContainer(final String stringValue) {
-        return Builders.containerBuilder()
-                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
-                .withChild(ImmutableNodes.leafNode(STRING_LEAF_QNAME, stringValue))
-                .build();
-    }
-
-    static ContainerNode getTopContainerWithLeafList(final String... stringValue) {
-        final ListNodeBuilder<String, LeafSetEntryNode<String>> leafSetBuilder = Builders.leafSetBuilder();
-        for (final String value : stringValue) {
-            leafSetBuilder.withChild(Builders.<String>leafSetEntryBuilder()
-                    .withNodeIdentifier(new YangInstanceIdentifier.NodeWithValue<>(NESTED_LEAF_LIST_QNAME, value))
-                    .withValue(value)
-                    .build());
-        }
 
-        return Builders.containerBuilder()
-                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
-                .withChild(Builders.containerBuilder()
-                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(FOR_LEAF_LIST_QNAME))
-                        .withChild(leafSetBuilder
-                                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LEAF_LIST_QNAME))
-                                .build())
-                        .build())
-                .build();
-    }
-
-    static MapNode getNestedList(final String listItemName, final String text) {
-        return Builders.mapBuilder()
-                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME))
-                .withChild(
-                        Builders.mapEntryBuilder()
-                                .withNodeIdentifier(
-                                        new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME,
-                                                NAME_LEAF_QNAME, listItemName))
-                                .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
-                                .withChild(ImmutableNodes.leafNode(TEXT_LEAF_QNAME, text))
-                                .build()
-                )
-                .build();
-    }
-
-    private MapNode getDeepList(final String listItemName) {
-        return Builders.mapBuilder()
-                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME))
-                .withChild(
-                        Builders.mapEntryBuilder()
-                                .withNodeIdentifier(
-                                        new YangInstanceIdentifier.NodeIdentifierWithPredicates(DEEP_LIST_QNAME,
-                                                NAME_LEAF_QNAME, listItemName))
-                                .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
-                                .build()
-                )
-                .build();
-    }
-
-    static SchemaContext getSchemaCtx() throws ReactorException {
-        final CrossSourceStatementReactor.BuildAction buildAction = YangInferencePipeline.RFC6020_REACTOR.newBuild();
-        buildAction.addSource(new YangStatementSourceImpl(ModificationDiffTest.class.getResourceAsStream("/test-diff.yang")));
-        return buildAction.buildEffective();
-    }
 }
\ No newline at end of file
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationMetadata.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationMetadata.java
new file mode 100644 (file)
index 0000000..27d1630
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+abstract class ModificationMetadata {
+
+    private static final String YANG_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:test:diff";
+    final QName TOP_CONTAINER_QNAME = QName.create(YANG_NAMESPACE, "2015-01-05", "top-container");
+    final QName STRING_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "string");
+    final QName NAME_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "name");
+    final QName TEXT_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "text");
+    final QName NESTED_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-list");
+    final QName DEEP_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "deep-list");
+    final QName EMPTY_QNAME = QName.create(TOP_CONTAINER_QNAME, "empty");
+
+    final QName FOR_LEAF_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "for-leaf-list");
+    final QName NESTED_LEAF_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-leaf-list");
+
+    final QName WITH_CHOICE_CONTAINER_QNAME = QName.create(YANG_NAMESPACE, "2015-01-05", "with-choice");
+    final QName CHOICE_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "choice");
+    final QName IN_CASE1_LEAF_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "in-case1");
+
+    final QName PRESENCE_CONTAINER_QNAME = QName.create(TOP_CONTAINER_QNAME, "presence");
+
+    final QName NESTED_CONTAINER_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-container");
+    final QName NESTED_LIST_IN_CONTAINER_QNAME = QName.create(NESTED_CONTAINER_QNAME, "nested-list-in-container");
+    final QName IN_CONTAINER_NAME_LEAF_QNAME = QName.create(NESTED_LIST_IN_CONTAINER_QNAME, "name");
+    final QName NESTED_CONTAINER_VAL = QName.create(NESTED_CONTAINER_QNAME, "nested-container-val");
+    final QName NESTED_CONTAINER_LEAF_LIST = QName.create(NESTED_CONTAINER_QNAME, "nested-container-leaf-list");
+
+    final QName NESTED_CHOICE = QName.create(NESTED_CONTAINER_QNAME,"nested-choice");
+    final QName NESTED_CASE=QName.create(NESTED_CHOICE,"nested-case");
+    final QName UNDER_NESTED_CASE=QName.create(NESTED_CASE,"under-nested-case");
+
+    final QName AUG_LEAF = QName.create(NESTED_CONTAINER_QNAME,"under-aug-leaf");
+    final QName AUG_LEAFLIST = QName.create(NESTED_CONTAINER_QNAME,"under-aug-leaflist");
+    final QName AUG_CONTAINER = QName.create(NESTED_CONTAINER_QNAME,"under-aug-container");
+    final QName AUG_CONTAINER_LEAF = QName.create(NESTED_CONTAINER_QNAME,"under-aug-cont-leaf");
+    final QName AUG_LIST = QName.create(NESTED_CONTAINER_QNAME,"under-aug-list");
+    final QName AUG_LIST_KEY = QName.create(NESTED_CONTAINER_QNAME,"under-aug-list-key");
+
+    final QName NESTED_AUG_CONTAINER = QName.create(AUG_CONTAINER, "nested-under-aug-container");
+    final QName NESTED_AUG_CONTAINER_LEAF = QName.create(AUG_CONTAINER, "nested-under-aug-container-leaf");
+    final QName NESTED_AUG_LEAF = QName.create(AUG_CONTAINER, "nested-under-aug-leaf");
+    final QName NESTED_AUG_LIST = QName.create(AUG_CONTAINER, "nested-under-aug-list");
+    final QName NESTED_AUG_LIST_KEY = QName.create(AUG_CONTAINER, "nested-under-aug-list-key");
+    final QName NESTED_AUG_LEAF_LIST = QName.create(AUG_CONTAINER, "nested-under-aug-leaf-list");
+
+    final YangInstanceIdentifier TOP_CONTAINER_ID = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME);
+    final YangInstanceIdentifier NESTED_LIST_ID = TOP_CONTAINER_ID.node(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+}
index b7a0c7e..f86ad32 100644 (file)
@@ -28,6 +28,32 @@ module test-diff {
             }
         }
 
+        container nested-container {
+            list nested-list-in-container {
+                key "name";
+
+                leaf name {
+                    type string;
+                }
+            }
+
+            leaf nested-container-val {
+                type string;
+            }
+
+            leaf-list nested-container-leaf-list {
+                type string;
+            }
+
+            choice nested-choice {
+                case nested-case{
+                    leaf under-nested-case{
+                        type string;
+                    }
+                }
+            }
+        }
+
         list nested-list {
             key "name";
 
@@ -50,6 +76,52 @@ module test-diff {
         }
     }
 
+    augment /top-container/nested-container {
+        container under-aug-container{
+           leaf under-aug-cont-leaf{
+                type string;
+           }
+        }
+
+        leaf under-aug-leaf {
+            type string;
+        }
+
+        list under-aug-list {
+            key under-aug-list-key;
+            leaf under-aug-list-key{
+                type string;
+            }
+        }
+
+        leaf-list under-aug-leaflist{
+            type string;
+        }
+    }
+
+    augment /top-container/nested-container/under-aug-container {
+        container nested-under-aug-container {
+            leaf nested-under-aug-container-leaf {
+                type string;
+            }
+        }
+
+        leaf nested-under-aug-leaf{
+            type string;
+        }
+
+        list nested-under-aug-list{
+            key nested-under-aug-list-key;
+            leaf nested-under-aug-list-key{
+                type string;
+            }
+        }
+
+        leaf-list nested-under-aug-leaf-list {
+            type string;
+        }
+    }
+
     container with-choice {
 
         choice choice {