Move SchemaOrderedNormalizedNodeWriter to yangtools 84/34884/4
authorAndrej Mak <andmak@cisco.com>
Thu, 18 Feb 2016 10:01:31 +0000 (11:01 +0100)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 23 Feb 2016 10:11:53 +0000 (10:11 +0000)
Move SchemaOrderedNormalizedNodeWriter from netconf to yangtools
and refactor to reuse existing code.

Change-Id: Icdd7a1a2b43fd8e8ab0312b770d954b73939ff69
Signed-off-by: Andrej Mak <andmak@cisco.com>
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/stream/NormalizedNodeWriter.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/SchemaTracker.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaOrderedNormalizedNodeWriter.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaUtils.java
yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaOrderedNormalizedNodeWriterTest.java [new file with mode: 0644]
yang/yang-data-impl/src/test/resources/bug-1848-2.yang [new file with mode: 0644]
yang/yang-data-impl/src/test/resources/bug-1848.yang [new file with mode: 0644]

index abb8c811c64c05096ff737a46e4efebaed2be224..6b23f8ee208f292d6be9f11bcaa2c8049fed5e92 100644 (file)
@@ -50,7 +50,7 @@ import org.slf4j.LoggerFactory;
 public class NormalizedNodeWriter implements Closeable, Flushable {
     private final NormalizedNodeStreamWriter writer;
 
-    private NormalizedNodeWriter(final NormalizedNodeStreamWriter writer) {
+    protected NormalizedNodeWriter(final NormalizedNodeStreamWriter writer) {
         this.writer = Preconditions.checkNotNull(writer);
     }
 
@@ -98,7 +98,7 @@ public class NormalizedNodeWriter implements Closeable, Flushable {
      * @return NormalizedNodeWriter this
      * @throws IOException when thrown from the backing writer.
      */
-    public final NormalizedNodeWriter write(final NormalizedNode<?, ?> node) throws IOException {
+    public NormalizedNodeWriter write(final NormalizedNode<?, ?> node) throws IOException {
         if (wasProcessedAsCompositeNode(node)) {
             return this;
         }
@@ -129,11 +129,11 @@ public class NormalizedNodeWriter implements Closeable, Flushable {
      * @param children Child nodes
      * @return Best estimate of the collection size required to hold all the children.
      */
-    static final int childSizeHint(final Iterable<?> children) {
+    protected static final int childSizeHint(final Iterable<?> children) {
         return (children instanceof Collection) ? ((Collection<?>) children).size() : UNKNOWN_SIZE;
     }
 
-    private boolean wasProcessAsSimpleNode(final NormalizedNode<?, ?> node) throws IOException {
+    protected boolean wasProcessAsSimpleNode(final NormalizedNode<?, ?> node) throws IOException {
         if (node instanceof LeafSetEntryNode) {
             final LeafSetEntryNode<?> nodeAsLeafList = (LeafSetEntryNode<?>)node;
             final QName name = nodeAsLeafList.getIdentifier().getNodeType();
@@ -168,7 +168,7 @@ public class NormalizedNodeWriter implements Closeable, Flushable {
      * @return True
      * @throws IOException when the writer reports it
      */
-    protected final boolean writeChildren(final Iterable<? extends NormalizedNode<?, ?>> children) throws IOException {
+    protected boolean writeChildren(final Iterable<? extends NormalizedNode<?, ?>> children) throws IOException {
         for (final NormalizedNode<?, ?> child : children) {
             write(child);
         }
@@ -187,7 +187,7 @@ public class NormalizedNodeWriter implements Closeable, Flushable {
         return writeChildren(node.getValue());
     }
 
-    private boolean wasProcessedAsCompositeNode(final NormalizedNode<?, ?> node) throws IOException {
+    protected boolean wasProcessedAsCompositeNode(final NormalizedNode<?, ?> node) throws IOException {
         if (node instanceof ContainerNode) {
             final ContainerNode n = (ContainerNode) node;
             if(writer instanceof NormalizedNodeStreamAttributeWriter) {
index 472be45cbafb0a62845cc58147121a757eb0e15d..313adbcd530d81cad7c3603b252d0666993283ca 100644 (file)
@@ -55,55 +55,11 @@ public final class SchemaTracker {
     private final DataNodeContainer root;
 
     private SchemaTracker(final SchemaContext context, final SchemaPath path) {
-        SchemaNode current = Preconditions.checkNotNull(context);
-        for (final QName qname : path.getPathFromRoot()) {
-            SchemaNode child;
-            if(current instanceof DataNodeContainer) {
-                child = ((DataNodeContainer) current).getDataChildByName(qname);
-
-                if (child == null && current instanceof SchemaContext) {
-                    child = tryFindGroupings((SchemaContext) current, qname).orNull();
-                }
-
-                if(child == null && current instanceof SchemaContext) {
-                    child = tryFindNotification((SchemaContext) current, qname)
-                            .or(tryFindRpc(((SchemaContext) current), qname)).orNull();
-                }
-            } else if (current instanceof ChoiceSchemaNode) {
-                child = ((ChoiceSchemaNode) current).getCaseNodeByName(qname);
-            } else if (current instanceof RpcDefinition) {
-                switch (qname.getLocalName()) {
-                    case "input":
-                        child = ((RpcDefinition) current).getInput();
-                        break;
-                    case "output":
-                        child = ((RpcDefinition) current).getOutput();
-                        break;
-                    default:
-                        child = null;
-                        break;
-                }
-            } else {
-                throw new IllegalArgumentException(String.format("Schema node %s does not allow children.", current));
-            }
-            current = child;
-        }
+        SchemaNode current = SchemaUtils.findParentSchemaOnPath(context, path);
         Preconditions.checkArgument(current instanceof DataNodeContainer,"Schema path must point to container or list or an rpc input/output. Supplied path %s pointed to: %s",path,current);
         root = (DataNodeContainer) current;
     }
 
-    private static Optional<SchemaNode> tryFindGroupings(final SchemaContext ctx, final QName qname) {
-        return Optional.<SchemaNode> fromNullable(Iterables.find(ctx.getGroupings(), new SchemaNodePredicate(qname), null));
-    }
-
-    private static Optional<SchemaNode> tryFindRpc(final SchemaContext ctx, final QName qname) {
-        return Optional.<SchemaNode>fromNullable(Iterables.find(ctx.getOperations(), new SchemaNodePredicate(qname), null));
-    }
-
-    private static Optional<SchemaNode> tryFindNotification(final SchemaContext ctx, final QName qname) {
-        return Optional.<SchemaNode>fromNullable(Iterables.find(ctx.getNotifications(), new SchemaNodePredicate(qname), null));
-    }
-
     /**
      * Create a new writer with the specified context as its root.
      *
@@ -279,16 +235,4 @@ public final class SchemaTracker {
         return schemaStack.pop();
     }
 
-    private static final class SchemaNodePredicate implements Predicate<SchemaNode> {
-        private final QName qname;
-
-        public SchemaNodePredicate(final QName qname) {
-            this.qname = qname;
-        }
-
-        @Override
-        public boolean apply(final SchemaNode input) {
-            return input.getQName().equals(qname);
-        }
-    }
 }
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaOrderedNormalizedNodeWriter.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaOrderedNormalizedNodeWriter.java
new file mode 100644 (file)
index 0000000..e92f0dd
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema;
+
+import com.google.common.collect.ArrayListMultimap;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+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.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+/**
+ * This is an iterator over a {@link NormalizedNode}. Unlike {@link NormalizedNodeWriter},
+ * this iterates over elements in order as they are defined in .yang file.
+ */
+public class SchemaOrderedNormalizedNodeWriter extends NormalizedNodeWriter {
+
+    private final SchemaContext schemaContext;
+    private final SchemaNode root;
+    private final NormalizedNodeStreamWriter writer;
+
+    private SchemaNode currentSchemaNode;
+
+    /**
+     * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
+     * @param writer Back-end writer
+     * @param schemaContext Schema context
+     * @param path path
+     */
+    public SchemaOrderedNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext, final SchemaPath path) {
+        super(writer);
+        this.writer = writer;
+        this.schemaContext = schemaContext;
+        this.root = SchemaUtils.findParentSchemaOnPath(schemaContext, path);
+    }
+
+    @Override
+    public SchemaOrderedNormalizedNodeWriter write(final NormalizedNode<?, ?> node) throws IOException {
+        if (Objects.equals(root, schemaContext)) {
+            currentSchemaNode = schemaContext.getDataChildByName(node.getNodeType());
+        } else {
+            currentSchemaNode = root;
+        }
+        return write(node, currentSchemaNode);
+
+    }
+
+    /**
+     * Iterate over the provided collection and emit write
+     * events to the encapsulated {@link NormalizedNodeStreamWriter}.
+     *
+     * @param nodes nodes
+     * @return NormalizedNodeWriter this
+     * @throws IOException when thrown from the backing writer.
+     */
+    public SchemaOrderedNormalizedNodeWriter write(final Collection<DataContainerChild<?,?>> nodes) throws IOException {
+        currentSchemaNode = root;
+        if (writeChildren(nodes, currentSchemaNode, false)) {
+            return this;
+        }
+
+        throw new IllegalStateException("It wasn't possible to serialize nodes " + nodes);
+
+    }
+
+    private SchemaOrderedNormalizedNodeWriter write(final NormalizedNode<?, ?> node, final SchemaNode dataSchemaNode) throws IOException {
+
+        //Set current schemaNode
+        try (SchemaNodeSetter sns = new SchemaNodeSetter(dataSchemaNode)) {
+            if (node == null) {
+                return this;
+            }
+
+            if (wasProcessedAsCompositeNode(node)) {
+                return this;
+            }
+
+            if (wasProcessAsSimpleNode(node)) {
+                return this;
+            }
+        }
+
+        throw new IllegalStateException("It wasn't possible to serialize node " + node);
+    }
+
+    private void write(final List<NormalizedNode<?, ?>> nodes, final SchemaNode dataSchemaNode) throws IOException {
+        for (NormalizedNode<?, ?> node : nodes) {
+            write(node, dataSchemaNode);
+        }
+    }
+
+    protected boolean writeChildren(final Iterable<? extends NormalizedNode<?, ?>> children) throws IOException {
+        return writeChildren(children, currentSchemaNode, true);
+    }
+
+    private boolean writeChildren(final Iterable<? extends NormalizedNode<?, ?>> children, final SchemaNode parentSchemaNode, boolean endParent) throws IOException {
+        //Augmentations cannot be gotten with node.getChild so create our own structure with augmentations resolved
+        ArrayListMultimap<QName, NormalizedNode<?, ?>> qNameToNodes = ArrayListMultimap.create();
+        for (NormalizedNode<?, ?> child : children) {
+            if (child instanceof AugmentationNode) {
+                qNameToNodes.putAll(resolveAugmentations(child));
+            } else {
+                qNameToNodes.put(child.getNodeType(), child);
+            }
+        }
+
+        if (parentSchemaNode instanceof DataNodeContainer) {
+            if (parentSchemaNode instanceof ListSchemaNode && qNameToNodes.containsKey(parentSchemaNode.getQName())) {
+                write(qNameToNodes.get(parentSchemaNode.getQName()), parentSchemaNode);
+            } else {
+                for (DataSchemaNode schemaNode : ((DataNodeContainer) parentSchemaNode).getChildNodes()) {
+                    write(qNameToNodes.get(schemaNode.getQName()), schemaNode);
+                }
+            }
+        } else if(parentSchemaNode instanceof ChoiceSchemaNode) {
+            for (ChoiceCaseNode ccNode : ((ChoiceSchemaNode) parentSchemaNode).getCases()) {
+                for (DataSchemaNode dsn : ccNode.getChildNodes()) {
+                    if (qNameToNodes.containsKey(dsn.getQName())) {
+                        write(qNameToNodes.get(dsn.getQName()), dsn);
+                    }
+                }
+            }
+        } else {
+            for (NormalizedNode<?, ?> child : children) {
+                writeLeaf(child);
+            }
+        }
+        if (endParent) {
+            writer.endNode();
+        }
+        return true;
+    }
+
+    private SchemaOrderedNormalizedNodeWriter writeLeaf(final NormalizedNode<?, ?> node) throws IOException {
+        if (wasProcessAsSimpleNode(node)) {
+            return this;
+        }
+
+        throw new IllegalStateException("It wasn't possible to serialize node " + node);
+    }
+
+    private ArrayListMultimap<QName, NormalizedNode<?, ?>> resolveAugmentations(final NormalizedNode<?, ?> child) {
+        final ArrayListMultimap<QName, NormalizedNode<?, ?>> resolvedAugs = ArrayListMultimap.create();
+        for (NormalizedNode<?, ?> node : ((AugmentationNode) child).getValue()) {
+            if (node instanceof AugmentationNode) {
+                resolvedAugs.putAll(resolveAugmentations(node));
+            } else {
+                resolvedAugs.put(node.getNodeType(), node);
+            }
+        }
+        return resolvedAugs;
+    }
+
+
+    private class SchemaNodeSetter implements AutoCloseable {
+
+        private final SchemaNode previousSchemaNode;
+
+        /**
+         * Sets current schema node new value and store old value for later restore
+         */
+        public SchemaNodeSetter(final SchemaNode schemaNode) {
+            previousSchemaNode = SchemaOrderedNormalizedNodeWriter.this.currentSchemaNode;
+            SchemaOrderedNormalizedNodeWriter.this.currentSchemaNode = schemaNode;
+        }
+
+        /**
+         * Restore previous schema node
+         */
+        @Override
+        public void close() {
+            SchemaOrderedNormalizedNodeWriter.this.currentSchemaNode = previousSchemaNode;
+        }
+    }
+
+}
\ No newline at end of file
index 8de2f5bf483681e699199c78326bc735585524cb..04abae09054e4853bc077637af1b6fee94a917f9 100644 (file)
@@ -10,8 +10,10 @@ package org.opendaylight.yangtools.yang.data.impl.schema;
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.util.Collection;
@@ -31,6 +33,10 @@ import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
 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.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 
 public final class SchemaUtils {
 
@@ -397,4 +403,72 @@ public final class SchemaUtils {
         return new AugmentationIdentifier(ImmutableSet.copyOf(qnames));
     }
 
+    /**
+     * Finds schema node for given path in schema context.
+     * @param schemaContext schema context
+     * @param path path
+     * @return schema node on path
+     */
+    public static SchemaNode findParentSchemaOnPath(final SchemaContext schemaContext, final SchemaPath path) {
+        SchemaNode current = Preconditions.checkNotNull(schemaContext);
+        for (final QName qname : path.getPathFromRoot()) {
+            SchemaNode child;
+            if(current instanceof DataNodeContainer) {
+                child = ((DataNodeContainer) current).getDataChildByName(qname);
+
+                if (child == null && current instanceof SchemaContext) {
+                    child = tryFindGroupings((SchemaContext) current, qname).orNull();
+                }
+
+                if(child == null && current instanceof SchemaContext) {
+                    child = tryFindNotification((SchemaContext) current, qname)
+                            .or(tryFindRpc(((SchemaContext) current), qname)).orNull();
+                }
+            } else if (current instanceof ChoiceSchemaNode) {
+                child = ((ChoiceSchemaNode) current).getCaseNodeByName(qname);
+            } else if (current instanceof RpcDefinition) {
+                switch (qname.getLocalName()) {
+                    case "input":
+                        child = ((RpcDefinition) current).getInput();
+                        break;
+                    case "output":
+                        child = ((RpcDefinition) current).getOutput();
+                        break;
+                    default:
+                        child = null;
+                        break;
+                }
+            } else {
+                throw new IllegalArgumentException(String.format("Schema node %s does not allow children.", current));
+            }
+            current = child;
+        }
+        return current;
+    }
+
+    private static Optional<SchemaNode> tryFindGroupings(final SchemaContext ctx, final QName qname) {
+        return Optional.<SchemaNode> fromNullable(Iterables.find(ctx.getGroupings(), new SchemaNodePredicate(qname), null));
+    }
+
+    private static Optional<SchemaNode> tryFindRpc(final SchemaContext ctx, final QName qname) {
+        return Optional.<SchemaNode>fromNullable(Iterables.find(ctx.getOperations(), new SchemaNodePredicate(qname), null));
+    }
+
+    private static Optional<SchemaNode> tryFindNotification(final SchemaContext ctx, final QName qname) {
+        return Optional.<SchemaNode>fromNullable(Iterables.find(ctx.getNotifications(), new SchemaNodePredicate(qname), null));
+    }
+
+    private static final class SchemaNodePredicate implements Predicate<SchemaNode> {
+        private final QName qname;
+
+        public SchemaNodePredicate(final QName qname) {
+            this.qname = qname;
+        }
+
+        @Override
+        public boolean apply(final SchemaNode input) {
+            return input.getQName().equals(qname);
+        }
+    }
+
 }
diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaOrderedNormalizedNodeWriterTest.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaOrderedNormalizedNodeWriterTest.java
new file mode 100644 (file)
index 0000000..4eef71b
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamWriter;
+import org.custommonkey.xmlunit.Diff;
+import org.custommonkey.xmlunit.XMLAssert;
+import org.custommonkey.xmlunit.XMLUnit;
+import org.junit.Before;
+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.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+
+public class SchemaOrderedNormalizedNodeWriterTest {
+
+    private static final String EXPECTED_1 =
+            "<root>\n" +
+            "    <policy>\n" +
+            "        <name>policy1</name>\n" +
+            "        <rule>\n" +
+            "            <name>rule1</name>\n" +
+            "        </rule>\n" +
+            "        <rule>\n" +
+            "            <name>rule2</name>\n" +
+            "        </rule>\n" +
+            "        <rule>\n" +
+            "            <name>rule3</name>\n" +
+            "        </rule>\n" +
+            "        <rule>\n" +
+            "            <name>rule4</name>\n" +
+            "        </rule>\n" +
+            "    </policy>\n" +
+            "    <policy>\n" +
+            "        <name>policy2</name>\n" +
+            "    </policy>\n" +
+            "</root>\n";
+
+
+    private static final String EXPECTED_2 = "<root>\n" +
+            "    <id>id1</id>\n" +
+            "    <cont>\n" +
+            "        <content>content1</content>\n" +
+            "    </cont>\n" +
+            "</root>";
+
+
+    @Before
+    public void setUp() throws Exception {
+        XMLUnit.setIgnoreWhitespace(true);
+    }
+
+    @Test
+    public void testWrite() throws Exception {
+        final StringWriter stringWriter = new StringWriter();
+        final XMLStreamWriter xmlStreamWriter = XMLOutputFactory.newFactory().createXMLStreamWriter(stringWriter);
+
+        SchemaContext schemaContext = getSchemaContext("/bug-1848.yang");
+        NormalizedNodeStreamWriter writer = XMLStreamNormalizedNodeStreamWriter.create(xmlStreamWriter, schemaContext);
+        SchemaOrderedNormalizedNodeWriter nnw = new SchemaOrderedNormalizedNodeWriter(writer, schemaContext, SchemaPath.ROOT);
+
+        List<MapEntryNode> rule1Names = new ArrayList<>();
+        rule1Names.add(ImmutableNodes.mapEntry(createQName("foo", "rule"), createQName("foo", "name"), "rule1"));
+        rule1Names.add(ImmutableNodes.mapEntry(createQName("foo", "rule"), createQName("foo", "name"), "rule2"));
+
+        List<MapEntryNode> rule2Names = new ArrayList<>();
+        rule1Names.add(ImmutableNodes.mapEntry(createQName("foo", "rule"), createQName("foo", "name"), "rule3"));
+        rule1Names.add(ImmutableNodes.mapEntry(createQName("foo", "rule"), createQName("foo", "name"), "rule4"));
+
+        DataContainerChild<?, ?> rules1 = Builders.orderedMapBuilder()
+                .withNodeIdentifier(getNodeIdentifier("foo", "rule"))
+                .withValue(rule1Names)
+                .build();
+        DataContainerChild<?, ?> rules2 = Builders.orderedMapBuilder()
+                .withNodeIdentifier(getNodeIdentifier("foo", "rule"))
+                .withValue(rule2Names)
+                .build();
+
+        List<MapEntryNode> policyNodes = new ArrayList<>();
+
+
+        final MapEntryNode pn1 = ImmutableNodes
+                .mapEntryBuilder(createQName("foo", "policy"), createQName("foo", "name"), "policy1")
+                .withChild(rules1)
+                .build();
+        final MapEntryNode pn2 = ImmutableNodes
+                .mapEntryBuilder(createQName("foo", "policy"), createQName("foo", "name"), "policy2")
+                .withChild(rules2)
+                .build();
+        policyNodes.add(pn1);
+        policyNodes.add(pn2);
+
+
+
+        DataContainerChild<?, ?> policy = Builders.orderedMapBuilder()
+                .withNodeIdentifier(getNodeIdentifier("foo", "policy"))
+                .withValue(policyNodes)
+                .build();
+        NormalizedNode<?, ?> root = Builders.containerBuilder()
+                .withNodeIdentifier(getNodeIdentifier("foo", "root"))
+                .withChild(policy).build();
+        nnw.write(root);
+
+        XMLAssert.assertXMLIdentical(new Diff(EXPECTED_1, stringWriter.toString()), true);
+    }
+
+    @Test
+    public void testWriteOrder() throws Exception {
+        final StringWriter stringWriter = new StringWriter();
+        final XMLStreamWriter xmlStreamWriter = XMLOutputFactory.newFactory().createXMLStreamWriter(stringWriter);
+        SchemaContext schemaContext = getSchemaContext("/bug-1848-2.yang");
+        NormalizedNodeStreamWriter writer = XMLStreamNormalizedNodeStreamWriter.create(xmlStreamWriter, schemaContext);
+
+        NormalizedNodeWriter nnw = new SchemaOrderedNormalizedNodeWriter(writer, schemaContext, SchemaPath.ROOT);
+
+        DataContainerChild<?, ?> cont = Builders.containerBuilder()
+                .withNodeIdentifier(getNodeIdentifier("order", "cont"))
+                .withChild(ImmutableNodes.leafNode(createQName("order", "content"), "content1"))
+                .build();
+
+        NormalizedNode<?, ?> root = Builders.containerBuilder()
+                .withNodeIdentifier(getNodeIdentifier("order", "root"))
+                .withChild(cont)
+                .withChild(ImmutableNodes.leafNode(createQName("order", "id"), "id1"))
+                .build();
+
+        nnw.write(root);
+        System.out.println(stringWriter.toString());
+        System.out.println(EXPECTED_2);
+
+        XMLAssert.assertXMLIdentical(new Diff(EXPECTED_2, stringWriter.toString()), true);
+    }
+
+    private SchemaContext getSchemaContext(String filePath) throws URISyntaxException {
+        File file = new File(getClass().getResource(filePath).toURI());
+        return YangParserImpl.getInstance().parseFiles(Collections.singletonList(file));
+    }
+
+    private YangInstanceIdentifier.NodeIdentifier getNodeIdentifier(String ns, String name) {
+        return YangInstanceIdentifier.NodeIdentifier.create(createQName(ns, name));
+    }
+
+    private QName createQName(String ns, String name) {
+        return QName.create(ns, "2016-02-17", name);
+    }
+
+}
\ No newline at end of file
diff --git a/yang/yang-data-impl/src/test/resources/bug-1848-2.yang b/yang/yang-data-impl/src/test/resources/bug-1848-2.yang
new file mode 100644 (file)
index 0000000..ec5caa2
--- /dev/null
@@ -0,0 +1,16 @@
+module order {
+    namespace "order";
+    prefix "order";
+    revision "2016-02-17";
+  container root {
+    leaf id {
+      type string;
+    }
+    container cont {
+      leaf content {
+        type string;
+      }
+    }
+
+  }
+}
\ No newline at end of file
diff --git a/yang/yang-data-impl/src/test/resources/bug-1848.yang b/yang/yang-data-impl/src/test/resources/bug-1848.yang
new file mode 100644 (file)
index 0000000..0355d66
--- /dev/null
@@ -0,0 +1,15 @@
+module foo {
+    namespace "foo";
+    prefix "foo";
+    revision "2016-02-17";
+  container root {
+    list policy {
+      key name;
+      leaf name { type string; }
+      list rule {
+        key name;
+        leaf name { type string; }
+      }
+    }
+  }
+}
\ No newline at end of file