Bug 1441: Bug fixes, clean-up and test migration 43/39643/10
authorIgor Foltin <ifoltin@cisco.com>
Tue, 31 May 2016 10:53:59 +0000 (12:53 +0200)
committerRobert Varga <nite@hq.sk>
Thu, 23 Jun 2016 11:26:33 +0000 (11:26 +0000)
- fix of yang-modeled anyxml deserialization
- fix of anyxml deserialization
- fix of deserialization of identityref and instance-identifier leaves

- code clean-up of XmlParserStream and related classes

- migration of unit tests to new xml parser

- copied new xml serializer to yang-data-codec-xml module

Change-Id: Id214b78849998cf54e087685dcc78e3ded74ab69
Signed-off-by: Igor Foltin <ifoltin@cisco.com>
52 files changed:
yang/yang-data-codec-gson/pom.xml
yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YangModeledAnyXmlSupportTest.java
yang/yang-data-codec-xml/pom.xml
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefix.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefixInstanceIdentifierSerializer.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemaAwareXMLStreamNormalizedNodeStreamWriter.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemalessXMLStreamNormalizedNodeStreamWriter.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XMLStreamNormalizedNodeStreamWriter.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlCodec.java
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlCodecFactory.java
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlParserStream.java
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlStreamUtils.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlStringIdentityrefCodec.java
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlStringInstanceIdentifierCodec.java
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/Bug5396Test.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/Bug5446Test.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/NormalizedNodeXmlTranslationTest.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/NormalizedNodesToXmlTest.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefixTest.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlStreamUtilsTest.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlToNormalizedNodesTest.java
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YangModeledAnyXMLDeserializationTest.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YangModeledAnyXMLSerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/anyxml-support/serialization/baz.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/anyxml-support/serialization/baz.yang [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/anyxml-support/serialization/yang-ext.yang [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/anyxml-support/xml/bar.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/anyxml-support/xml/foo.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/anyxml-support/yang/bar.yang [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/anyxml-support/yang/foo.yang [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/anyxml-support/yang/yang-ext.yang [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo2.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo3.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo4.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo5.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug5396/xml/invalid-foo.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug5396/yang/foo.yang [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug5446/xml/foo.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug5446/yang/foo.yang [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/leafref-test.yang [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell.yang [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell_ok.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell_ok2.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell_ok3.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/rpc-test-model.yang [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/rpc-test-payload1.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/rpc-test-payload2.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/simple.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/simple2.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/simple_xml_with_attributes.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/schema/test.yang [new file with mode: 0644]

index 6a74e61fe88f41cbbfc8a79643373a6a450886c4..587d9cfb7751c458b881c8f8105b1eed3c54d1f3 100644 (file)
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-data-util</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-data-codec-xml</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-parser-impl</artifactId>
index 9033049a2d66bd2eaa0317863614a53af4cb29e4..fc93965366673737df96e0c91683dec708b38a5f 100644 (file)
@@ -8,9 +8,11 @@
 package org.opendaylight.yangtools.yang.data.codec.gson;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.opendaylight.yangtools.yang.data.codec.gson.TestUtils.loadModules;
 import static org.opendaylight.yangtools.yang.data.codec.gson.TestUtils.loadTextFile;
-import com.google.common.base.Preconditions;
+
 import com.google.gson.JsonElement;
 import com.google.gson.JsonParser;
 import com.google.gson.stream.JsonReader;
@@ -22,11 +24,10 @@ import java.io.StringReader;
 import java.io.StringWriter;
 import java.io.Writer;
 import java.net.URISyntaxException;
-import java.util.Collections;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
@@ -35,43 +36,40 @@ 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.data.codec.xml.XmlParserStream;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
-import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils;
-import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
-import org.w3c.dom.Document;
 import org.xml.sax.SAXException;
 
 public class YangModeledAnyXmlSupportTest {
 
-    private static final XMLOutputFactory XML_FACTORY;
-    private static final DocumentBuilderFactory BUILDERFACTORY;
-
-    static {
-        XML_FACTORY = XMLOutputFactory.newFactory();
-        XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, false);
-
-        BUILDERFACTORY = DocumentBuilderFactory.newInstance();
-        BUILDERFACTORY.setNamespaceAware(true);
-        BUILDERFACTORY.setCoalescing(true);
-        BUILDERFACTORY.setIgnoringElementContentWhitespace(true);
-        BUILDERFACTORY.setIgnoringComments(true);
-    }
-
     private static SchemaContext schemaContext;
-    private static Document xmlDoc;
     private static ContainerNode data;
 
     @BeforeClass
-    public static void init() throws IOException, URISyntaxException, ReactorException, SAXException {
+    public static void init() throws IOException, URISyntaxException, ReactorException, SAXException,
+            XMLStreamException, ParserConfigurationException {
         schemaContext = loadModules("/yang-modeled-anyxml/yang");
-        xmlDoc = loadDocument("/yang-modeled-anyxml/xml/baz.xml");
-        data = DomToNormalizedNodeParserFactory
-                .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext).getContainerNodeParser()
-                .parse(Collections.singletonList(xmlDoc.getDocumentElement()), schemaContext);
+
+        final InputStream resourceAsStream = YangModeledAnyXmlSupportTest.class.getResourceAsStream(
+                "/yang-modeled-anyxml/xml/baz.xml");
+
+        final XMLInputFactory factory = XMLInputFactory.newInstance();
+        final XMLStreamReader reader = factory.createXMLStreamReader(resourceAsStream);
+
+        final NormalizedNodeResult result = new NormalizedNodeResult();
+
+        final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+
+        final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, schemaContext);
+        xmlParser.parse(reader);
+
+        assertNotNull(result.getResult());
+        assertTrue(result.getResult() instanceof ContainerNode);
+        data = (ContainerNode) result.getResult();
     }
 
     @Test
@@ -83,12 +81,12 @@ public class YangModeledAnyXmlSupportTest {
         jsonParser.parse(new JsonReader(new StringReader(inputJson)));
         final NormalizedNode<?, ?> transformedInput = result.getResult();
 
-        assertEquals(data.getValue().iterator().next(), transformedInput);
+        assertEquals(data, transformedInput);
     }
 
     @Test
     public void normalizedNodesToJsonTest() throws IOException, URISyntaxException, SAXException {
-        final DataContainerChild<? extends PathArgument, ?> baz = data.getValue().iterator().next();
+        final DataContainerChild<? extends PathArgument, ?> baz = data;
 
         final Writer writer = new StringWriter();
         final String jsonOutput = normalizedNodeToJsonStreamTransformation(writer, baz);
@@ -113,26 +111,4 @@ public class YangModeledAnyXmlSupportTest {
         nodeWriter.close();
         return writer.toString();
     }
-
-    private static Document loadDocument(final String xmlPath) throws IOException, SAXException {
-        final InputStream resourceAsStream = YangModeledAnyXmlSupportTest.class.getResourceAsStream(xmlPath);
-
-        final Document currentConfigElement = readXmlToDocument(resourceAsStream);
-        Preconditions.checkNotNull(currentConfigElement);
-        return currentConfigElement;
-    }
-
-    private static Document readXmlToDocument(final InputStream xmlContent) throws IOException, SAXException {
-        final DocumentBuilder dBuilder;
-        try {
-            dBuilder = BUILDERFACTORY.newDocumentBuilder();
-        } catch (final ParserConfigurationException e) {
-            throw new RuntimeException("Failed to parse XML document", e);
-        }
-        final Document doc = dBuilder.parse(xmlContent);
-
-        doc.getDocumentElement().normalize();
-        return doc;
-    }
-
 }
\ No newline at end of file
index 750e497f7a5cfdeba452c2876ae4ef9b70d3c142..55a393099ba9c51737b2b632a9d607cae366535a 100644 (file)
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>xmlunit</groupId>
+            <artifactId>xmlunit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
   <!--
diff --git a/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefix.java b/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefix.java
new file mode 100644 (file)
index 0000000..80505dd
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014 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.codec.xml;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.net.URI;
+import java.util.Map.Entry;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+class RandomPrefix {
+    // 32 characters, carefully chosen
+    private static final String LOOKUP = "abcdefghiknoprstABCDEFGHIKNOPRST";
+    private static final int MASK = 0x1f;
+    private static final int SHIFT = 5;
+
+    private int counter = 0;
+
+    // BiMap to make values lookup faster
+    private final BiMap<URI, String> prefixes = HashBiMap.create();
+    private final NamespaceContext context;
+
+    RandomPrefix() {
+        this.context = null;
+    }
+
+    RandomPrefix(final NamespaceContext context) {
+        this.context = Preconditions.checkNotNull(context);
+    }
+
+    Iterable<Entry<URI, String>> getPrefixes() {
+        return prefixes.entrySet();
+    }
+
+    String encodePrefix(final URI namespace) {
+        String prefix = prefixes.get(namespace);
+        if (prefix != null) {
+            return prefix;
+        }
+
+        do {
+            prefix = encode(counter);
+            counter++;
+        } while (alreadyUsedPrefix(prefix));
+
+        prefixes.put(namespace, prefix);
+        return prefix;
+    }
+
+    private boolean alreadyUsedPrefix(final String prefix) {
+        if (context == null) {
+            return false;
+        }
+
+        final String str = context.getNamespaceURI(prefix);
+        return !XMLConstants.NULL_NS_URI.equals(str);
+    }
+
+    @VisibleForTesting
+    static int decode(final String str) {
+        int ret = 0;
+        for (char c : str.toCharArray()) {
+            int idx = LOOKUP.indexOf(c);
+            Preconditions.checkArgument(idx != -1, "Invalid string %s", str);
+            ret = (ret << SHIFT) + idx;
+        }
+
+        return ret;
+    }
+
+    @VisibleForTesting
+    static String encode(int num) {
+        final StringBuilder sb = new StringBuilder();
+
+        do {
+            sb.append(LOOKUP.charAt(num & MASK));
+            num >>>= SHIFT;
+        } while (num != 0);
+
+        return sb.reverse().toString();
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefixInstanceIdentifierSerializer.java b/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefixInstanceIdentifierSerializer.java
new file mode 100644 (file)
index 0000000..42e179d
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2014 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.codec.xml;
+
+import java.net.URI;
+import java.util.Map.Entry;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.util.AbstractStringInstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+final class RandomPrefixInstanceIdentifierSerializer extends AbstractStringInstanceIdentifierCodec {
+    private final RandomPrefix prefixes = new RandomPrefix();
+    private final DataSchemaContextTree schemaTree;
+
+
+    RandomPrefixInstanceIdentifierSerializer(SchemaContext ctx) {
+        schemaTree = DataSchemaContextTree.from(ctx);
+    }
+
+    Iterable<Entry<URI, String>> getPrefixes() {
+        return prefixes.getPrefixes();
+    }
+
+    @Override
+    protected String prefixForNamespace(final URI namespace) {
+        return prefixes.encodePrefix(namespace);
+    }
+
+    @Override
+    protected QName createQName(final String prefix, final String localName) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    protected DataSchemaContextTree getDataContextTree() {
+        return schemaTree;
+    }
+
+}
diff --git a/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemaAwareXMLStreamNormalizedNodeStreamWriter.java b/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemaAwareXMLStreamNormalizedNodeStreamWriter.java
new file mode 100644 (file)
index 0000000..c6b8736
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ * Copyright (c) 2016 Brocade Communications 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.codec.xml;
+
+import com.google.common.base.Strings;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.annotation.Nonnull;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker;
+import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+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.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+final class SchemaAwareXMLStreamNormalizedNodeStreamWriter extends XMLStreamNormalizedNodeStreamWriter<SchemaNode> {
+    private final SchemaTracker tracker;
+    private final XmlStreamUtils streamUtils;
+
+    private SchemaAwareXMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer, final SchemaContext context,
+                                                           final SchemaPath path) {
+        super(writer);
+        this.tracker = SchemaTracker.create(context, path);
+        this.streamUtils = XmlStreamUtils.create(context);
+    }
+
+    static NormalizedNodeStreamWriter newInstance(final XMLStreamWriter writer, final SchemaContext context,
+            final SchemaPath path) {
+        return new SchemaAwareXMLStreamNormalizedNodeStreamWriter(writer, context, path);
+    }
+
+    @Override
+    protected void writeAttributes(@Nonnull final Map<QName, String> attributes) throws IOException {
+        for (final Entry<QName, String> qNameStringEntry : attributes.entrySet()) {
+            try {
+                final String namespace = qNameStringEntry.getKey().getNamespace().toString();
+
+                if (Strings.isNullOrEmpty(namespace)) {
+                    writer.writeAttribute(qNameStringEntry.getKey().getLocalName(), qNameStringEntry.getValue());
+                } else {
+                    writer.writeAttribute(namespace, qNameStringEntry.getKey().getLocalName(), qNameStringEntry.getValue());
+                }
+            } catch (final XMLStreamException e) {
+                throw new IOException("Unable to emit attribute " + qNameStringEntry, e);
+            }
+        }
+    }
+
+    @Override
+    protected void writeValue(final XMLStreamWriter xmlWriter, final QName qname, final Object value,
+            final SchemaNode schemaNode) throws IOException, XMLStreamException {
+        streamUtils.writeValue(xmlWriter, schemaNode, value, qname.getModule());
+    }
+
+    @Override
+    protected void startList(final NodeIdentifier name) {
+        tracker.startList(name);
+    }
+
+    @Override
+    protected void startListItem(final PathArgument name) throws IOException {
+        tracker.startListItem(name);
+        startElement(name.getNodeType());
+    }
+
+    @Override
+    protected void endNode(final XMLStreamWriter xmlWriter) throws IOException, XMLStreamException {
+        final Object schema = tracker.endNode();
+        if (schema instanceof ListSchemaNode) {
+            // For lists, we only emit end element on the inner frame
+            final Object parent = tracker.getParent();
+            if (parent == schema) {
+                xmlWriter.writeEndElement();
+            }
+        } else if (schema instanceof ContainerSchemaNode) {
+            // Emit container end element
+            xmlWriter.writeEndElement();
+        }
+    }
+
+    @Override
+    public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
+        final LeafSchemaNode schema = tracker.leafNode(name);
+        writeElement(schema.getQName(), value, Collections.emptyMap(), schema);
+    }
+
+    @Override
+    public void leafNode(NodeIdentifier name, Object value, Map<QName, String> attributes) throws IOException {
+        final LeafSchemaNode schema = tracker.leafNode(name);
+        writeElement(schema.getQName(), value, attributes, schema);
+    }
+
+    @Override
+    public void leafSetEntryNode(final QName name, final Object value, final Map<QName, String> attributes)
+            throws IOException {
+        final LeafListSchemaNode schema = tracker.leafSetEntryNode();
+        writeElement(schema.getQName(), value, attributes, schema);
+    }
+
+    @Override
+    public void leafSetEntryNode(final QName name, final Object value) throws IOException {
+        final LeafListSchemaNode schema = tracker.leafSetEntryNode();
+        writeElement(schema.getQName(), value, Collections.emptyMap(), schema);
+    }
+    @Override
+    public void startLeafSet(final NodeIdentifier name, final int childSizeHint) {
+        tracker.startLeafSet(name);
+    }
+
+    @Override
+    public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) {
+        tracker.startLeafSet(name);
+    }
+
+    @Override
+    public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        final SchemaNode schema = tracker.startContainerNode(name);
+        startElement(schema.getQName());
+    }
+
+    @Override
+    public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) {
+        tracker.startChoiceNode(name);
+    }
+
+    @Override
+    public void startAugmentationNode(final AugmentationIdentifier identifier) {
+        tracker.startAugmentationNode(identifier);
+    }
+
+    @Override
+    public void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        final SchemaNode schema = tracker.startYangModeledAnyXmlNode(name);
+        startElement(schema.getQName());
+    }
+
+    @Override
+    public void anyxmlNode(final NodeIdentifier name, final Object value) throws IOException {
+        final AnyXmlSchemaNode schema = tracker.anyxmlNode(name);
+        anyxmlNode(schema.getQName(), value);
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemalessXMLStreamNormalizedNodeStreamWriter.java b/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemalessXMLStreamNormalizedNodeStreamWriter.java
new file mode 100644 (file)
index 0000000..e218986
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.codec.xml;
+
+import com.google.common.base.Strings;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.annotation.Nonnull;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+
+class SchemalessXMLStreamNormalizedNodeStreamWriter extends XMLStreamNormalizedNodeStreamWriter<Object> {
+    private enum ContainerType {
+        CONTAINER,
+        LEAF_SET,
+        LIST,
+        LIST_ITEM,
+        ANY_XML,
+        CHOICE,
+        AUGMENTATION
+    }
+
+    private final Deque<ContainerType> containerTypeStack = new ArrayDeque<>();
+    private final RandomPrefix randomPrefix;
+
+    private SchemalessXMLStreamNormalizedNodeStreamWriter(XMLStreamWriter writer) {
+        super(writer);
+        randomPrefix = new RandomPrefix();
+    }
+
+    static NormalizedNodeStreamWriter newInstance(XMLStreamWriter writer) {
+        return new SchemalessXMLStreamNormalizedNodeStreamWriter(writer);
+    }
+
+    @Override
+    public void leafNode(NodeIdentifier name, Object value, Map<QName, String> attributes) throws IOException {
+        writeElement(name.getNodeType(), value, attributes, null);
+    }
+
+    @Override
+    public void leafSetEntryNode(QName name, Object value, Map<QName, String> attributes) throws IOException {
+        writeElement(name, value, attributes, null);
+    }
+
+    @Override
+    public void leafNode(NodeIdentifier name, Object value) throws IOException {
+        writeElement(name.getNodeType(), value, Collections.emptyMap(), null);
+    }
+
+    @Override
+    public void leafSetEntryNode(QName name, Object value) throws IOException {
+        writeElement(name, value, Collections.emptyMap(), null);
+    }
+
+    @Override
+    public void startLeafSet(NodeIdentifier name, int childSizeHint) throws IOException {
+        containerTypeStack.push(ContainerType.LEAF_SET);
+    }
+
+    @Override
+    public void startOrderedLeafSet(NodeIdentifier name, int childSizeHint)
+            throws IOException, IllegalArgumentException {
+        containerTypeStack.push(ContainerType.LEAF_SET);
+    }
+
+    @Override
+    public void startContainerNode(NodeIdentifier name, int childSizeHint) throws IOException {
+        containerTypeStack.push(ContainerType.CONTAINER);
+        startElement(name.getNodeType());
+    }
+
+    @Override
+    public void startChoiceNode(NodeIdentifier name, int childSizeHint) throws IOException {
+        containerTypeStack.push(ContainerType.CHOICE);
+    }
+
+    @Override
+    public void startAugmentationNode(AugmentationIdentifier identifier) throws IOException {
+        containerTypeStack.push(ContainerType.AUGMENTATION);
+    }
+
+    @Override
+    public void anyxmlNode(NodeIdentifier name, Object value) throws IOException {
+        anyxmlNode(name.getNodeType(), value);
+    }
+
+    @Override
+    public void startYangModeledAnyXmlNode(NodeIdentifier name, int childSizeHint) throws IOException {
+        containerTypeStack.push(ContainerType.ANY_XML);
+        startElement(name.getNodeType());
+    }
+
+    @Override
+    protected void writeAttributes(@Nonnull final Map<QName, String> attributes) throws IOException {
+        for (final Entry<QName, String> qNameStringEntry : attributes.entrySet()) {
+            try {
+                final String namespace = qNameStringEntry.getKey().getNamespace().toString();
+
+                if (Strings.isNullOrEmpty(namespace)) {
+                    writer.writeAttribute(qNameStringEntry.getKey().getLocalName(), qNameStringEntry.getValue());
+                } else {
+                    final String prefix = randomPrefix.encodePrefix(qNameStringEntry.getKey().getNamespace());
+                    writer.writeAttribute(prefix, namespace, qNameStringEntry.getKey().getLocalName(), qNameStringEntry
+                            .getValue());
+                }
+            } catch (final XMLStreamException e) {
+                throw new IOException("Unable to emit attribute " + qNameStringEntry, e);
+            }
+        }
+    }
+
+    @Override
+    protected void writeValue(XMLStreamWriter xmlWriter, QName qname, Object value, Object context)
+            throws XMLStreamException {
+        xmlWriter.writeCharacters(value.toString());
+    }
+
+    @Override
+    protected void startList(NodeIdentifier name) {
+        containerTypeStack.push(ContainerType.LIST);
+    }
+
+    @Override
+    protected void startListItem(PathArgument name) throws IOException {
+        containerTypeStack.push(ContainerType.LIST_ITEM);
+        startElement(name.getNodeType());
+    }
+
+    @Override
+    protected void endNode(XMLStreamWriter xmlWriter) throws IOException, XMLStreamException {
+        ContainerType type = containerTypeStack.pop();
+        switch (type) {
+        case CONTAINER:
+        case LIST_ITEM:
+        case ANY_XML:
+            xmlWriter.writeEndElement();
+            break;
+        default:
+            break;
+        }
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XMLStreamNormalizedNodeStreamWriter.java b/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XMLStreamNormalizedNodeStreamWriter.java
new file mode 100644 (file)
index 0000000..bc88103
--- /dev/null
@@ -0,0 +1,451 @@
+/*
+ * Copyright (c) 2014 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.codec.xml;
+
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stax.StAXResult;
+import javax.xml.transform.stream.StreamResult;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
+
+/**
+ * A {@link NormalizedNodeStreamWriter} which translates the events into an {@link XMLStreamWriter},
+ * resulting in a RFC 6020 XML encoding. There are 2 versions of this class, one that takes a
+ * SchemaContext and encodes values appropriately according to the yang schema. The other is
+ * schema-less and merely outputs values using toString. The latter is intended for debugging
+ * where doesn't have a SchemaContext available and isn't meant for production use.
+ */
+public abstract class XMLStreamNormalizedNodeStreamWriter<T> implements NormalizedNodeStreamAttributeWriter {
+    private static final Logger LOG = LoggerFactory.getLogger(XMLStreamNormalizedNodeStreamWriter.class);
+    private static final String COM_SUN_TRANSFORMER = "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
+
+    private static final TransformerFactory TRANSFORMER_FACTORY;
+    static {
+        TransformerFactory f = TransformerFactory.newInstance();
+        if (!f.getFeature(StAXResult.FEATURE)) {
+            LOG.warn("Platform-default TransformerFactory {} does not support StAXResult, attempting fallback to {}",
+                    f, COM_SUN_TRANSFORMER);
+            f = TransformerFactory.newInstance(COM_SUN_TRANSFORMER, null);
+            if (!f.getFeature(StAXResult.FEATURE)) {
+                throw new TransformerFactoryConfigurationError("No TransformerFactory supporting StAXResult found.");
+            }
+        }
+
+        TRANSFORMER_FACTORY = f;
+    }
+
+    final XMLStreamWriter writer;
+
+    XMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer) {
+        this.writer = Preconditions.checkNotNull(writer);
+    }
+
+    /**
+     * Create a new writer with the specified context as its root.
+     *
+     * @param writer Output {@link XMLStreamWriter}
+     * @param context Associated {@link SchemaContext}.
+     * @return A new {@link NormalizedNodeStreamWriter}
+     */
+    public static NormalizedNodeStreamWriter create(final XMLStreamWriter writer, final SchemaContext context) {
+        return create( writer, context, SchemaPath.ROOT);
+    }
+
+    /**
+     * Create a new writer with the specified context and rooted in the specified schema path
+     *
+     * @param writer Output {@link XMLStreamWriter}
+     * @param context Associated {@link SchemaContext}.
+     * @param path path
+     *
+     * @return A new {@link NormalizedNodeStreamWriter}
+     */
+    public static NormalizedNodeStreamWriter create(final XMLStreamWriter writer, final SchemaContext context,
+            final SchemaPath path) {
+        return SchemaAwareXMLStreamNormalizedNodeStreamWriter.newInstance(writer, context, path);
+    }
+
+    /**
+     * Create a new schema-less writer. Note that this version is intended for debugging
+     * where doesn't have a SchemaContext available and isn't meant for production use.
+     *
+     * @param writer Output {@link XMLStreamWriter}
+     *
+     * @return A new {@link NormalizedNodeStreamWriter}
+     */
+    public static NormalizedNodeStreamWriter createSchemaless(final XMLStreamWriter writer) {
+        return SchemalessXMLStreamNormalizedNodeStreamWriter.newInstance(writer);
+    }
+
+    abstract void writeAttributes(@Nonnull final Map<QName, String> attributes) throws IOException;
+
+    abstract void writeValue(final XMLStreamWriter xmlWriter, final QName qname,
+            @Nonnull final Object value, T context) throws IOException, XMLStreamException;
+
+    abstract void startList(final NodeIdentifier name);
+
+    abstract void startListItem(final PathArgument name) throws IOException;
+
+    abstract void endNode(XMLStreamWriter xmlWriter) throws IOException, XMLStreamException;
+
+    private void writeStartElement(final QName qname) throws XMLStreamException {
+        String ns = qname.getNamespace().toString();
+        writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, qname.getLocalName(), ns);
+        if (writer.getNamespaceContext() != null) {
+            String parentNs = writer.getNamespaceContext().getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX);
+            if (!ns.equals(parentNs)) {
+                writer.writeDefaultNamespace(ns);
+            }
+        }
+    }
+
+    void writeElement(final QName qname, final Object value, @Nullable final Map<QName, String> attributes,
+            final T context) throws IOException {
+        try {
+            writeStartElement(qname);
+
+            writeAttributes(attributes);
+            if (value != null) {
+                writeValue(writer, qname, value, context);
+            }
+            writer.writeEndElement();
+        } catch (XMLStreamException e) {
+            throw new IOException("Failed to emit element", e);
+        }
+    }
+
+    void startElement(final QName qname) throws IOException {
+        try {
+            writeStartElement(qname);
+        } catch (XMLStreamException e) {
+            throw new IOException("Failed to start element", e);
+        }
+    }
+
+    void anyxmlNode(final QName qname, final Object value) throws IOException {
+        if (value != null) {
+            Preconditions.checkArgument(value instanceof DOMSource, "AnyXML value must be DOMSource, not %s", value);
+            final DOMSource domSource = (DOMSource) value;
+            Preconditions.checkNotNull(domSource.getNode());
+            Preconditions.checkArgument(domSource.getNode().getNodeName().equals(qname.getLocalName()));
+            Preconditions.checkArgument(domSource.getNode().getNamespaceURI().equals(qname.getNamespace().toString()));
+            try {
+                // TODO can the transformer be a constant ? is it thread safe ?
+                final Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
+                // Writer has to be wrapped in a wrapper that ignores endDocument event
+                // EndDocument event forbids any other modification to the writer so a nested anyXml breaks serialization
+                transformer.transform(domSource, new StAXResult(new DelegateWriterNoEndDoc(writer)));
+            } catch (final TransformerException e) {
+                throw new IOException("Unable to transform anyXml(" + qname + ") value: " + value, e);
+            }
+        }
+    }
+
+    @Override
+    public final void startContainerNode(final NodeIdentifier name, final int childSizeHint,
+                                         final Map<QName, String> attributes) throws IOException {
+        startContainerNode(name, childSizeHint);
+        writeAttributes(attributes);
+    }
+
+    @Override
+    public final void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint,
+                                                 final Map<QName, String> attributes) throws IOException {
+        startYangModeledAnyXmlNode(name, childSizeHint);
+        writeAttributes(attributes);
+    }
+
+    @Override
+    public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint,
+                                           final Map<QName, String> attributes) throws IOException {
+        startUnkeyedListItem(name, childSizeHint);
+        writeAttributes(attributes);
+    }
+
+    @Override
+    public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint,
+                                        final Map<QName, String> attributes) throws IOException {
+        startMapEntryNode(identifier, childSizeHint);
+        writeAttributes(attributes);
+    }
+
+    @Override
+    public final void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) {
+        startList(name);
+    }
+
+    @Override
+    public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        startListItem(name);
+    }
+
+    @Override
+    public final void startMapNode(final NodeIdentifier name, final int childSizeHint) {
+        startList(name);
+    }
+
+    @Override
+    public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
+            throws IOException {
+        startListItem(identifier);
+    }
+
+    @Override
+    public final void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) {
+        startList(name);
+    }
+
+    public static String toString(final Element xml) {
+        try {
+            final Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+            final StreamResult result = new StreamResult(new StringWriter());
+            final DOMSource source = new DOMSource(xml);
+            transformer.transform(source, result);
+
+            return result.getWriter().toString();
+        } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) {
+            throw new RuntimeException("Unable to serialize xml element " + xml, e);
+        }
+    }
+
+    @Override
+    public final void endNode() throws IOException {
+        try {
+            endNode(writer);
+        } catch (XMLStreamException e) {
+            throw new IOException("Failed to end element", e);
+        }
+    }
+
+    @Override
+    public final void close() throws IOException {
+        try {
+            writer.close();
+        } catch (XMLStreamException e) {
+            throw new IOException("Failed to close writer", e);
+        }
+    }
+
+    @Override
+    public final void flush() throws IOException {
+        try {
+            writer.flush();
+        } catch (XMLStreamException e) {
+            throw new IOException("Failed to flush writer", e);
+        }
+    }
+
+    /**
+     * Delegate writer that ignores writeEndDocument event. Used for AnyXml serialization.
+     */
+    private static final class DelegateWriterNoEndDoc implements XMLStreamWriter {
+        private final XMLStreamWriter writer;
+
+        public DelegateWriterNoEndDoc(final XMLStreamWriter writer) {
+            this.writer = writer;
+        }
+
+        @Override
+        public void writeStartElement(final String localName) throws XMLStreamException {
+            writer.writeStartElement(localName);
+        }
+
+        @Override
+        public void writeStartElement(final String namespaceURI, final String localName) throws XMLStreamException {
+            writer.writeStartElement(namespaceURI, localName);
+        }
+
+        @Override
+        public void writeStartElement(final String prefix, final String localName, final String namespaceURI)
+                throws XMLStreamException {
+            writer.writeStartElement(prefix, localName, namespaceURI);
+        }
+
+        @Override
+        public void writeEmptyElement(final String namespaceURI, final String localName) throws XMLStreamException {
+            writer.writeEmptyElement(namespaceURI, localName);
+        }
+
+        @Override
+        public void writeEmptyElement(final String prefix, final String localName, final String namespaceURI)
+                throws XMLStreamException {
+            writer.writeEmptyElement(prefix, localName, namespaceURI);
+        }
+
+        @Override
+        public void writeEmptyElement(final String localName) throws XMLStreamException {
+            writer.writeEmptyElement(localName);
+        }
+
+        @Override
+        public void writeEndElement() throws XMLStreamException {
+            writer.writeEndElement();
+
+        }
+
+        @Override
+        public void writeEndDocument() throws XMLStreamException {
+            // End document is disabled
+        }
+
+        @Override
+        public void close() throws XMLStreamException {
+            writer.close();
+        }
+
+        @Override
+        public void flush() throws XMLStreamException {
+            writer.flush();
+        }
+
+        @Override
+        public void writeAttribute(final String localName, final String value) throws XMLStreamException {
+            writer.writeAttribute(localName, value);
+        }
+
+        @Override
+        public void writeAttribute(final String prefix, final String namespaceURI, final String localName,
+                                   final String value) throws XMLStreamException {
+            writer.writeAttribute(prefix, namespaceURI, localName, value);
+        }
+
+        @Override
+        public void writeAttribute(final String namespaceURI, final String localName, final String value)
+                throws XMLStreamException {
+            writer.writeAttribute(namespaceURI, localName, value);
+        }
+
+        @Override
+        public void writeNamespace(final String prefix, final String namespaceURI) throws XMLStreamException {
+            // Workaround for default namespace
+            // If a namespace is not prefixed, it is is still treated as prefix namespace.
+            // This results in the NamespaceSupport class ignoring the namespace since xmlns is not a valid prefix
+            // Write the namespace at least as an attribute
+            // TODO this is a hotfix, the transformer itself should write namespaces passing the namespace
+            // in writeStartElement method
+            if (prefix.equals("xml") || prefix.equals("xmlns")) {
+                writer.writeAttribute(prefix, namespaceURI);
+            } else {
+                writer.writeNamespace(prefix, namespaceURI);
+            }
+        }
+
+        @Override
+        public void writeDefaultNamespace(final String namespaceURI) throws XMLStreamException {
+            writer.writeDefaultNamespace(namespaceURI);
+        }
+
+        @Override
+        public void writeComment(final String data) throws XMLStreamException {
+            writer.writeComment(data);
+        }
+
+        @Override
+        public void writeProcessingInstruction(final String target) throws XMLStreamException {
+            writer.writeProcessingInstruction(target);
+        }
+
+        @Override
+        public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException {
+            writer.writeProcessingInstruction(target, data);
+        }
+
+        @Override
+        public void writeCData(final String data) throws XMLStreamException {
+            writer.writeCData(data);
+        }
+
+        @Override
+        public void writeDTD(final String dtd) throws XMLStreamException {
+            writer.writeDTD(dtd);
+        }
+
+        @Override
+        public void writeEntityRef(final String name) throws XMLStreamException {
+            writer.writeEntityRef(name);
+        }
+
+        @Override
+        public void writeStartDocument() throws XMLStreamException {
+        }
+
+        @Override
+        public void writeStartDocument(final String version) throws XMLStreamException {
+        }
+
+        @Override
+        public void writeStartDocument(final String encoding, final String version) throws XMLStreamException {
+        }
+
+        @Override
+        public void writeCharacters(final String text) throws XMLStreamException {
+            writer.writeCharacters(text);
+        }
+
+        @Override
+        public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException {
+            writer.writeCharacters(text, start, len);
+        }
+
+        @Override
+        public String getPrefix(final String uri) throws XMLStreamException {
+            return writer.getPrefix(uri);
+        }
+
+        @Override
+        public void setPrefix(final String prefix, final String uri) throws XMLStreamException {
+            // Disabled since it causes exceptions in the underlying writer
+        }
+
+        @Override
+        public void setDefaultNamespace(final String uri) throws XMLStreamException {
+            writer.setDefaultNamespace(uri);
+        }
+
+        @Override
+        public void setNamespaceContext(final NamespaceContext context) throws XMLStreamException {
+            writer.setNamespaceContext(context);
+        }
+
+        @Override
+        public NamespaceContext getNamespaceContext() {
+            return writer.getNamespaceContext();
+        }
+
+        @Override
+        public Object getProperty(final String name) {
+            return writer.getProperty(name);
+        }
+    }
+}
index fe591358abc3ac179140dfea95e14d90b901b45f..d8307aec56b2b3df24ec7fcabcc4483787e65a61 100644 (file)
@@ -19,6 +19,7 @@ interface XmlCodec<T> extends Codec<String, T> {
      *
      * @param writer XMLStreamWriter
      * @param value value which will be serialized to the writer
+     * @throws XMLStreamException from {@link XMLStreamWriter}
      */
     void serializeToWriter(XMLStreamWriter writer, T value) throws XMLStreamException;
 }
index fe1573d6c648dc668633b32a69040d685bea1f1d..56a0333d4a4171fa9d75b3c6a59a63be3d998f7f 100644 (file)
@@ -8,11 +8,16 @@
 
 package org.opendaylight.yangtools.yang.data.codec.xml;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Map.Entry;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.xml.namespace.NamespaceContext;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -27,10 +32,11 @@ import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
-import org.opendaylight.yangtools.yang.model.util.type.DerivedTypes;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+@Beta
+@ThreadSafe
 public final class XmlCodecFactory {
 
     private static final Logger LOG = LoggerFactory.getLogger(XmlCodecFactory.class);
@@ -53,28 +59,29 @@ public final class XmlCodecFactory {
         }
     };
 
-    private final LoadingCache<DataSchemaNode, XmlCodec<?>> codecs =
-            CacheBuilder.newBuilder().softValues().build(new CacheLoader<DataSchemaNode, XmlCodec<?>>() {
+    private final LoadingCache<Entry<DataSchemaNode, NamespaceContext>, XmlCodec<?>> codecs =
+            CacheBuilder.newBuilder().softValues().build(
+                    new CacheLoader<Entry<DataSchemaNode, NamespaceContext>, XmlCodec<?>>() {
                 @Override
-                public XmlCodec<?> load(final DataSchemaNode key) throws Exception {
+                public XmlCodec<?> load(final Entry<DataSchemaNode, NamespaceContext> schemaNodeAndNamespaceCtxPair)
+                        throws Exception {
+                    final DataSchemaNode schemaNode = schemaNodeAndNamespaceCtxPair.getKey();
                     final TypeDefinition<?> type;
-                    if (key instanceof LeafSchemaNode) {
-                        type = ((LeafSchemaNode) key).getType();
-                    } else if (key instanceof LeafListSchemaNode) {
-                        type = ((LeafListSchemaNode) key).getType();
+                    if (schemaNode instanceof LeafSchemaNode) {
+                        type = ((LeafSchemaNode) schemaNode).getType();
+                    } else if (schemaNode instanceof LeafListSchemaNode) {
+                        type = ((LeafListSchemaNode) schemaNode).getType();
                     } else {
-                        throw new IllegalArgumentException("Not supported node type " + key.getClass().getName());
+                        throw new IllegalArgumentException("Not supported node type " + schemaNode.getClass().getName());
                     }
-                    return createCodec(key,type);
+                    return createCodec(schemaNode,type, schemaNodeAndNamespaceCtxPair.getValue());
                 }
             });
 
     private final SchemaContext schemaContext;
-    private final XmlCodec<YangInstanceIdentifier> iidCodec;
 
     private XmlCodecFactory(final SchemaContext context) {
         this.schemaContext = Preconditions.checkNotNull(context);
-        iidCodec = new XmlStringInstanceIdentifierCodec(context, this);
     }
 
     /**
@@ -87,28 +94,31 @@ public final class XmlCodecFactory {
         return new XmlCodecFactory(context);
     }
 
-    private XmlCodec<?> createCodec(final DataSchemaNode key, final TypeDefinition<?> type) {
-        final TypeDefinition<?> normalizedType = DerivedTypes.derivedTypeBuilder(type, type.getPath()).build();
-        if (normalizedType instanceof LeafrefTypeDefinition) {
-            return createReferencedTypeCodec(key, (LeafrefTypeDefinition) normalizedType);
-        } else if (normalizedType instanceof IdentityrefTypeDefinition) {
+    private XmlCodec<?> createCodec(final DataSchemaNode key, final TypeDefinition<?> type,
+                                    final NamespaceContext namespaceContext) {
+        if (type instanceof LeafrefTypeDefinition) {
+            return createReferencedTypeCodec(key, (LeafrefTypeDefinition) type, namespaceContext);
+        } else if (type instanceof IdentityrefTypeDefinition) {
             final XmlCodec<?> xmlStringIdentityrefCodec =
-                    new XmlStringIdentityrefCodec(schemaContext, key.getQName().getModule());
+                    new XmlStringIdentityrefCodec(schemaContext, key.getQName().getModule(), namespaceContext);
             return xmlStringIdentityrefCodec;
         }
-        return createFromSimpleType(normalizedType);
+        return createFromSimpleType(type, namespaceContext);
     }
 
-    private XmlCodec<?> createReferencedTypeCodec(final DataSchemaNode schema, final LeafrefTypeDefinition type) {
+    private XmlCodec<?> createReferencedTypeCodec(final DataSchemaNode schema, final LeafrefTypeDefinition type,
+                                                  final NamespaceContext namespaceContext) {
         // FIXME: Verify if this does indeed support leafref of leafref
         final TypeDefinition<?> referencedType =
                 SchemaContextUtil.getBaseTypeForLeafRef(type, getSchemaContext(), schema);
         Verify.verifyNotNull(referencedType, "Unable to find base type for leafref node '%s'.", schema.getPath());
-        return createCodec(schema, referencedType);
+        return createCodec(schema, referencedType, namespaceContext);
     }
 
-    private XmlCodec<?> createFromSimpleType(final TypeDefinition<?> type) {
+    private XmlCodec<?> createFromSimpleType(final TypeDefinition<?> type, final NamespaceContext namespaceContext) {
         if (type instanceof InstanceIdentifierTypeDefinition) {
+            final XmlCodec<YangInstanceIdentifier> iidCodec = new XmlStringInstanceIdentifierCodec(schemaContext, this,
+                    namespaceContext);
             return iidCodec;
         }
         if (type instanceof EmptyTypeDefinition) {
@@ -127,7 +137,7 @@ public final class XmlCodecFactory {
         return schemaContext;
     }
 
-    XmlCodec<?> codecFor(final DataSchemaNode schema) {
-        return codecs.getUnchecked(schema);
+    XmlCodec<?> codecFor(final DataSchemaNode schema, final NamespaceContext namespaceContext) {
+        return codecs.getUnchecked(new SimpleImmutableEntry<>(schema, namespaceContext));
     }
 }
index 1ea337039e99465bb56fa425b5f2e112b406f632..63dc8f8e3d4f8c4ac19baaad5d2636937d9511a7 100644 (file)
@@ -8,6 +8,7 @@
 
 package org.opendaylight.yangtools.yang.data.codec.xml;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
 import java.io.Closeable;
 import java.io.Flushable;
@@ -18,9 +19,11 @@ import java.net.URISyntaxException;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.Set;
-import javax.xml.parsers.DocumentBuilder;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.xml.namespace.NamespaceContext;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.Location;
 import javax.xml.stream.XMLStreamConstants;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
@@ -44,6 +47,7 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.YangModeledAnyXmlSchemaNode;
 import org.w3c.dom.Document;
+import org.w3c.dom.Element;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 
@@ -52,17 +56,23 @@ import org.xml.sax.SAXException;
  * instances of the same element except for leaf-list and list entries. It also expects that the YANG-modeled data in
  * the XML source are wrapped in a root element.
  */
+@Beta
+@NotThreadSafe
 public final class XmlParserStream implements Closeable, Flushable {
+    private static final DocumentBuilderFactory FACTORY;
+
+    static {
+        DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
+        f.setNamespaceAware(true);
+        FACTORY = f;
+    }
 
-    private String rootElement = null;
     private final NormalizedNodeStreamWriter writer;
     private final XmlCodecFactory codecs;
-    private final SchemaContext schema;
     private final DataSchemaNode parentNode;
 
     private XmlParserStream(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext,
                              final DataSchemaNode parentNode) {
-        this.schema = Preconditions.checkNotNull(schemaContext);
         this.writer = Preconditions.checkNotNull(writer);
         this.codecs = XmlCodecFactory.create(schemaContext);
         this.parentNode = parentNode;
@@ -105,8 +115,7 @@ public final class XmlParserStream implements Closeable, Flushable {
         if (reader.hasNext()) {
             final CompositeNodeDataWithSchema compositeNodeDataWithSchema = new CompositeNodeDataWithSchema(parentNode);
             reader.nextTag();
-            rootElement = reader.getLocalName();
-            read(reader, compositeNodeDataWithSchema);
+            read(reader, compositeNodeDataWithSchema, reader.getLocalName());
             compositeNodeDataWithSchema.write(writer);
         }
 
@@ -114,135 +123,134 @@ public final class XmlParserStream implements Closeable, Flushable {
     }
 
     private static String readAnyXmlValue(final XMLStreamReader in) throws XMLStreamException {
-        String result = "";
-        String anyXmlElementName = in.getLocalName();
+        final StringBuilder sb = new StringBuilder();
+        final String anyXmlElementName = in.getLocalName();
+        sb.append('<').append(anyXmlElementName).append(" xmlns=\"").append(in.getNamespaceURI()).append("\">");
 
         while (in.hasNext()) {
-            int eventType = in.next();
+            final int eventType = in.next();
 
             if (eventType == XMLStreamConstants.START_ELEMENT) {
-                result += "<" + in.getLocalName() + ">";
+                sb.append('<').append(in.getLocalName()).append('>');
             } else if (eventType == XMLStreamConstants.END_ELEMENT) {
+                sb.append("</").append(in.getLocalName()).append('>');
+
                 if (in.getLocalName().equals(anyXmlElementName)) {
                     break;
                 }
 
-                result += "</" + in.getLocalName() + ">";
             } else if (eventType == XMLStreamConstants.CHARACTERS) {
-                result += in.getText();
+                sb.append(in.getText());
             }
         }
 
-        return result;
+        return sb.toString();
     }
 
-    private void read(final XMLStreamReader in, final AbstractNodeDataWithSchema parent) throws XMLStreamException,
-            URISyntaxException, ParserConfigurationException, SAXException, IOException {
-        if (in.hasNext()) {
-            if (parent instanceof LeafNodeDataWithSchema || parent instanceof LeafListEntryNodeDataWithSchema) {
-                setValue(parent, in.getElementText().trim());
-                in.nextTag();
-                return;
-            } else if (parent instanceof LeafListNodeDataWithSchema || parent instanceof ListNodeDataWithSchema) {
-                String parentSchemaName = parent.getSchema().getQName().getLocalName();
-                String xmlElementName = in.getLocalName();
-                while (xmlElementName.equals(parentSchemaName)) {
-                    AbstractNodeDataWithSchema newChild = newEntryNode(parent);
-                    read(in, newChild);
-                    xmlElementName = in.getLocalName();
-                }
+    private void read(final XMLStreamReader in, final AbstractNodeDataWithSchema parent, final String rootElement)
+            throws XMLStreamException, URISyntaxException, ParserConfigurationException, SAXException, IOException {
+        if (!in.hasNext()) {
+            return;
+        }
 
-                return;
-            } else if (parent instanceof AnyXmlNodeDataWithSchema) {
-                setValue(parent, readAnyXmlValue(in));
-                in.nextTag();
-                return;
+        if (parent instanceof LeafNodeDataWithSchema || parent instanceof LeafListEntryNodeDataWithSchema) {
+            setValue(parent, in.getElementText().trim(), in.getNamespaceContext());
+            in.nextTag();
+            return;
+        }
+
+        if (parent instanceof LeafListNodeDataWithSchema || parent instanceof ListNodeDataWithSchema) {
+            String xmlElementName = in.getLocalName();
+            while (xmlElementName.equals(parent.getSchema().getQName().getLocalName())) {
+                read(in, newEntryNode(parent), rootElement);
+                xmlElementName = in.getLocalName();
             }
 
-            switch (in.nextTag()) {
-                case XMLStreamConstants.START_ELEMENT:
-                    final Set<String> namesakes = new HashSet<>();
-                    while (in.hasNext()) {
-                        String xmlElementName = in.getLocalName();
-                        String xmlElementNamespace = in.getNamespaceURI();
-
-                        if (xmlElementName.equals(rootElement)) {
-                            break;
-                        }
-
-                        DataSchemaNode parentSchema = parent.getSchema();
-                        if (parentSchema instanceof YangModeledAnyXmlSchemaNode) {
-                            parentSchema = ((YangModeledAnyXmlSchemaNode) parentSchema).getSchemaOfAnyXmlData();
-                        }
-
-                        String parentSchemaName = parentSchema.getQName().getLocalName();
-                        if (parentSchemaName.equals(xmlElementName)
-                                && in.getEventType() == XMLStreamConstants.END_ELEMENT) {
-                            in.nextTag();
-                            break;
-                        }
-
-                        if (namesakes.contains(xmlElementName)) {
-                            int lineNumber = in.getLocation().getLineNumber();
-                            int columnNumber = in.getLocation().getColumnNumber();
-                            throw new IllegalStateException("Duplicate element \"" + xmlElementName + "\" in XML " +
-                                    "input at: line " + lineNumber + " column " + columnNumber);
-                        }
-                        namesakes.add(xmlElementName);
-
-                        Deque<DataSchemaNode> childDataSchemaNodes = ParserStreamUtils.findSchemaNodeByNameAndNamespace(
-                                parentSchema, xmlElementName, new URI(xmlElementNamespace));
-
-                        if (childDataSchemaNodes.isEmpty()) {
-                            throw new IllegalStateException("Schema for node with name " + xmlElementName +
-                                    " and namespace " + xmlElementNamespace + " doesn't exist.");
-                        }
-
-                        AbstractNodeDataWithSchema newChild =
-                                ((CompositeNodeDataWithSchema) parent).addChild(childDataSchemaNodes);
-
-                        read(in, newChild);
+            return;
+        }
+
+        if (parent instanceof AnyXmlNodeDataWithSchema) {
+            setValue(parent, readAnyXmlValue(in), in.getNamespaceContext());
+            in.nextTag();
+            return;
+        }
+
+        switch (in.nextTag()) {
+            case XMLStreamConstants.START_ELEMENT:
+                final Set<String> namesakes = new HashSet<>();
+                while (in.hasNext()) {
+                    final String xmlElementName = in.getLocalName();
+                    if (rootElement.equals(xmlElementName)) {
+                        break;
                     }
-                    break;
-                case XMLStreamConstants.END_ELEMENT:
-                    in.nextTag();
-                    break;
-            }
+
+                    DataSchemaNode parentSchema = parent.getSchema();
+
+                    final String parentSchemaName = parentSchema.getQName().getLocalName();
+                    if (parentSchemaName.equals(xmlElementName) && in.getEventType() == XMLStreamConstants.END_ELEMENT) {
+                        in.nextTag();
+                        break;
+                    }
+
+                    if (parentSchema instanceof YangModeledAnyXmlSchemaNode) {
+                        parentSchema = ((YangModeledAnyXmlSchemaNode) parentSchema).getSchemaOfAnyXmlData();
+                    }
+
+                    if (!namesakes.add(xmlElementName)) {
+                        final Location loc = in.getLocation();
+                        throw new IllegalStateException(String.format(
+                                "Duplicate element \"%s\" in XML input at: line %s column %s", xmlElementName,
+                                loc.getLineNumber(), loc.getColumnNumber()));
+                    }
+
+                    final String xmlElementNamespace = in.getNamespaceURI();
+                    final Deque<DataSchemaNode> childDataSchemaNodes =
+                            ParserStreamUtils.findSchemaNodeByNameAndNamespace(parentSchema, xmlElementName,
+                                    new URI(xmlElementNamespace));
+
+                    Preconditions.checkState(!childDataSchemaNodes.isEmpty(),
+                            "Schema for node with name %s and namespace %s doesn't exist.",
+                            xmlElementName, xmlElementNamespace);
+
+                    read(in, ((CompositeNodeDataWithSchema) parent).addChild(childDataSchemaNodes), rootElement);
+                }
+                break;
+            case XMLStreamConstants.END_ELEMENT:
+                in.nextTag();
+                break;
         }
     }
 
-    private void setValue(final AbstractNodeDataWithSchema parent, final String value) throws
-            ParserConfigurationException, SAXException, IOException {
+    private void setValue(final AbstractNodeDataWithSchema parent, final String value, final NamespaceContext nsContext)
+            throws ParserConfigurationException, SAXException, IOException {
         Preconditions.checkArgument(parent instanceof SimpleNodeDataWithSchema, "Node %s is not a simple type",
                 parent.getSchema().getQName());
         final SimpleNodeDataWithSchema parentSimpleNode = (SimpleNodeDataWithSchema) parent;
         Preconditions.checkArgument(parentSimpleNode.getValue() == null, "Node '%s' has already set its value to '%s'",
                 parentSimpleNode.getSchema().getQName(), parentSimpleNode.getValue());
 
-        final Object translatedValue = translateValueByType(value, parentSimpleNode.getSchema());
-        parentSimpleNode.setValue(translatedValue);
+        parentSimpleNode.setValue(translateValueByType(value, parentSimpleNode.getSchema(), nsContext));
     }
 
-    private Object translateValueByType(final String value, final DataSchemaNode node) throws IOException,
-            SAXException, ParserConfigurationException {
+    private Object translateValueByType(final String value, final DataSchemaNode node, final NamespaceContext namespaceCtx)
+            throws IOException, SAXException, ParserConfigurationException {
         if (node instanceof AnyXmlSchemaNode) {
             /*
              *  FIXME: Figure out some YANG extension dispatch, which will
              *  reuse JSON parsing or XML parsing - anyxml is not well-defined in
              * JSON.
              */
-            DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-            Document doc = db.parse( new InputSource(new StringReader(value)));
+            final Document doc = FACTORY.newDocumentBuilder().parse(new InputSource(new StringReader(value)));
             doc.normalize();
-            DOMSource anyXmlValueSource = new DOMSource(doc);
 
-            return anyXmlValueSource;
+            return new DOMSource(doc.getDocumentElement());
+        } else {
+            return codecs.codecFor(node, namespaceCtx).deserialize(value);
         }
-        return codecs.codecFor(node).deserialize(value);
     }
 
     private static AbstractNodeDataWithSchema newEntryNode(final AbstractNodeDataWithSchema parent) {
-        AbstractNodeDataWithSchema newChild;
+        final AbstractNodeDataWithSchema newChild;
         if (parent instanceof ListNodeDataWithSchema) {
             newChild = new ListEntryNodeDataWithSchema(parent.getSchema());
         } else {
diff --git a/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlStreamUtils.java b/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlStreamUtils.java
new file mode 100644 (file)
index 0000000..28927d6
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2015 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.codec.xml;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import java.net.URI;
+import java.util.Map.Entry;
+import javax.annotation.Nonnull;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class for bridging JAXP Stream and YANG Data APIs. Note that the definition of this class
+ * by no means final and subject to change as more functionality is centralized here.
+ */
+class XmlStreamUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(XmlStreamUtils.class);
+    private final Optional<SchemaContext> schemaContext;
+
+    private XmlStreamUtils(final SchemaContext schemaContext) {
+        this.schemaContext = Optional.fromNullable(schemaContext);
+    }
+
+    static XmlStreamUtils create(final SchemaContext schemaContext) {
+        return new XmlStreamUtils(schemaContext);
+    }
+
+    @VisibleForTesting
+    static void writeAttribute(final XMLStreamWriter writer, final Entry<QName, String> attribute,
+                               final RandomPrefix randomPrefix) throws XMLStreamException {
+        final QName key = attribute.getKey();
+        final String prefix = randomPrefix.encodePrefix(key.getNamespace());
+        writer.writeAttribute("xmlns:" + prefix, key.getNamespace().toString());
+        writer.writeAttribute(prefix, key.getNamespace().toString(), key.getLocalName(), attribute.getValue());
+    }
+
+    /**
+     * Write a value into a XML stream writer. This method assumes the start and end of element is
+     * emitted by the caller.
+     *
+     * @param writer XML Stream writer
+     * @param schemaNode Schema node that describes the value
+     * @param value data value
+     * @param parent optional parameter of a module QName owning the leaf definition
+     * @throws XMLStreamException if an encoding problem occurs
+     */
+    void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final SchemaNode schemaNode,
+                           final Object value, final Optional<QNameModule> parent) throws XMLStreamException {
+        if (value == null) {
+            LOG.debug("Value of {}:{} is null, not encoding it", schemaNode.getQName().getNamespace(),
+                    schemaNode.getQName().getLocalName());
+            return;
+        }
+
+        Preconditions.checkArgument(schemaNode instanceof LeafSchemaNode || schemaNode instanceof LeafListSchemaNode,
+                "Unable to write value for node %s, only nodes of type: leaf and leaf-list can be written at this point",
+                schemaNode.getQName());
+
+        TypeDefinition<?> type = schemaNode instanceof LeafSchemaNode ?
+                ((LeafSchemaNode) schemaNode).getType():
+                ((LeafListSchemaNode) schemaNode).getType();
+
+        if (schemaContext.isPresent() && type instanceof LeafrefTypeDefinition) {
+            LeafrefTypeDefinition leafrefTypeDefinition = (LeafrefTypeDefinition) type;
+            type = SchemaContextUtil.getBaseTypeForLeafRef(leafrefTypeDefinition, schemaContext.get(), schemaNode);
+            Verify.verifyNotNull(type, "Unable to find base type for leafref node '%s'.", schemaNode.getPath());
+        }
+
+        writeValue(writer, type, value, parent);
+    }
+
+    void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final SchemaNode schemaNode,
+                           final Object value) throws XMLStreamException {
+        writeValue(writer, schemaNode, value, Optional.absent());
+    }
+
+    void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final SchemaNode schemaNode,
+                           final Object value, final QNameModule parent) throws XMLStreamException {
+        writeValue(writer, schemaNode, value, Optional.of(parent));
+    }
+
+    /**
+     * Write a value into a XML stream writer. This method assumes the start and end of element is
+     * emitted by the caller.
+     *
+     * @param writer XML Stream writer
+     * @param type data type. In case of leaf ref this should be the type of leaf being referenced
+     * @param value data value
+     * @param parent optional parameter of a module QName owning the leaf definition
+     * @throws XMLStreamException if an encoding problem occurs
+     */
+    void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final TypeDefinition<?> type,
+                           final Object value, final Optional<QNameModule> parent) throws XMLStreamException {
+        if (value == null) {
+            LOG.debug("Value of {}:{} is null, not encoding it", type.getQName().getNamespace(),
+                    type.getQName().getLocalName());
+            return;
+        }
+
+        if (type instanceof IdentityrefTypeDefinition) {
+            if (parent.isPresent()) {
+                write(writer, (IdentityrefTypeDefinition) type, value, parent);
+            } else {
+                write(writer, (IdentityrefTypeDefinition) type, value, Optional.absent());
+            }
+        } else if (type instanceof InstanceIdentifierTypeDefinition) {
+            write(writer, (InstanceIdentifierTypeDefinition) type, value);
+        } else {
+            final TypeDefinitionAwareCodec<Object, ?> codec = TypeDefinitionAwareCodec.from(type);
+            String text;
+            if (codec != null) {
+                try {
+                    text = codec.serialize(value);
+                } catch (ClassCastException e) {
+                    LOG.warn("Provided node value {} did not have type {} required by mapping. Using stream instead.",
+                            value, type, e);
+                    text = String.valueOf(value);
+                }
+            } else {
+                LOG.warn("Failed to find codec for {}, falling back to using stream", type);
+                text = String.valueOf(value);
+            }
+            writer.writeCharacters(text);
+        }
+    }
+
+    void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final TypeDefinition<?> type,
+                           final Object value, final QNameModule parent) throws XMLStreamException {
+        writeValue(writer, type, value, Optional.of(parent));
+    }
+
+    void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final TypeDefinition<?> type,
+                           final Object value) throws XMLStreamException {
+        writeValue(writer, type, value, Optional.absent());
+    }
+
+    @VisibleForTesting
+    static void write(@Nonnull final XMLStreamWriter writer, @Nonnull final IdentityrefTypeDefinition type,
+                      @Nonnull final Object value, final Optional<QNameModule>  parent) throws XMLStreamException {
+        if (value instanceof QName) {
+            final QName qname = (QName) value;
+            final String prefix = "x";
+
+            //in case parent is present and same as element namespace write value without namespace
+            if (parent.isPresent() && qname.getNamespace().equals(parent.get().getNamespace())){
+                writer.writeCharacters(qname.getLocalName());
+            } else {
+                final String ns = qname.getNamespace().toString();
+                writer.writeNamespace(prefix, ns);
+                writer.writeCharacters(prefix + ':' + qname.getLocalName());
+            }
+
+        } else {
+            LOG.debug("Value of {}:{} is not a QName but {}", type.getQName().getNamespace(),
+                    type.getQName().getLocalName(), value.getClass());
+            writer.writeCharacters(String.valueOf(value));
+        }
+    }
+
+    private void write(@Nonnull final XMLStreamWriter writer, @Nonnull final InstanceIdentifierTypeDefinition type,
+                       @Nonnull final Object value) throws XMLStreamException {
+        if (value instanceof YangInstanceIdentifier) {
+            writeInstanceIdentifier(writer, (YangInstanceIdentifier)value);
+        } else {
+            LOG.warn("Value of {}:{} is not an InstanceIdentifier but {}", type.getQName().getNamespace(),
+                    type.getQName().getLocalName(), value.getClass());
+            writer.writeCharacters(String.valueOf(value));
+        }
+    }
+
+    void writeInstanceIdentifier(final XMLStreamWriter writer, final YangInstanceIdentifier value)
+            throws XMLStreamException {
+        if (schemaContext.isPresent()) {
+            RandomPrefixInstanceIdentifierSerializer iiCodec =
+                    new RandomPrefixInstanceIdentifierSerializer(schemaContext.get());
+            String serializedValue = iiCodec.serialize(value);
+            writeNamespaceDeclarations(writer, iiCodec.getPrefixes());
+            writer.writeCharacters(serializedValue);
+        } else {
+            LOG.warn("Schema context not present in {}, serializing {} without schema.",this,value);
+            writeInstanceIdentifier(writer, value);
+        }
+    }
+
+    private static void writeNamespaceDeclarations(final XMLStreamWriter writer,
+                                               final Iterable<Entry<URI, String>> prefixes) throws XMLStreamException {
+        for (Entry<URI, String> e: prefixes) {
+            writer.writeNamespace(e.getValue(), e.getKey().toString());
+        }
+    }
+}
index 2ea18600cd6adb357289147814b33efc32ea23b1..baeb011097f55ed31e446f56b0e47744dd437912 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.data.codec.xml;
 
 import com.google.common.base.Preconditions;
 import java.net.URI;
+import javax.xml.namespace.NamespaceContext;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -22,10 +23,13 @@ final class XmlStringIdentityrefCodec extends AbstractModuleStringIdentityrefCod
 
     private final SchemaContext context;
     private final QNameModule parentModuleQname;
+    private final NamespaceContext namespaceContext;
 
-    XmlStringIdentityrefCodec(final SchemaContext context, final QNameModule parentModule) {
+    XmlStringIdentityrefCodec(final SchemaContext context, final QNameModule parentModule,
+                              final NamespaceContext namespaceContext) {
         this.context = Preconditions.checkNotNull(context);
         this.parentModuleQname = Preconditions.checkNotNull(parentModule);
+        this.namespaceContext = Preconditions.checkNotNull(namespaceContext);
     }
 
     @Override
@@ -34,7 +38,8 @@ final class XmlStringIdentityrefCodec extends AbstractModuleStringIdentityrefCod
             return context.findModuleByNamespaceAndRevision(parentModuleQname.getNamespace(),
                     parentModuleQname.getRevision());
         } else {
-            return context.findModuleByName(prefix, null);
+            final String prefixedNS = namespaceContext.getNamespaceURI(prefix);
+            return context.findModuleByNamespaceAndRevision(URI.create(prefixedNS), null);
         }
     }
 
@@ -51,7 +56,7 @@ final class XmlStringIdentityrefCodec extends AbstractModuleStringIdentityrefCod
      * @param value QName
      */
     @Override
-    public void serializeToWriter(XMLStreamWriter writer, QName value) throws XMLStreamException {
+    public void serializeToWriter(final XMLStreamWriter writer, final QName value) throws XMLStreamException {
         writer.writeCharacters(serialize(value));
     }
 }
index 9e68b27b65a7a8a89031e1ac483796714bb75172..11845c2880a6a60e69911b82581fe34ab4a7746b 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.data.codec.xml;
 
 import com.google.common.base.Preconditions;
 import java.net.URI;
+import javax.xml.namespace.NamespaceContext;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -26,16 +27,20 @@ final class XmlStringInstanceIdentifierCodec  extends AbstractModuleStringInstan
     private final DataSchemaContextTree dataContextTree;
     private final XmlCodecFactory codecFactory;
     private final SchemaContext context;
+    private final NamespaceContext namespaceContext;
 
-    XmlStringInstanceIdentifierCodec(final SchemaContext context, final XmlCodecFactory jsonCodecFactory) {
+    XmlStringInstanceIdentifierCodec(final SchemaContext context, final XmlCodecFactory xmlCodecFactory,
+                                     final NamespaceContext namespaceContext) {
         this.context = Preconditions.checkNotNull(context);
         this.dataContextTree = DataSchemaContextTree.from(context);
-        this.codecFactory = Preconditions.checkNotNull(jsonCodecFactory);
+        this.codecFactory = Preconditions.checkNotNull(xmlCodecFactory);
+        this.namespaceContext = Preconditions.checkNotNull(namespaceContext);
     }
 
     @Override
     protected Module moduleForPrefix(final String prefix) {
-        return context.findModuleByName(prefix, null);
+        final String prefixedNS = namespaceContext.getNamespaceURI(prefix);
+        return context.findModuleByNamespaceAndRevision(URI.create(prefixedNS), null);
     }
 
     @Override
@@ -53,7 +58,7 @@ final class XmlStringInstanceIdentifierCodec  extends AbstractModuleStringInstan
     protected Object deserializeKeyValue(final DataSchemaNode schemaNode, final String value) {
         Preconditions.checkNotNull(schemaNode, "schemaNode cannot be null");
         Preconditions.checkArgument(schemaNode instanceof LeafSchemaNode, "schemaNode must be of type LeafSchemaNode");
-        final XmlCodec<?> objectXmlCodec = codecFactory.codecFor(schemaNode);
+        final XmlCodec<?> objectXmlCodec = codecFactory.codecFor(schemaNode, namespaceContext);
         return objectXmlCodec.deserialize(value);
     }
 
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/Bug5396Test.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/Bug5396Test.java
new file mode 100644 (file)
index 0000000..b6ffc06
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * 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.codec.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Optional;
+import java.io.InputStream;
+import java.net.URI;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamReader;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.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.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+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;
+
+public class Bug5396Test {
+
+    private QNameModule fooModuleQName;
+    private SchemaContext schemaContext;
+
+    @Before
+    public void Init() throws Exception {
+        fooModuleQName = QNameModule.create(new URI("foo"), SimpleDateFormatUtil.getRevisionFormat().parse("2016-03-22"));
+
+        CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+        reactor.addSource(new YangStatementSourceImpl("/bug5396/yang/foo.yang", false));
+
+        schemaContext = reactor.buildEffective();
+    }
+
+    @Test
+    public void test() throws Exception {
+        testInputXML("/bug5396/xml/foo.xml", "dp1o34");
+        testInputXML("/bug5396/xml/foo2.xml", "dp0s3f9");
+        testInputXML("/bug5396/xml/foo3.xml", "dp09P1p2s3");
+        testInputXML("/bug5396/xml/foo4.xml", "dp0p3p1");
+        testInputXML("/bug5396/xml/foo5.xml", "dp0s3");
+
+        try {
+            testInputXML("/bug5396/xml/invalid-foo.xml", null);
+            fail("Test should fail due to invalid input string");
+        } catch (IllegalArgumentException e) {
+            assertTrue(e.getMessage().startsWith("Invalid value \"dp09P1p2s1234\" for union type."));
+        }
+    }
+
+    private void testInputXML(String xmlPath, String expectedValue) throws Exception {
+        final InputStream resourceAsStream = XmlToNormalizedNodesTest.class.getResourceAsStream(xmlPath);
+
+        final XMLInputFactory factory = XMLInputFactory.newInstance();
+        final XMLStreamReader reader = factory.createXMLStreamReader(resourceAsStream);
+
+        final NormalizedNodeResult result = new NormalizedNodeResult();
+
+        final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+
+        final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, schemaContext);
+        xmlParser.parse(reader);
+
+        assertNotNull(result.getResult());
+        assertTrue(result.getResult() instanceof ContainerNode);
+        final ContainerNode rootContainer = (ContainerNode) result.getResult();
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> myLeaf = rootContainer.getChild(new NodeIdentifier(
+                QName.create(fooModuleQName, "my-leaf")));
+        assertTrue(myLeaf.orNull() instanceof LeafNode);
+
+        assertEquals(expectedValue, myLeaf.get().getValue());
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/Bug5446Test.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/Bug5446Test.java
new file mode 100644 (file)
index 0000000..5838a44
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * 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.codec.xml;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.net.URI;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.custommonkey.xmlunit.XMLTestCase;
+import org.custommonkey.xmlunit.XMLUnit;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.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.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.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+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.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+public class Bug5446Test extends XMLTestCase {
+    private static final XMLOutputFactory XML_FACTORY;
+    private static final DocumentBuilderFactory BUILDERFACTORY;
+
+    static {
+        XML_FACTORY = XMLOutputFactory.newFactory();
+        XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, false);
+
+        BUILDERFACTORY = DocumentBuilderFactory.newInstance();
+        BUILDERFACTORY.setNamespaceAware(true);
+        BUILDERFACTORY.setCoalescing(true);
+        BUILDERFACTORY.setIgnoringElementContentWhitespace(true);
+        BUILDERFACTORY.setIgnoringComments(true);
+    }
+
+    private QNameModule fooModuleQName;
+    private QName rootQName;
+    private QName ipAddressQName;
+    private SchemaContext schemaContext;
+
+    public Bug5446Test() throws Exception {
+        fooModuleQName = QNameModule.create(new URI("foo"), SimpleDateFormatUtil.getRevisionFormat()
+                .parse("2015-11-05"));
+        rootQName = QName.create(fooModuleQName, "root");
+        ipAddressQName = QName.create(fooModuleQName, "ip-address");
+
+        CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+        reactor.addSource(new YangStatementSourceImpl("/bug5446/yang/foo.yang", false));
+
+        schemaContext = reactor.buildEffective();
+    }
+
+    @Test
+    public void test() throws Exception {
+        final Document doc = loadDocument("/bug5446/xml/foo.xml");
+
+        final ContainerNode docNode = createDocNode();
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> root = docNode.getChild(new NodeIdentifier(rootQName));
+        assertTrue(root.orNull() instanceof ContainerNode);
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> child = ((ContainerNode) root.orNull())
+                .getChild(new NodeIdentifier(ipAddressQName));
+        assertTrue(child.orNull() instanceof LeafNode);
+        LeafNode<?> ipAdress = (LeafNode<?>) child.get();
+
+        Object value = ipAdress.getValue();
+        assertTrue(value instanceof byte[]);
+        assertEquals("fwAAAQ==", BaseEncoding.base64().encode((byte[]) value));
+
+        DOMResult serializationResult = writeNormalizedNode(docNode, schemaContext);
+        assertNotNull(serializationResult);
+
+        XMLUnit.setIgnoreWhitespace(true);
+        XMLUnit.setIgnoreComments(true);
+        XMLUnit.setIgnoreAttributeOrder(true);
+        XMLUnit.setNormalize(true);
+
+        String expectedXMLString = toString(doc.getDocumentElement().getElementsByTagName("root").item(0));
+        String serializationResultXMLString = toString(serializationResult.getNode());
+
+        assertXMLEqual(expectedXMLString, serializationResultXMLString);
+    }
+
+    private ContainerNode createDocNode() {
+        LeafNode<byte[]> ipAddress = ImmutableNodes.leafNode(ipAddressQName, BaseEncoding.base64().decode("fwAAAQ=="));
+        ContainerNode root = ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(rootQName))
+                .withChild(ipAddress).build();
+        return ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(rootQName)).withChild(root)
+                .build();
+    }
+
+    private static DOMResult writeNormalizedNode(final ContainerNode normalized, final SchemaContext context)
+            throws IOException, XMLStreamException {
+        final Document doc = getDocument();
+        final DOMResult result = new DOMResult(doc);
+        NormalizedNodeWriter normalizedNodeWriter = null;
+        NormalizedNodeStreamWriter normalizedNodeStreamWriter = null;
+        XMLStreamWriter writer = null;
+        try {
+            writer = XML_FACTORY.createXMLStreamWriter(result);
+            normalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(writer, context);
+            normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(normalizedNodeStreamWriter);
+
+            for (NormalizedNode<?, ?> child : normalized.getValue()) {
+                normalizedNodeWriter.write(child);
+            }
+
+            normalizedNodeWriter.flush();
+        } finally {
+            if (normalizedNodeWriter != null) {
+                normalizedNodeWriter.close();
+            }
+            if (normalizedNodeStreamWriter != null) {
+                normalizedNodeStreamWriter.close();
+            }
+            if (writer != null) {
+                writer.close();
+            }
+        }
+
+        return result;
+    }
+
+    private static Document loadDocument(final String xmlPath) throws IOException, SAXException {
+        final InputStream resourceAsStream = Bug5446Test.class.getResourceAsStream(xmlPath);
+        final Document currentConfigElement = readXmlToDocument(resourceAsStream);
+        Preconditions.checkNotNull(currentConfigElement);
+        return currentConfigElement;
+    }
+
+    private static Document readXmlToDocument(final InputStream xmlContent) throws IOException, SAXException {
+        final DocumentBuilder dBuilder;
+        try {
+            dBuilder = BUILDERFACTORY.newDocumentBuilder();
+        } catch (final ParserConfigurationException e) {
+            throw new RuntimeException("Failed to parse XML document", e);
+        }
+        final Document doc = dBuilder.parse(xmlContent);
+
+        doc.getDocumentElement().normalize();
+        return doc;
+    }
+
+    private static String toString(final Node xml) {
+        try {
+            final Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+
+            final StreamResult result = new StreamResult(new StringWriter());
+            final DOMSource source = new DOMSource(xml);
+            transformer.transform(source, result);
+
+            return result.getWriter().toString();
+        } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) {
+            throw new RuntimeException("Unable to serialize xml element " + xml, e);
+        }
+    }
+
+    private static Document getDocument() {
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        Document doc = null;
+        try {
+            DocumentBuilder bob = dbf.newDocumentBuilder();
+            doc = bob.newDocument();
+        } catch (ParserConfigurationException e) {
+            throw new RuntimeException(e);
+        }
+        return doc;
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/NormalizedNodeXmlTranslationTest.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/NormalizedNodeXmlTranslationTest.java
new file mode 100644 (file)
index 0000000..07ca944
--- /dev/null
@@ -0,0 +1,428 @@
+/*
+ * 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.codec.xml;
+
+import static org.junit.Assert.assertNotNull;
+import static org.opendaylight.yangtools.yang.data.impl.schema.Builders.augmentationBuilder;
+import static org.opendaylight.yangtools.yang.data.impl.schema.Builders.choiceBuilder;
+import static org.opendaylight.yangtools.yang.data.impl.schema.Builders.containerBuilder;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.leafNode;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.net.URI;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.custommonkey.xmlunit.Diff;
+import org.custommonkey.xmlunit.ElementNameAndTextQualifier;
+import org.custommonkey.xmlunit.IgnoreTextAndAttributeValuesDifferenceListener;
+import org.custommonkey.xmlunit.XMLUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+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.LeafNode;
+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.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
+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.Module;
+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.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+@RunWith(Parameterized.class)
+public class NormalizedNodeXmlTranslationTest {
+    private final SchemaContext schema;
+
+    @Parameterized.Parameters()
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {
+                { "/schema/augment_choice_hell.yang", "/schema/augment_choice_hell_ok.xml", augmentChoiceHell() },
+                { "/schema/augment_choice_hell.yang", "/schema/augment_choice_hell_ok2.xml", null },
+                { "/schema/augment_choice_hell.yang", "/schema/augment_choice_hell_ok3.xml", augmentChoiceHell2() },
+                { "/schema/test.yang", "/schema/simple.xml", null },
+                { "/schema/test.yang", "/schema/simple2.xml", null },
+                // TODO check attributes
+                { "/schema/test.yang", "/schema/simple_xml_with_attributes.xml", withAttributes() }
+        });
+    }
+
+    private static final String NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:test";
+    private static final Date revision;
+    static {
+        try {
+            revision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-03-13");
+        } catch (final ParseException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static ContainerNode augmentChoiceHell2() {
+        final YangInstanceIdentifier.NodeIdentifier container = getNodeIdentifier("container");
+        final QName augmentChoice1QName = QName.create(container.getNodeType(), "augment-choice1");
+        final QName augmentChoice2QName = QName.create(augmentChoice1QName, "augment-choice2");
+        final QName containerQName = QName.create(augmentChoice1QName, "case11-choice-case-container");
+        final QName leafQName = QName.create(augmentChoice1QName, "case11-choice-case-leaf");
+
+        final YangInstanceIdentifier.AugmentationIdentifier aug1Id = new YangInstanceIdentifier.AugmentationIdentifier(
+                Sets.newHashSet(augmentChoice1QName));
+        final YangInstanceIdentifier.AugmentationIdentifier aug2Id = new YangInstanceIdentifier.AugmentationIdentifier(
+                Sets.newHashSet(augmentChoice2QName));
+        final YangInstanceIdentifier.NodeIdentifier augmentChoice1Id = new YangInstanceIdentifier.NodeIdentifier(
+                augmentChoice1QName);
+        final YangInstanceIdentifier.NodeIdentifier augmentChoice2Id = new YangInstanceIdentifier.NodeIdentifier(
+                augmentChoice2QName);
+        final YangInstanceIdentifier.NodeIdentifier containerId = new YangInstanceIdentifier.NodeIdentifier(
+                containerQName);
+
+        return containerBuilder().withNodeIdentifier(container)
+                .withChild(augmentationBuilder().withNodeIdentifier(aug1Id)
+                        .withChild(choiceBuilder().withNodeIdentifier(augmentChoice1Id)
+                                .withChild(augmentationBuilder().withNodeIdentifier(aug2Id)
+                                        .withChild(choiceBuilder().withNodeIdentifier(augmentChoice2Id)
+                                                .withChild(containerBuilder().withNodeIdentifier(containerId)
+                                                        .withChild(leafNode(leafQName, "leaf-value"))
+                                                        .build())
+                                                .build())
+                                        .build())
+                                .build())
+                        .build()).build();
+    }
+
+    private static ContainerNode withAttributes() {
+        final DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> b = containerBuilder();
+        b.withNodeIdentifier(getNodeIdentifier("container"));
+
+        final CollectionNodeBuilder<MapEntryNode, MapNode> listBuilder = Builders.mapBuilder().withNodeIdentifier(
+                getNodeIdentifier("list"));
+
+        final Map<QName, Object> predicates = Maps.newHashMap();
+        predicates.put(getNodeIdentifier("uint32InList").getNodeType(), 3L);
+
+        final DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifierWithPredicates, MapEntryNode> list1Builder = Builders
+                .mapEntryBuilder().withNodeIdentifier(
+                        new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+                                getNodeIdentifier("list").getNodeType(), predicates));
+        final NormalizedNodeBuilder<YangInstanceIdentifier.NodeIdentifier, Object, LeafNode<Object>> uint32InListBuilder = Builders
+                .leafBuilder().withNodeIdentifier(getNodeIdentifier("uint32InList"));
+
+        list1Builder.withChild(uint32InListBuilder.withValue(3L).build());
+
+        listBuilder.withChild(list1Builder.build());
+        b.withChild(listBuilder.build());
+
+        final NormalizedNodeBuilder<YangInstanceIdentifier.NodeIdentifier, Object, LeafNode<Object>> booleanBuilder = Builders
+                .leafBuilder().withNodeIdentifier(getNodeIdentifier("boolean"));
+        booleanBuilder.withValue(false);
+        b.withChild(booleanBuilder.build());
+
+        final ListNodeBuilder<Object, LeafSetEntryNode<Object>> leafListBuilder = Builders.leafSetBuilder()
+                .withNodeIdentifier(getNodeIdentifier("leafList"));
+
+        final NormalizedNodeBuilder<YangInstanceIdentifier.NodeWithValue, Object, LeafSetEntryNode<Object>> leafList1Builder = Builders
+                .leafSetEntryBuilder().withNodeIdentifier(
+                        new YangInstanceIdentifier.NodeWithValue(getNodeIdentifier("leafList").getNodeType(), "a"));
+
+        leafList1Builder.withValue("a");
+
+        leafListBuilder.withChild(leafList1Builder.build());
+        b.withChild(leafListBuilder.build());
+
+        return b.build();
+    }
+
+    private static ContainerNode augmentChoiceHell() {
+
+        final DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> b = containerBuilder();
+        b.withNodeIdentifier(getNodeIdentifier("container"));
+
+        b.withChild(choiceBuilder()
+                .withNodeIdentifier(getNodeIdentifier("ch2"))
+                .withChild(
+                        Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("c2Leaf")).withValue("2").build())
+                .withChild(
+                        choiceBuilder()
+                                .withNodeIdentifier(getNodeIdentifier("c2DeepChoice"))
+                                .withChild(
+                                        Builders.leafBuilder()
+                                                .withNodeIdentifier(getNodeIdentifier("c2DeepChoiceCase1Leaf2"))
+                                                .withValue("2").build()).build()).build());
+
+        b.withChild(choiceBuilder()
+                .withNodeIdentifier(getNodeIdentifier("ch3"))
+                .withChild(
+                        Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("c3Leaf")).withValue("3").build())
+                .build());
+
+        b.withChild(augmentationBuilder()
+                .withNodeIdentifier(getAugmentIdentifier("augLeaf"))
+                .withChild(
+                        Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("augLeaf")).withValue("augment")
+                                .build()).build());
+
+        b.withChild(augmentationBuilder()
+                .withNodeIdentifier(getAugmentIdentifier("ch"))
+                .withChild(
+                        choiceBuilder()
+                                .withNodeIdentifier(getNodeIdentifier("ch"))
+                                .withChild(
+                                        Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("c1Leaf"))
+                                                .withValue("1").build())
+                                .withChild(
+                                        augmentationBuilder()
+                                                .withNodeIdentifier(
+                                                        getAugmentIdentifier("c1Leaf_AnotherAugment", "deepChoice"))
+                                                .withChild(
+                                                        Builders.leafBuilder()
+                                                                .withNodeIdentifier(
+                                                                        getNodeIdentifier("c1Leaf_AnotherAugment"))
+                                                                .withValue("1").build())
+                                                .withChild(
+                                                        choiceBuilder()
+                                                                .withNodeIdentifier(getNodeIdentifier("deepChoice"))
+                                                                .withChild(
+                                                                        Builders.leafBuilder()
+                                                                                .withNodeIdentifier(
+                                                                                        getNodeIdentifier("deepLeafc1"))
+                                                                                .withValue("1").build()).build())
+                                                .build()).build()).build());
+
+        return b.build();
+    }
+
+    private static YangInstanceIdentifier.NodeIdentifier getNodeIdentifier(final String localName) {
+        return new YangInstanceIdentifier.NodeIdentifier(QName.create(URI.create(NAMESPACE), revision, localName));
+    }
+
+    private static YangInstanceIdentifier.AugmentationIdentifier getAugmentIdentifier(final String... childNames) {
+        final Set<QName> qn = Sets.newHashSet();
+
+        for (final String childName : childNames) {
+            qn.add(getNodeIdentifier(childName).getNodeType());
+        }
+
+        return new YangInstanceIdentifier.AugmentationIdentifier(qn);
+    }
+
+    public NormalizedNodeXmlTranslationTest(final String yangPath, final String xmlPath,
+                                            final ContainerNode expectedNode) throws ReactorException {
+        this.schema = parseTestSchema(yangPath);
+        this.xmlPath = xmlPath;
+        this.containerNode = (ContainerSchemaNode) getSchemaNode(schema, "test", "container");
+        this.expectedNode = expectedNode;
+    }
+
+    private final ContainerNode expectedNode;
+    private final ContainerSchemaNode containerNode;
+    private final String xmlPath;
+
+    private SchemaContext parseTestSchema(final String yangPath) throws ReactorException {
+        final CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+        reactor.addSource(new YangStatementSourceImpl(yangPath, false));
+        return reactor.buildEffective();
+    }
+
+    @Test
+    public void testTranslation() throws Exception {
+        final InputStream resourceAsStream = XmlToNormalizedNodesTest.class.getResourceAsStream(xmlPath);
+
+        final XMLInputFactory factory = XMLInputFactory.newInstance();
+        final XMLStreamReader reader = factory.createXMLStreamReader(resourceAsStream);
+
+        final NormalizedNodeResult result = new NormalizedNodeResult();
+        final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+
+        final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, schema);
+        xmlParser.parse(reader);
+
+        final NormalizedNode<?, ?> built = result.getResult();
+        assertNotNull(built);
+
+        if (expectedNode != null) {
+            org.junit.Assert.assertEquals(expectedNode, built);
+        }
+
+        final Document document = XmlDocumentUtils.getDocument();
+        final DOMResult domResult = new DOMResult(document);
+
+        final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
+        outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+
+        final XMLStreamWriter xmlStreamWriter = outputFactory.createXMLStreamWriter(domResult);
+
+        final NormalizedNodeStreamWriter xmlNormalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter
+                .create(xmlStreamWriter, schema);
+
+        final NormalizedNodeWriter normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter
+                (xmlNormalizedNodeStreamWriter);
+
+        normalizedNodeWriter.write(built);
+
+        final Document doc = loadDocument(xmlPath);
+
+        XMLUnit.setIgnoreWhitespace(true);
+        XMLUnit.setIgnoreComments(true);
+        XMLUnit.setIgnoreAttributeOrder(true);
+        XMLUnit.setNormalize(true);
+
+        final String expectedXml = toString(doc.getDocumentElement().getElementsByTagName("container").item(0));
+        final String serializedXml = toString(domResult.getNode());
+
+        final Diff diff = new Diff(expectedXml, serializedXml);
+        diff.overrideDifferenceListener(new IgnoreTextAndAttributeValuesDifferenceListener());
+        diff.overrideElementQualifier(new ElementNameAndTextQualifier());
+
+        // FIXME the comparison cannot be performed, since the qualifiers supplied by XMlUnit do not work correctly in
+        // this case
+        // We need to implement custom qualifier so that the element ordering does not mess the DIFF
+        // dd.overrideElementQualifier(new MultiLevelElementNameAndTextQualifier(100, true));
+        // assertTrue(dd.toString(), dd.similar());
+
+        //new XMLTestCase() {}.assertXMLEqual(diff, true);
+    }
+
+    static final XMLOutputFactory XML_FACTORY;
+    static {
+        XML_FACTORY = XMLOutputFactory.newFactory();
+        XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, false);
+    }
+
+    private static Document loadDocument(final String xmlPath) throws IOException, SAXException {
+        final InputStream resourceAsStream = NormalizedNodeXmlTranslationTest.class.getResourceAsStream(xmlPath);
+
+        final Document currentConfigElement = readXmlToDocument(resourceAsStream);
+        Preconditions.checkNotNull(currentConfigElement);
+        return currentConfigElement;
+    }
+
+    private static final DocumentBuilderFactory BUILDERFACTORY;
+
+    static {
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        factory.setCoalescing(true);
+        factory.setIgnoringElementContentWhitespace(true);
+        factory.setIgnoringComments(true);
+        BUILDERFACTORY = factory;
+    }
+
+    private static Document readXmlToDocument(final InputStream xmlContent) throws IOException, SAXException {
+        final DocumentBuilder dBuilder;
+        try {
+            dBuilder = BUILDERFACTORY.newDocumentBuilder();
+        } catch (final ParserConfigurationException e) {
+            throw new RuntimeException("Failed to parse XML document", e);
+        }
+        final Document doc = dBuilder.parse(xmlContent);
+
+        doc.getDocumentElement().normalize();
+        return doc;
+    }
+
+    private static String toString(final Node xml) {
+        try {
+            final Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+
+            final StreamResult result = new StreamResult(new StringWriter());
+            final DOMSource source = new DOMSource(xml);
+            transformer.transform(source, result);
+
+            return result.getWriter().toString();
+        } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) {
+            throw new RuntimeException("Unable to serialize xml element " + xml, e);
+        }
+    }
+
+    private static DataSchemaNode getSchemaNode(final SchemaContext context, final String moduleName,
+                                               final String childNodeName) {
+        for (Module module : context.getModules()) {
+            if (module.getName().equals(moduleName)) {
+                DataSchemaNode found = findChildNode(module.getChildNodes(), childNodeName);
+                Preconditions.checkState(found != null, "Unable to find %s", childNodeName);
+                return found;
+            }
+        }
+        throw new IllegalStateException("Unable to find child node " + childNodeName);
+    }
+
+    private static DataSchemaNode findChildNode(final Iterable<DataSchemaNode> children, final String name) {
+        List<DataNodeContainer> containers = Lists.newArrayList();
+
+        for (DataSchemaNode dataSchemaNode : children) {
+            if (dataSchemaNode.getQName().getLocalName().equals(name)) {
+                return dataSchemaNode;
+            }
+            if (dataSchemaNode instanceof DataNodeContainer) {
+                containers.add((DataNodeContainer) dataSchemaNode);
+            } else if (dataSchemaNode instanceof ChoiceSchemaNode) {
+                containers.addAll(((ChoiceSchemaNode) dataSchemaNode).getCases());
+            }
+        }
+
+        for (DataNodeContainer container : containers) {
+            DataSchemaNode retVal = findChildNode(container.getChildNodes(), name);
+            if (retVal != null) {
+                return retVal;
+            }
+        }
+
+        return null;
+    }
+
+}
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/NormalizedNodesToXmlTest.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/NormalizedNodesToXmlTest.java
new file mode 100644 (file)
index 0000000..1e0e68d
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * 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.codec.xml;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.custommonkey.xmlunit.Diff;
+import org.custommonkey.xmlunit.DifferenceListener;
+import org.custommonkey.xmlunit.IgnoreTextAndAttributeValuesDifferenceListener;
+import org.custommonkey.xmlunit.XMLTestCase;
+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.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+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.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+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.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+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.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+public class NormalizedNodesToXmlTest {
+
+    private QNameModule bazModule;
+
+    private QName outerContainer;
+
+    private QName myContainer1;
+    private QName myKeyedList;
+    private QName myKeyLeaf;
+    private QName myLeafInList1;
+    private QName myLeafInList2;
+    private QName myLeaf1;
+    private QName myLeafList;
+
+    private QName myContainer2;
+    private QName innerContainer;
+    private QName myLeaf2;
+    private QName myLeaf3;
+    private QName myChoice;
+    private QName myLeafInCase2;
+
+    private QName myContainer3;
+    private QName myDoublyKeyedList;
+    private QName myFirstKeyLeaf;
+    private QName mySecondKeyLeaf;
+    private QName myLeafInList3;
+
+    @Before
+    public void setup() throws URISyntaxException, ParseException {
+        bazModule = QNameModule.create(new URI("baz-namespace"), SimpleDateFormatUtil.getRevisionFormat()
+                .parse("1970-01-01"));
+
+        outerContainer = QName.create(bazModule, "outer-container");
+
+        myContainer1 = QName.create(bazModule, "my-container-1");
+        myKeyedList = QName.create(bazModule, "my-keyed-list");
+        myKeyLeaf = QName.create(bazModule, "my-key-leaf");
+        myLeafInList1 = QName.create(bazModule, "my-leaf-in-list-1");
+        myLeafInList2 = QName.create(bazModule, "my-leaf-in-list-2");
+        myLeaf1 = QName.create(bazModule, "my-leaf-1");
+        myLeafList = QName.create(bazModule, "my-leaf-list");
+
+        myContainer2 = QName.create(bazModule, "my-container-2");
+        innerContainer = QName.create(bazModule, "inner-container");
+        myLeaf2 = QName.create(bazModule, "my-leaf-2");
+        myLeaf3 = QName.create(bazModule, "my-leaf-3");
+        myChoice = QName.create(bazModule, "my-choice");
+        myLeafInCase2 = QName.create(bazModule, "my-leaf-in-case-2");
+
+        myContainer3 = QName.create(bazModule, "my-container-3");
+        myDoublyKeyedList = QName.create(bazModule, "my-doubly-keyed-list");
+        myFirstKeyLeaf = QName.create(bazModule, "my-first-key-leaf");
+        mySecondKeyLeaf = QName.create(bazModule, "my-second-key-leaf");
+        myLeafInList3 = QName.create(bazModule, "my-leaf-in-list-3");
+    }
+
+    @Test
+    public void testNormalizedNodeToXmlSerialization() throws ReactorException, XMLStreamException, IOException,
+            SAXException {
+        final CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+        reactor.addSource(new YangStatementSourceImpl("/baz.yang", false));
+
+        final SchemaContext schemaContext = reactor.buildEffective();
+
+        final Document doc = loadDocument("/baz.xml");
+
+        final Document document = getDocument();
+        final DOMResult domResult = new DOMResult(document);
+
+        final XMLOutputFactory factory = XMLOutputFactory.newInstance();
+        factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+
+        final XMLStreamWriter xmlStreamWriter = factory.createXMLStreamWriter(domResult);
+
+        final NormalizedNodeStreamWriter xmlNormalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter.create
+                (xmlStreamWriter, schemaContext);
+
+        final NormalizedNodeWriter normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter
+                (xmlNormalizedNodeStreamWriter);
+
+        normalizedNodeWriter.write(buildOuterContainerNode());
+
+        XMLUnit.setIgnoreWhitespace(true);
+        XMLUnit.setNormalize(true);
+
+        final String expectedXml = toString(doc.getDocumentElement().getElementsByTagName("outer-container").item(0));
+        final String serializedXml = toString(domResult.getNode());
+        final Diff diff = new Diff(expectedXml, serializedXml);
+
+        final DifferenceListener differenceListener = new IgnoreTextAndAttributeValuesDifferenceListener();
+        diff.overrideDifferenceListener(differenceListener);
+
+        new XMLTestCase() {}.assertXMLEqual(diff, true);
+    }
+
+    private NormalizedNode<?, ?> buildOuterContainerNode() {
+        // my-container-1
+        MapNode myKeyedListNode = Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(myKeyedList))
+                .withChild(Builders.mapEntryBuilder().withNodeIdentifier(
+                        new NodeIdentifierWithPredicates(myKeyedList, myKeyLeaf, "listkeyvalue1"))
+                        .withChild(Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(myLeafInList1))
+                                .withValue("listleafvalue1").build())
+                        .withChild(Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(myLeafInList2))
+                                .withValue("listleafvalue2").build()).build())
+                .withChild(Builders.mapEntryBuilder().withNodeIdentifier(
+                        new NodeIdentifierWithPredicates(myKeyedList, myKeyLeaf, "listkeyvalue2"))
+                        .withChild(Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(myLeafInList1))
+                                .withValue("listleafvalue12").build())
+                        .withChild(Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(myLeafInList2))
+                                .withValue("listleafvalue22").build()).build()).build();
+
+        LeafNode<?> myLeaf1Node = Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(myLeaf1))
+                .withValue("value1").build();
+
+        LeafSetNode<?> myLeafListNode = Builders.leafSetBuilder().withNodeIdentifier(new NodeIdentifier(myLeafList))
+                .withChild(Builders.leafSetEntryBuilder().withNodeIdentifier(
+                        new NodeWithValue<>(myLeafList, "lflvalue1")).withValue("lflvalue1").build())
+                .withChild(Builders.leafSetEntryBuilder().withNodeIdentifier(
+                        new NodeWithValue<>(myLeafList, "lflvalue2")).withValue("lflvalue2").build()).build();
+
+        ContainerNode myContainer1Node = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(myContainer1))
+                .withChild(myKeyedListNode)
+                .withChild(myLeaf1Node)
+                .withChild(myLeafListNode).build();
+
+        // my-container-2
+        ContainerNode innerContainerNode = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(innerContainer))
+                .withChild(Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(myLeaf2))
+                        .withValue("value2").build()).build();
+
+        LeafNode<?> myLeaf3Node = Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(myLeaf3))
+                .withValue("value3").build();
+
+        ChoiceNode myChoiceNode = Builders.choiceBuilder().withNodeIdentifier(new NodeIdentifier(myChoice))
+                .withChild(Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(myLeafInCase2))
+                        .withValue("case2value").build()).build();
+
+        ContainerNode myContainer2Node = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(myContainer2))
+                .withChild(innerContainerNode)
+                .withChild(myLeaf3Node)
+                .withChild(myChoiceNode).build();
+
+        // my-container-3
+        Map<QName, Object> keys = new HashMap<>();
+        keys.put(myFirstKeyLeaf, "listkeyvalue1");
+        keys.put(mySecondKeyLeaf, "listkeyvalue2");
+
+        MapNode myDoublyKeyedListNode = Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(myDoublyKeyedList))
+                .withChild(Builders.mapEntryBuilder().withNodeIdentifier(
+                        new NodeIdentifierWithPredicates(myDoublyKeyedList, keys))
+                        .withChild(Builders.leafBuilder().withNodeIdentifier(
+                                new NodeIdentifier(myLeafInList3)).withValue("listleafvalue1").build()).build())
+                .build();
+
+        AugmentationNode myDoublyKeyedListAugNode = Builders.augmentationBuilder().withNodeIdentifier(
+                new AugmentationIdentifier(Sets.newHashSet(myDoublyKeyedList)))
+                .withChild(myDoublyKeyedListNode).build();
+
+        ContainerNode myContainer3Node = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(myContainer3))
+                .withChild(myDoublyKeyedListAugNode).build();
+
+        AugmentationNode myContainer3AugNode = Builders.augmentationBuilder().withNodeIdentifier(
+                new AugmentationIdentifier(Sets.newHashSet(myContainer3)))
+                .withChild(myContainer3Node).build();
+
+        ContainerNode outerContainerNode = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(outerContainer))
+                .withChild(myContainer1Node)
+                .withChild(myContainer2Node)
+                .withChild(myContainer3AugNode).build();
+
+        return outerContainerNode;
+    }
+
+    private static Document loadDocument(final String xmlPath) throws IOException, SAXException {
+        final InputStream resourceAsStream = NormalizedNodesToXmlTest.class.getResourceAsStream(xmlPath);
+        final Document currentConfigElement = readXmlToDocument(resourceAsStream);
+        Preconditions.checkNotNull(currentConfigElement);
+        return currentConfigElement;
+    }
+
+    private static Document readXmlToDocument(final InputStream xmlContent) throws IOException, SAXException {
+        final DocumentBuilder dBuilder;
+        try {
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            factory.setNamespaceAware(true);
+            factory.setCoalescing(true);
+            factory.setIgnoringElementContentWhitespace(true);
+            factory.setIgnoringComments(true);
+            dBuilder = factory.newDocumentBuilder();
+        } catch (final ParserConfigurationException e) {
+            throw new RuntimeException("Failed to parse XML document", e);
+        }
+        final Document doc = dBuilder.parse(xmlContent);
+
+        doc.getDocumentElement().normalize();
+        return doc;
+    }
+
+    private static String toString(final Node xml) {
+        try {
+            final Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+
+            final StreamResult result = new StreamResult(new StringWriter());
+            final DOMSource source = new DOMSource(xml);
+            transformer.transform(source, result);
+
+            return result.getWriter().toString();
+        } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) {
+            throw new RuntimeException("Unable to serialize xml element " + xml, e);
+        }
+    }
+
+    private static Document getDocument() {
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        Document doc = null;
+        try {
+            DocumentBuilder bob = dbf.newDocumentBuilder();
+            doc = bob.newDocument();
+        } catch (ParserConfigurationException e) {
+            throw new RuntimeException(e);
+        }
+        return doc;
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefixTest.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefixTest.java
new file mode 100644 (file)
index 0000000..81505b8
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014 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.codec.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+
+public class RandomPrefixTest {
+    static final int MAX_COUNTER = 4000;
+
+    @Test
+    public void testEncodeDecode() throws Exception {
+
+        final List<String> allGenerated = Lists.newArrayList();
+        for (int i = 0; i < MAX_COUNTER; i++) {
+            final String encoded = RandomPrefix.encode(i);
+            assertEquals(RandomPrefix.decode(encoded), i);
+            allGenerated.add(encoded);
+        }
+
+        assertEquals(allGenerated.size(), MAX_COUNTER);
+        assertEquals("dPT", allGenerated.get(MAX_COUNTER - 1));
+        assertEquals("a", allGenerated.get(0));
+        assertEquals(allGenerated.size(), Sets.newHashSet(allGenerated).size());
+    }
+
+    @Test
+    public void testQNameWithPrefix() throws Exception {
+        final RandomPrefix a = new RandomPrefix();
+
+        final List<String> allGenerated = Lists.newArrayList();
+        for (int i = 0; i < MAX_COUNTER; i++) {
+            final String prefix = RandomPrefix.encode(i);
+            final URI uri = new URI("localhost:" + prefix);
+            final QName qName = QName.create(QNameModule.create(uri, new Date()), "local-name");
+            allGenerated.add(a.encodePrefix(qName.getNamespace()));
+        }
+
+        assertEquals(MAX_COUNTER, allGenerated.size());
+        // We are generating MAX_COUNTER_VALUE + 27 prefixes total, so we should encounter a reset in prefix a start from 0 at some point
+        // At the end, there should be only 27 values in RandomPrefix cache
+        assertEquals(MAX_COUNTER, Iterables.size(a.getPrefixes()));
+        assertThat(allGenerated, CoreMatchers.not(CoreMatchers.hasItem("xml")));
+        assertThat(allGenerated, CoreMatchers.not(CoreMatchers.hasItem("xmla")));
+        assertThat(allGenerated, CoreMatchers.not(CoreMatchers.hasItem("xmlz")));
+
+        assertEquals(1, Iterables.frequency(allGenerated, "a"));
+    }
+
+    @Test
+    public void test2QNames1Namespace() throws Exception {
+        final RandomPrefix a = new RandomPrefix();
+
+        final URI uri = URI.create("localhost");
+        final QName qName = QName.create(QNameModule.create(uri, new Date()), "local-name");
+        final QName qName2 = QName.create(QNameModule.create(uri, new Date()), "local-name");
+
+        assertEquals(a.encodePrefix(qName.getNamespace()), a.encodePrefix(qName2.getNamespace()));
+    }
+
+    @Test
+    public void testQNameNoPrefix() throws Exception {
+        final RandomPrefix a = new RandomPrefix();
+
+        final URI uri = URI.create("localhost");
+        QName qName = QName.create(uri, new Date(), "local-name");
+        assertEquals("a", a.encodePrefix(qName.getNamespace()));
+        qName = QName.create(QNameModule.create(uri, new Date()), "local-name");
+        assertEquals("a", a.encodePrefix(qName.getNamespace()));
+        qName = QName.create(QNameModule.create(URI.create("second"), new Date()), "local-name");
+        assertEquals("b", a.encodePrefix(qName.getNamespace()));
+
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlStreamUtilsTest.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlStreamUtilsTest.java
new file mode 100644 (file)
index 0000000..3f04e98
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * 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.codec.xml;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Maps;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.AbstractMap;
+import java.util.Date;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamWriter;
+import org.custommonkey.xmlunit.Diff;
+import org.custommonkey.xmlunit.XMLUnit;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+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.w3c.dom.Document;
+
+public class XmlStreamUtilsTest {
+
+    public static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
+
+    private static SchemaContext schemaContext;
+    private static Module leafRefModule;
+
+    @BeforeClass
+    public static void initialize() throws URISyntaxException, FileNotFoundException, ReactorException {
+        final CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+        reactor.addSource(new YangStatementSourceImpl("/leafref-test.yang", false));
+
+        schemaContext = reactor.buildEffective();
+        assertNotNull(schemaContext);
+        assertEquals(1, schemaContext.getModules().size());
+        leafRefModule = schemaContext.getModules().iterator().next();
+        assertNotNull(leafRefModule);
+    }
+
+    @Test
+    public void testWriteAttribute() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        final XMLStreamWriter writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
+        writer.writeStartElement("element");
+
+        QName name = getAttrQName("namespace", "2012-12-12", "attr", Optional.of("prefix"));
+        final Map.Entry<QName, String> attributeEntry = new AbstractMap.SimpleEntry<>(name, "value");
+
+        name = getAttrQName("namespace2", "2012-12-12", "attr", Optional.absent());
+        final Map.Entry<QName, String> attributeEntryNoPrefix = new AbstractMap.SimpleEntry<>(name, "value");
+
+        final RandomPrefix randomPrefix = new RandomPrefix();
+        XmlStreamUtils.writeAttribute(writer, attributeEntry, randomPrefix);
+        XmlStreamUtils.writeAttribute(writer, attributeEntryNoPrefix, randomPrefix);
+
+        writer.writeEndElement();
+        writer.close();
+        out.close();
+
+        final String xmlAsString = new String(out.toByteArray());
+
+        final Map<String, String> mappedPrefixes = mapPrefixed(randomPrefix.getPrefixes());
+        assertEquals(2, mappedPrefixes.size());
+        final String randomPrefixValue = mappedPrefixes.get("namespace2");
+
+        final String expectedXmlAsString = "<element xmlns:a=\"namespace\" a:attr=\"value\" xmlns:" + randomPrefixValue
+                + "=\"namespace2\" " + randomPrefixValue + ":attr=\"value\"></element>";
+
+        XMLUnit.setIgnoreAttributeOrder(true);
+        final Document control = XMLUnit.buildControlDocument(expectedXmlAsString);
+        final Document test = XMLUnit.buildTestDocument(xmlAsString);
+        final Diff diff = XMLUnit.compareXML(control, test);
+
+        final boolean identical = diff.identical();
+        assertTrue("Xml differs: " + diff.toString(), identical);
+    }
+
+    @Test
+    public void testWriteIdentityRef() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        final XMLStreamWriter writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
+
+        writer.writeStartElement("element");
+        final QNameModule parent = QNameModule.create(URI.create("parent:uri"), new Date());
+        XmlStreamUtils.write(writer, null, QName.create(parent, "identity"), Optional.of(parent));
+        writer.writeEndElement();
+
+        writer.writeStartElement("elementDifferent");
+        XmlStreamUtils.write(writer, null, QName.create("different:namespace", "identity"), Optional.of(parent));
+        writer.writeEndElement();
+
+        writer.close();
+        out.close();
+
+        final String xmlAsString = new String(out.toByteArray()).replaceAll("\\s*", "");
+        assertThat(xmlAsString, containsString("element>identity"));
+
+        final Pattern prefixedIdentityPattern = Pattern.compile(".*\"different:namespace\">(.*):identity.*");
+        final Matcher matcher = prefixedIdentityPattern.matcher(xmlAsString);
+        assertTrue("Xml: " + xmlAsString + " should match: " + prefixedIdentityPattern, matcher.matches());
+    }
+
+    /**
+     * One leafref reference to other leafref via relative references
+     */
+    @Test
+    public void testLeafRefRelativeChaining() {
+        getTargetNodeForLeafRef("leafname3", StringTypeDefinition.class);
+    }
+
+    @Test
+    public void testLeafRefRelative() {
+        getTargetNodeForLeafRef("pointToStringLeaf", StringTypeDefinition.class);
+    }
+
+    @Test
+    public void testLeafRefAbsoluteWithSameTarget() {
+        getTargetNodeForLeafRef("absname", InstanceIdentifierTypeDefinition.class);
+    }
+
+    /**
+     * Tests relative path with double point inside path (e. g. "../../lf:interface/../lf:cont2/lf:stringleaf")
+     */
+    // ignored because this isn't implemented
+    @Ignore
+    @Test
+    public void testLeafRefWithDoublePointInPath() {
+        getTargetNodeForLeafRef("lf-with-double-point-inside", StringTypeDefinition.class);
+    }
+
+    @Test
+    public void testLeafRefRelativeAndAbsoluteWithSameTarget() {
+        final TypeDefinition<?> targetNodeForAbsname = getTargetNodeForLeafRef("absname",
+            InstanceIdentifierTypeDefinition.class);
+        final TypeDefinition<?> targetNodeForRelname = getTargetNodeForLeafRef("relname",
+            InstanceIdentifierTypeDefinition.class);
+        assertEquals(targetNodeForAbsname, targetNodeForRelname);
+    }
+
+    private TypeDefinition<?> getTargetNodeForLeafRef(final String nodeName, final Class<?> clas) {
+        final LeafSchemaNode schemaNode = findSchemaNodeWithLeafrefType(leafRefModule, nodeName);
+        assertNotNull(schemaNode);
+        final LeafrefTypeDefinition leafrefTypedef = findLeafrefType(schemaNode);
+        assertNotNull(leafrefTypedef);
+        final TypeDefinition<?> targetBaseType = SchemaContextUtil.getBaseTypeForLeafRef(leafrefTypedef, schemaContext,
+                schemaNode);
+        assertTrue("Wrong class found.", clas.isInstance(targetBaseType));
+        return targetBaseType;
+    }
+
+    private static Map<String, String> mapPrefixed(final Iterable<Map.Entry<URI, String>> prefixes) {
+        final Map<String, String> mappedPrefixes = Maps.newHashMap();
+        for (final Map.Entry<URI, String> prefix : prefixes) {
+            mappedPrefixes.put(prefix.getKey().toString(), prefix.getValue());
+        }
+        return mappedPrefixes;
+    }
+
+    private static QName getAttrQName(final String namespace, final String revision, final String localName,
+            final Optional<String> prefix) {
+        if (prefix.isPresent()) {
+            final QName moduleQName = QName.create(namespace, revision, "module");
+            final QNameModule module = QNameModule.create(moduleQName.getNamespace(), moduleQName.getRevision());
+            return QName.create(module, localName);
+        } else {
+            return QName.create(namespace, revision, localName);
+        }
+    }
+
+    private LeafSchemaNode findSchemaNodeWithLeafrefType(final DataNodeContainer module, final String nodeName) {
+        for (final DataSchemaNode childNode : module.getChildNodes()) {
+            if (childNode instanceof DataNodeContainer) {
+                LeafSchemaNode leafrefFromRecursion = findSchemaNodeWithLeafrefType((DataNodeContainer) childNode,
+                        nodeName);
+                if (leafrefFromRecursion != null) {
+                    return leafrefFromRecursion;
+                }
+            } else if (childNode.getQName().getLocalName().equals(nodeName) && childNode instanceof LeafSchemaNode) {
+                final TypeDefinition<?> leafSchemaNodeType = ((LeafSchemaNode) childNode).getType();
+                if (leafSchemaNodeType instanceof LeafrefTypeDefinition) {
+                    return (LeafSchemaNode) childNode;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static LeafrefTypeDefinition findLeafrefType(final LeafSchemaNode schemaNode) {
+        final TypeDefinition<?> type = schemaNode.getType();
+        if (type instanceof LeafrefTypeDefinition) {
+            return (LeafrefTypeDefinition) type;
+        }
+        return null;
+    }
+}
index c57228e7a8db3fb8e8db0f7390decf0fdc491f68..30977e39117df63349eaffff5c8e784aac1a8c2f 100644 (file)
@@ -31,9 +31,9 @@ import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 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;
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YangModeledAnyXMLDeserializationTest.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YangModeledAnyXMLDeserializationTest.java
new file mode 100644 (file)
index 0000000..1130885
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * 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.codec.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Optional;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamReader;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.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.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.YangModeledAnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeSchemaAwareBuilder;
+import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.api.Status;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.YangModeledAnyXmlSchemaNode;
+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;
+
+public class YangModeledAnyXMLDeserializationTest {
+
+    private QNameModule fooModuleQName;
+    private QNameModule barModuleQName;
+    private QName myContainer1;
+    private QName myContainer2;
+    private QName innerContainer;
+    private QName myLeaf3;
+    private QName myLeaf2;
+    private QName myLeaf1;
+    private QName myAnyXMLDataBar;
+    private QName myAnyXMLDataFoo;
+    private SchemaContext schemaContext;
+
+    @Before
+    public void Init() throws Exception {
+        barModuleQName = QNameModule.create(new URI("bar"), SimpleDateFormatUtil.getRevisionFormat()
+                .parse("1970-01-01"));
+        myContainer1 = QName.create(barModuleQName, "my-container-1");
+        myLeaf1 = QName.create(barModuleQName, "my-leaf-1");
+        myAnyXMLDataBar = QName.create(barModuleQName, "my-anyxml-data");
+
+        fooModuleQName = QNameModule.create(new URI("foo"), SimpleDateFormatUtil.getRevisionFormat()
+                .parse("1970-01-01"));
+        myContainer2 = QName.create(fooModuleQName, "my-container-2");
+        innerContainer = QName.create(fooModuleQName, "inner-container");
+        myLeaf3 = QName.create(fooModuleQName, "my-leaf-3");
+        myLeaf2 = QName.create(fooModuleQName, "my-leaf-2");
+        myAnyXMLDataFoo = QName.create(fooModuleQName, "my-anyxml-data");
+
+        CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+        reactor.addSources(new YangStatementSourceImpl("/anyxml-support/yang/foo.yang", false),
+                new YangStatementSourceImpl("/anyxml-support/yang/bar.yang", false),
+                new YangStatementSourceImpl("/anyxml-support/yang/yang-ext.yang", false));
+
+        schemaContext = reactor.buildEffective();
+    }
+
+    @Test
+    public void testRawAnyXMLFromBar() throws Exception {
+        DataSchemaNode barContainer = schemaContext.getDataChildByName(QName.create(barModuleQName, "bar"));;
+        assertTrue(barContainer instanceof ContainerSchemaNode);
+        final YangModeledAnyXmlSchemaNode yangModeledAnyXML = new YangModeledAnyXMLSchemaNodeImplTest(myAnyXMLDataBar,
+                (ContainerSchemaNode) barContainer);
+
+        final InputStream resourceAsStream = YangModeledAnyXMLDeserializationTest.class.getResourceAsStream(
+                "/anyxml-support/xml/bar.xml");
+
+        final XMLInputFactory factory = XMLInputFactory.newInstance();
+        final XMLStreamReader reader = factory.createXMLStreamReader(resourceAsStream);
+
+        final ImmutableContainerNodeBuilder result = (ImmutableContainerNodeBuilder) ImmutableContainerNodeSchemaAwareBuilder
+                        .create(yangModeledAnyXML.getSchemaOfAnyXmlData());
+
+        final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+
+        final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, schemaContext, yangModeledAnyXML);
+        xmlParser.parse(reader);
+
+        final NormalizedNode<?, ?> output = result.build();
+
+        Collection<DataContainerChild<? extends PathArgument, ?>> value = ((ContainerNode) output).getValue();
+        assertEquals(2, value.size());
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> child = ((ContainerNode) output)
+                .getChild(new NodeIdentifier(myContainer1));
+        assertTrue(child.orNull() instanceof ContainerNode);
+        ContainerNode myContainerNode1 = (ContainerNode) child.get();
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> child2 = myContainerNode1.getChild(new NodeIdentifier(
+                myLeaf1));
+        assertTrue(child2.orNull() instanceof LeafNode);
+        LeafNode<?> LeafNode1 = (LeafNode<?>) child2.get();
+
+        Object leafNode1Value = LeafNode1.getValue();
+        assertEquals("value1", leafNode1Value);
+    }
+
+    @Test
+    public void testRealSchemaContextFromFoo() throws Exception {
+        final InputStream resourceAsStream = YangModeledAnyXMLDeserializationTest.class.getResourceAsStream(
+                "/anyxml-support/xml/foo.xml");
+
+        final XMLInputFactory factory = XMLInputFactory.newInstance();
+        final XMLStreamReader reader = factory.createXMLStreamReader(resourceAsStream);
+
+        final ImmutableContainerNodeBuilder result = (ImmutableContainerNodeBuilder) ImmutableContainerNodeSchemaAwareBuilder
+                .create(schemaContext);
+
+        final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+
+        final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, schemaContext);
+        xmlParser.parse(reader);
+
+        final NormalizedNode<?, ?> output = result.build();
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> child = ((ContainerNode) output).getChild(
+                new NodeIdentifier(myAnyXMLDataFoo));
+        assertTrue(child.orNull() instanceof YangModeledAnyXmlNode);
+        YangModeledAnyXmlNode yangModeledAnyXmlNode = (YangModeledAnyXmlNode) child.get();
+
+        DataSchemaNode schemaOfAnyXmlData = yangModeledAnyXmlNode.getSchemaOfAnyXmlData();
+        DataSchemaNode expectedSchemaOfAnyXmlData = schemaContext.getDataChildByName(myContainer2);
+        assertEquals(expectedSchemaOfAnyXmlData, schemaOfAnyXmlData);
+
+        Collection<DataContainerChild<? extends PathArgument, ?>> value = yangModeledAnyXmlNode.getValue();
+        assertEquals(2, value.size());
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> child2 = yangModeledAnyXmlNode
+                .getChild(new NodeIdentifier(innerContainer));
+        assertTrue(child2.orNull() instanceof ContainerNode);
+        ContainerNode innerContainerNode = (ContainerNode) child2.get();
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> child3 = innerContainerNode
+                .getChild(new NodeIdentifier(myLeaf2));
+        assertTrue(child3.orNull() instanceof LeafNode);
+        LeafNode<?> LeafNode2 = (LeafNode<?>) child3.get();
+
+        Object leafNode2Value = LeafNode2.getValue();
+        assertEquals("any-xml-leaf-2-value", leafNode2Value);
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> child4 = yangModeledAnyXmlNode
+                .getChild(new NodeIdentifier(myLeaf3));
+        assertTrue(child4.orNull() instanceof LeafNode);
+        LeafNode<?> LeafNode3 = (LeafNode<?>) child4.get();
+
+        Object leafNode3Value = LeafNode3.getValue();
+        assertEquals("any-xml-leaf-3-value", leafNode3Value);
+    }
+
+    private static class YangModeledAnyXMLSchemaNodeImplTest implements YangModeledAnyXmlSchemaNode {
+        private final QName qName;
+        private final ContainerSchemaNode contentSchema;
+
+        private YangModeledAnyXMLSchemaNodeImplTest(QName qName, ContainerSchemaNode contentSchema) {
+            this.qName = qName;
+            this.contentSchema = contentSchema;
+        }
+
+        @Override
+        public boolean isAugmenting() {
+            return false;
+        }
+
+        @Override
+        public boolean isAddedByUses() {
+            return false;
+        }
+
+        @Override
+        public boolean isConfiguration() {
+            return false;
+        }
+
+        @Override
+        public ConstraintDefinition getConstraints() {
+            return null;
+        }
+
+        @Override
+        public QName getQName() {
+            return qName;
+        }
+
+        @Override
+        public SchemaPath getPath() {
+            return null;
+        }
+
+        @Override
+        public List<UnknownSchemaNode> getUnknownSchemaNodes() {
+            return null;
+        }
+
+        @Override
+        public String getDescription() {
+            return null;
+        }
+
+        @Override
+        public String getReference() {
+            return null;
+        }
+
+        @Override
+        public Status getStatus() {
+            return null;
+        }
+
+        @Override
+        public ContainerSchemaNode getSchemaOfAnyXmlData() {
+            return contentSchema;
+        }
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YangModeledAnyXMLSerializationTest.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YangModeledAnyXMLSerializationTest.java
new file mode 100644 (file)
index 0000000..32a43aa
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * 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.codec.xml;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.net.URI;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.custommonkey.xmlunit.XMLTestCase;
+import org.custommonkey.xmlunit.XMLUnit;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+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.YangModeledAnyXmlNode;
+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.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+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.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+public class YangModeledAnyXMLSerializationTest extends XMLTestCase {
+    private static final DocumentBuilderFactory BUILDERFACTORY;
+
+    static {
+        BUILDERFACTORY = DocumentBuilderFactory.newInstance();
+        BUILDERFACTORY.setNamespaceAware(true);
+        BUILDERFACTORY.setCoalescing(true);
+        BUILDERFACTORY.setIgnoringElementContentWhitespace(true);
+        BUILDERFACTORY.setIgnoringComments(true);
+    }
+
+    private QNameModule bazModuleQName;
+    private QName myAnyXMLDataBaz;
+    private QName bazQName;
+    private QName myContainer2QName;
+    private SchemaContext schemaContext;
+
+    public YangModeledAnyXMLSerializationTest() throws Exception {
+        bazModuleQName = QNameModule.create(new URI("baz"), SimpleDateFormatUtil.getRevisionFormat()
+                .parse("1970-01-01"));
+        bazQName = QName.create(bazModuleQName, "baz");
+        myContainer2QName = QName.create(bazModuleQName, "my-container-2");
+        myAnyXMLDataBaz = QName.create(bazModuleQName, "my-anyxml-data");
+
+        CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+        reactor.addSources(new YangStatementSourceImpl("/anyxml-support/serialization/baz.yang", false),
+                new YangStatementSourceImpl("/anyxml-support/serialization/yang-ext.yang", false));
+        schemaContext = reactor.buildEffective();
+    }
+
+    @Test
+    public void testSerializationOfBaz() throws Exception {
+        final InputStream resourceAsStream = XmlToNormalizedNodesTest.class.getResourceAsStream("/anyxml-support/serialization/baz.xml");
+
+        final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
+        final XMLStreamReader reader = inputFactory.createXMLStreamReader(resourceAsStream);
+
+        final NormalizedNodeResult result = new NormalizedNodeResult();
+
+        final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+
+        final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, schemaContext);
+        xmlParser.parse(reader);
+
+        final NormalizedNode<?, ?> transformedInput = result.getResult();
+        assertNotNull(transformedInput);
+
+        assertTrue(transformedInput instanceof ContainerNode);
+        ContainerNode bazContainer = (ContainerNode) transformedInput;
+        assertEquals(bazContainer.getNodeType(), bazQName);
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> bazContainerChild = bazContainer.getChild(
+                new NodeIdentifier(myAnyXMLDataBaz));
+        assertTrue(bazContainerChild.orNull() instanceof YangModeledAnyXmlNode);
+        YangModeledAnyXmlNode yangModeledAnyXmlNode = (YangModeledAnyXmlNode) bazContainerChild.get();
+
+        DataSchemaNode schemaOfAnyXmlData = yangModeledAnyXmlNode.getSchemaOfAnyXmlData();
+        SchemaNode myContainer2SchemaNode = SchemaContextUtil.findDataSchemaNode(schemaContext,
+                SchemaPath.create(true, bazQName, myContainer2QName));
+        assertTrue(myContainer2SchemaNode instanceof ContainerSchemaNode);
+        assertEquals(myContainer2SchemaNode, schemaOfAnyXmlData);
+
+        final Document document = getDocument();
+        final DOMResult domResult = new DOMResult(document);
+
+        final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
+        outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+
+        final XMLStreamWriter xmlStreamWriter = outputFactory.createXMLStreamWriter(domResult);
+
+        final NormalizedNodeStreamWriter xmlNormalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter
+                .create(xmlStreamWriter, schemaContext);
+
+        final NormalizedNodeWriter normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter
+                (xmlNormalizedNodeStreamWriter);
+
+        normalizedNodeWriter.write(transformedInput);
+
+        final Document doc = loadDocument("/anyxml-support/serialization/baz.xml");
+
+        XMLUnit.setIgnoreWhitespace(true);
+        XMLUnit.setIgnoreComments(true);
+        XMLUnit.setIgnoreAttributeOrder(true);
+        XMLUnit.setNormalize(true);
+
+        String expectedXml = toString(doc.getDocumentElement().getElementsByTagName("baz").item(0));
+        String serializedXml = toString(domResult.getNode());
+
+        assertXMLEqual(expectedXml, serializedXml);
+    }
+
+    private static Document loadDocument(final String xmlPath) throws IOException, SAXException {
+        final InputStream resourceAsStream = YangModeledAnyXMLSerializationTest.class.getResourceAsStream(xmlPath);
+        final Document currentConfigElement = readXmlToDocument(resourceAsStream);
+        Preconditions.checkNotNull(currentConfigElement);
+        return currentConfigElement;
+    }
+
+    private static Document readXmlToDocument(final InputStream xmlContent) throws IOException, SAXException {
+        final DocumentBuilder dBuilder;
+        try {
+            dBuilder = BUILDERFACTORY.newDocumentBuilder();
+        } catch (final ParserConfigurationException e) {
+            throw new RuntimeException("Failed to parse XML document", e);
+        }
+        final Document doc = dBuilder.parse(xmlContent);
+
+        doc.getDocumentElement().normalize();
+        return doc;
+    }
+
+    private static String toString(final Node xml) {
+        try {
+            final Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+
+            final StreamResult result = new StreamResult(new StringWriter());
+            final DOMSource source = new DOMSource(xml);
+            transformer.transform(source, result);
+
+            return result.getWriter().toString();
+        } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) {
+            throw new RuntimeException("Unable to serialize xml element " + xml, e);
+        }
+    }
+
+    private static Document getDocument() {
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        Document doc = null;
+        try {
+            DocumentBuilder bob = dbf.newDocumentBuilder();
+            doc = bob.newDocument();
+        } catch (ParserConfigurationException e) {
+            throw new RuntimeException(e);
+        }
+        return doc;
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/anyxml-support/serialization/baz.xml b/yang/yang-data-codec-xml/src/test/resources/anyxml-support/serialization/baz.xml
new file mode 100644 (file)
index 0000000..16029d1
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root xmlns="baz">
+    <baz>
+        <my-container-1>
+            <my-leaf-1>value1</my-leaf-1>
+        </my-container-1>
+        <my-container-2>
+            <inner-container>
+                <my-leaf-2>value2</my-leaf-2>
+            </inner-container>
+            <my-leaf-3>value3</my-leaf-3>
+        </my-container-2>
+
+        <my-anyxml-data>
+            <inner-container>
+                <my-leaf-2>any-xml-leaf-2-value</my-leaf-2>
+            </inner-container>
+            <my-leaf-3>any-xml-leaf-3-value</my-leaf-3>
+        </my-anyxml-data>
+    </baz>
+</root>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/anyxml-support/serialization/baz.yang b/yang/yang-data-codec-xml/src/test/resources/anyxml-support/serialization/baz.yang
new file mode 100644 (file)
index 0000000..9cd2233
--- /dev/null
@@ -0,0 +1,29 @@
+module baz {
+    namespace "baz";
+    prefix baz;
+
+    import yang-ext { prefix ext; revision-date 2013-07-09; }
+
+    container baz {
+        container my-container-1 {
+            leaf my-leaf-1 {
+                type string;
+            }
+        }
+
+        container my-container-2 {
+            container inner-container {
+                leaf my-leaf-2 {
+                    type string;
+                }
+            }
+            leaf my-leaf-3 {
+                type string;
+            }
+        }
+
+        anyxml my-anyxml-data {
+            ext:anyxml-schema-location "/baz/my-container-2";
+        }
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/anyxml-support/serialization/yang-ext.yang b/yang/yang-data-codec-xml/src/test/resources/anyxml-support/serialization/yang-ext.yang
new file mode 100644 (file)
index 0000000..99eb4b6
--- /dev/null
@@ -0,0 +1,75 @@
+module yang-ext {
+    yang-version 1;
+    namespace "urn:opendaylight:yang:extension:yang-ext";
+    prefix "ext";
+
+    contact "Anton Tkacik <ttkacik@cisco.com>";
+
+    description
+        "Copyright (c) 2013 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";
+
+    revision "2013-07-09" {
+        description "";
+    }
+
+    // Augmentation name
+
+    extension "augment-identifier" {
+        description
+                "YANG language extension which assigns an identifier to augmentation.
+                Augment identifier is used to identify specific augment statement
+                by name. The identifier syntax is defined formally defined by
+                the rule 'identifier' in Section 12 of RFC 6020. All augment identifiers
+                defined in a namespace MUST be unique. The namespace of augment
+                identifiers is shared by module and its submodules.";
+
+        /*
+                Discussion:
+                This extension allows for ease of development / debug
+                of YANG modules and it is suitable for code generation,
+                where each augment statement is nicely identified by
+                unique name instead of combination of augment target
+                and when condition.
+            */
+        argument "identifier";
+    }
+
+    // Context-aware RPCs
+
+    grouping rpc-context-ref {
+        description
+                "A reference to RPC context.";
+        leaf context-instance {
+            type instance-identifier;
+            description "Pointer to the context. ";
+        }
+    }
+
+    extension "rpc-context-instance" {
+        description
+                "YANG language extension which defines enclosing (parent) schema
+                node as referencable context for RPCs. The argument is identity
+                which is used to identify RPC context type.";
+
+        argument "context-type";
+    }
+
+    extension "context-reference" {
+        argument "context-type";
+    }
+
+    extension "context-instance" {
+        argument "context-type";
+    }
+
+    extension "instance-target" {
+        argument "path";
+    }
+
+    extension "anyxml-schema-location" {
+        argument "target-node";
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/anyxml-support/xml/bar.xml b/yang/yang-data-codec-xml/src/test/resources/anyxml-support/xml/bar.xml
new file mode 100644 (file)
index 0000000..2637686
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<root xmlns="bar">
+    <my-container-1>
+        <my-leaf-1>value1</my-leaf-1>
+    </my-container-1>
+    <my-container-2>
+        <my-leaf-2>value2</my-leaf-2>
+    </my-container-2>
+</root>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/anyxml-support/xml/foo.xml b/yang/yang-data-codec-xml/src/test/resources/anyxml-support/xml/foo.xml
new file mode 100644 (file)
index 0000000..2492e95
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo xmlns="foo">
+    <my-container-1>
+        <my-leaf-1>value1</my-leaf-1>
+    </my-container-1>
+    <my-container-2>
+        <inner-container>
+            <my-leaf-2>value2</my-leaf-2>
+        </inner-container>
+        <my-leaf-3>value3</my-leaf-3>
+    </my-container-2>
+
+    <my-anyxml-data>
+        <inner-container>
+            <my-leaf-2>any-xml-leaf-2-value</my-leaf-2>
+        </inner-container>
+        <my-leaf-3>any-xml-leaf-3-value</my-leaf-3>
+    </my-anyxml-data>
+</foo>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/anyxml-support/yang/bar.yang b/yang/yang-data-codec-xml/src/test/resources/anyxml-support/yang/bar.yang
new file mode 100644 (file)
index 0000000..b8c57a8
--- /dev/null
@@ -0,0 +1,18 @@
+module bar {
+    namespace "bar";
+    prefix bar;
+
+    container bar {
+        container my-container-1 {
+            leaf my-leaf-1 {
+                type string;
+            }
+        }
+
+        container my-container-2 {
+            leaf my-leaf-2 {
+                type string;
+            }
+        }
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/anyxml-support/yang/foo.yang b/yang/yang-data-codec-xml/src/test/resources/anyxml-support/yang/foo.yang
new file mode 100644 (file)
index 0000000..70ffa0b
--- /dev/null
@@ -0,0 +1,27 @@
+module foo {
+    namespace "foo";
+    prefix foo;
+
+    import yang-ext { prefix ext; revision-date 2013-07-09; }
+
+    container my-container-1 {
+        leaf my-leaf-1 {
+            type string;
+        }
+    }
+
+    container my-container-2 {
+        container inner-container {
+            leaf my-leaf-2 {
+                type string;
+            }
+        }
+        leaf my-leaf-3 {
+            type string;
+        }
+    }
+
+    anyxml my-anyxml-data {
+        ext:anyxml-schema-location "/my-container-2";
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/anyxml-support/yang/yang-ext.yang b/yang/yang-data-codec-xml/src/test/resources/anyxml-support/yang/yang-ext.yang
new file mode 100644 (file)
index 0000000..99eb4b6
--- /dev/null
@@ -0,0 +1,75 @@
+module yang-ext {
+    yang-version 1;
+    namespace "urn:opendaylight:yang:extension:yang-ext";
+    prefix "ext";
+
+    contact "Anton Tkacik <ttkacik@cisco.com>";
+
+    description
+        "Copyright (c) 2013 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";
+
+    revision "2013-07-09" {
+        description "";
+    }
+
+    // Augmentation name
+
+    extension "augment-identifier" {
+        description
+                "YANG language extension which assigns an identifier to augmentation.
+                Augment identifier is used to identify specific augment statement
+                by name. The identifier syntax is defined formally defined by
+                the rule 'identifier' in Section 12 of RFC 6020. All augment identifiers
+                defined in a namespace MUST be unique. The namespace of augment
+                identifiers is shared by module and its submodules.";
+
+        /*
+                Discussion:
+                This extension allows for ease of development / debug
+                of YANG modules and it is suitable for code generation,
+                where each augment statement is nicely identified by
+                unique name instead of combination of augment target
+                and when condition.
+            */
+        argument "identifier";
+    }
+
+    // Context-aware RPCs
+
+    grouping rpc-context-ref {
+        description
+                "A reference to RPC context.";
+        leaf context-instance {
+            type instance-identifier;
+            description "Pointer to the context. ";
+        }
+    }
+
+    extension "rpc-context-instance" {
+        description
+                "YANG language extension which defines enclosing (parent) schema
+                node as referencable context for RPCs. The argument is identity
+                which is used to identify RPC context type.";
+
+        argument "context-type";
+    }
+
+    extension "context-reference" {
+        argument "context-type";
+    }
+
+    extension "context-instance" {
+        argument "context-type";
+    }
+
+    extension "instance-target" {
+        argument "path";
+    }
+
+    extension "anyxml-schema-location" {
+        argument "target-node";
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo.xml b/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo.xml
new file mode 100644 (file)
index 0000000..26fe222
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo xmlns="foo">
+    <root>
+        <my-leaf>dp1o34</my-leaf>
+    </root>
+</foo>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo2.xml b/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo2.xml
new file mode 100644 (file)
index 0000000..72b9185
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo xmlns="foo">
+    <root>
+        <my-leaf>dp0s3f9</my-leaf>
+    </root>
+</foo>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo3.xml b/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo3.xml
new file mode 100644 (file)
index 0000000..c18e3cc
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo xmlns="foo">
+    <root>
+        <my-leaf>dp09P1p2s3</my-leaf>
+    </root>
+</foo>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo4.xml b/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo4.xml
new file mode 100644 (file)
index 0000000..154a114
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo xmlns="foo">
+    <root>
+        <my-leaf>dp0p3p1</my-leaf>
+    </root>
+</foo>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo5.xml b/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/foo5.xml
new file mode 100644 (file)
index 0000000..7a5ce58
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo xmlns="foo">
+    <root>
+        <my-leaf>dp0s3</my-leaf>
+    </root>
+</foo>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/invalid-foo.xml b/yang/yang-data-codec-xml/src/test/resources/bug5396/xml/invalid-foo.xml
new file mode 100644 (file)
index 0000000..cf86806
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo xmlns="foo">
+    <root>
+        <my-leaf>dp09P1p2s1234</my-leaf>
+    </root>
+</foo>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug5396/yang/foo.yang b/yang/yang-data-codec-xml/src/test/resources/bug5396/yang/foo.yang
new file mode 100644 (file)
index 0000000..bad4e39
--- /dev/null
@@ -0,0 +1,32 @@
+module foo {
+    yang-version 1;
+    namespace "foo";
+    prefix "foo";
+
+    revision "2016-03-22" {
+        description "test";
+    }
+
+    container root {
+        leaf my-leaf {
+            type my-type;
+        }
+    }
+
+    typedef my-type {
+        type union {
+            type string {
+                pattern "dp[0-9]+o[0-9]+";
+            }
+            type string {
+                pattern "dp[0-9]+s[0-9]+(f[0-9]+)?(d[0-9]+)?";
+            }
+            type string {
+                pattern "dp[0-9]+(P[0-9]+)?p[0-9]{1,3}s[0-9]{1,3}(f[0-9]+)?(d[0-9]+)?";
+            }
+            type string {
+                pattern "dp[0-9]+p[0-9]+p[0-9]+";
+            }
+        }
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug5446/xml/foo.xml b/yang/yang-data-codec-xml/src/test/resources/bug5446/xml/foo.xml
new file mode 100644 (file)
index 0000000..b9b8abf
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo xmlns="foo">
+    <root>
+        <ip-address>fwAAAQ==</ip-address>
+    </root>
+</foo>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug5446/yang/foo.yang b/yang/yang-data-codec-xml/src/test/resources/bug5446/yang/foo.yang
new file mode 100644 (file)
index 0000000..57ae347
--- /dev/null
@@ -0,0 +1,33 @@
+module foo {
+    yang-version 1;
+    namespace "foo";
+    prefix "foo";
+
+    revision "2015-11-05" {
+    }
+
+    typedef ipv4-address-binary {
+        type binary {
+            length "4";
+        }
+    }
+
+    typedef ipv6-address-binary {
+        type binary {
+            length "16";
+        }
+    }
+
+    typedef ip-address-binary {
+        type union {
+            type ipv4-address-binary;
+            type ipv6-address-binary;
+        }
+    }
+
+    container root {
+        leaf ip-address {
+            type ip-address-binary;
+        }
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/leafref-test.yang b/yang/yang-data-codec-xml/src/test/resources/leafref-test.yang
new file mode 100644 (file)
index 0000000..6851300
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2014 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
+ */
+module leafref-test {
+    yang-version 1;
+    namespace "urn:opendaylight:yangtools:leafref:test";
+    prefix "lt";
+
+    revision 2014-11-04 {
+        description "Test deserialization value of leafref type.";
+    }
+
+    identity test-identity-base {
+    }
+
+    identity test-identity {
+      base lt:test-identity-base;
+    }
+
+    container interface {
+        leaf simpleValue {
+            type instance-identifier;
+        }
+    }
+
+    container cont2 {
+        container cont3 {
+            leaf leafname3 {
+                type leafref {
+                    path "../../pointToStringLeaf";
+                }
+            }
+        }
+        leaf pointToStringLeaf {
+            type leafref {
+                path "../stringleaf";
+            }
+        }
+
+        leaf point-to-identityrefleaf {
+            type leafref {
+                path "../identityrefleaf";
+            }
+        }
+
+        leaf identityrefleaf {
+            type identityref {
+              base lt:test-identity-base;
+            }
+        }
+
+        leaf stringleaf {
+            type string;
+        }
+        leaf absname {
+            type leafref {
+                path "/lt:interface/lt:simpleValue";
+            }
+        }
+        leaf relname {
+            type leafref {
+                path "../../lt:interface/lt:simpleValue";
+            }
+        }
+
+        leaf lf-with-double-point-inside {
+            type leafref {
+                path "../../lt:interface/../lt:cont2/lt:stringleaf";
+            }
+        }
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell.yang b/yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell.yang
new file mode 100644 (file)
index 0000000..5c3e49f
--- /dev/null
@@ -0,0 +1,143 @@
+// vi: set smarttab et sw=4 tabstop=4:
+module test {
+    yang-version 1;
+    namespace "urn:opendaylight:params:xml:ns:yang:controller:test";
+    prefix "test";
+
+    organization "Cisco Systems, Inc.";
+
+    revision "2014-3-13" {
+        description
+            "Initial revision";
+    }
+
+
+    container container {
+        choice ch2{}
+        choice ch3{
+            case c3 {
+                leaf c3Leaf {
+                    type string;
+                }
+            }
+        }
+    }
+
+    augment "/container/" {
+        leaf augLeaf {
+            type string;
+        }
+    }
+
+    augment "/container/" {
+        choice ch{}
+    }
+
+    augment "/container/ch/" {
+        case c1 {
+            leaf c1Leaf {
+                type string;
+            }
+        }
+
+        leaf c12 {
+            type string;
+        }
+    }
+    augment "/container/ch/c1/" {
+        leaf c1Leaf_AnotherAugment {
+            type string;
+        }
+
+        choice deepChoice{}
+    }
+
+    augment "/container/ch3/" {
+        case c32 {
+            leaf c32Leaf {
+                type string;
+            }
+        }
+
+        leaf c34LeafS {
+            type string;
+        }
+    }
+
+
+    augment "/container/ch/c1/deepChoice/" {
+        case deepCase1 {
+            leaf deepLeafc1 {
+                type string;
+            }
+        }
+        case deepCase2 {
+            leaf deepLeafc2 {
+                type string;
+            }
+        }
+    }
+
+    augment "/container/ch2/" {
+        case c2 {
+            leaf c2Leaf {
+                type string;
+            }
+
+            choice c2DeepChoice {
+                case c2DeepChoiceCase1 {
+                    leaf c2DeepChoiceCase1Leaf1 {
+                        type string;
+                    }
+                }
+                case c2DeepChoiceCase2 {
+                    leaf c2DeepChoiceCase1Leaf2 {
+                        type string;
+                    }
+                }
+            }
+        }
+    }
+
+    augment "/container/ch2/" {
+        leaf c22Leaf {
+            type string;
+        }
+    }
+
+    augment "/container" {
+        /*ext:augment-identifier top-choice-augment1;*/
+        choice augment-choice1 {
+            case case1 {
+                container case1-container {
+                    leaf case1-leaf {
+                        type string;
+                    }
+                }
+            }
+
+            case case2 {
+                container case2-container {
+                    leaf case2-leaf {
+                        type string;
+                    }
+                }
+            }
+        }
+    }
+
+    augment "/container/augment-choice1/case1" {
+        /*ext:augment-identifier top-choice-augment2;*/
+        choice augment-choice2 {
+            case case11 {
+                container case11-choice-case-container {
+                    leaf case11-choice-case-leaf {
+                        type string;
+                    }
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell_ok.xml b/yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell_ok.xml
new file mode 100644 (file)
index 0000000..970cd57
--- /dev/null
@@ -0,0 +1,17 @@
+<root xmlns="urn:opendaylight:params:xml:ns:yang:controller:test">
+    <container>
+        <c2Leaf>2</c2Leaf>
+        <c2DeepChoiceCase1Leaf2>2</c2DeepChoiceCase1Leaf2>
+        <!--<c2DeepChoiceCase1Leaf1>2</c2DeepChoiceCase1Leaf1>-->
+
+        <c3Leaf>3</c3Leaf>
+
+        <augLeaf>augment</augLeaf>
+
+        <c1Leaf>1</c1Leaf>
+        <c1Leaf_AnotherAugment>1</c1Leaf_AnotherAugment>
+        <deepLeafc1>1</deepLeafc1>
+        <!--<deepLeafc2>1</deepLeafc2>-->
+    </container>
+</root>
+
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell_ok2.xml b/yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell_ok2.xml
new file mode 100644 (file)
index 0000000..fc51007
--- /dev/null
@@ -0,0 +1,14 @@
+<root xmlns="urn:opendaylight:params:xml:ns:yang:controller:test">
+    <container>
+        <c22Leaf>2</c22Leaf>
+
+        <c34LeafS>3</c34LeafS>
+
+        <augLeaf>augment</augLeaf>
+
+        <c1Leaf>1</c1Leaf>
+        <c1Leaf_AnotherAugment>1</c1Leaf_AnotherAugment>
+        <deepLeafc2>1</deepLeafc2>
+    </container>
+</root>
+
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell_ok3.xml b/yang/yang-data-codec-xml/src/test/resources/schema/augment_choice_hell_ok3.xml
new file mode 100644 (file)
index 0000000..e367c45
--- /dev/null
@@ -0,0 +1,8 @@
+<root xmlns="urn:opendaylight:params:xml:ns:yang:controller:test">
+    <container>
+        <case11-choice-case-container>
+            <case11-choice-case-leaf>leaf-value</case11-choice-case-leaf>
+        </case11-choice-case-container>
+    </container>
+</root>
+
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/rpc-test-model.yang b/yang/yang-data-codec-xml/src/test/resources/schema/rpc-test-model.yang
new file mode 100644 (file)
index 0000000..652b6a0
--- /dev/null
@@ -0,0 +1,44 @@
+module rpc-test-model {
+yang-version 1;
+    namespace "org:opendaylight:rpc-reply:test:ns:yang";
+    prefix "user";
+
+    organization "Cisco Systems";
+    contact "Lukas Sedlak";
+    description "Test model for testing rpc input message translation to DOM Nodes.";
+
+    revision "2014-07-17" {
+        description "Initial revision";
+    }
+
+    rpc rock-the-house {
+        output {
+            leaf zip-code {
+                type string;
+            }
+        }
+    }
+
+    rpc activate-software-image {
+        input {
+            leaf image-name {
+                type string;
+            }
+        }
+        output {
+            container image-properties {
+                list image-property {
+                    key "image-id";
+
+                    leaf image-id {
+                        type string;
+                    }
+                }
+            }
+
+            leaf status {
+                type string;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/rpc-test-payload1.xml b/yang/yang-data-codec-xml/src/test/resources/schema/rpc-test-payload1.xml
new file mode 100644 (file)
index 0000000..d81377a
--- /dev/null
@@ -0,0 +1,4 @@
+<rpc-reply message-id="101"
+           xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <trpc:zip-code xmlns:trpc="org:opendaylight:rpc-reply:test:ns:yang">123014</trpc:zip-code>
+</rpc-reply>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/rpc-test-payload2.xml b/yang/yang-data-codec-xml/src/test/resources/schema/rpc-test-payload2.xml
new file mode 100644 (file)
index 0000000..79792cc
--- /dev/null
@@ -0,0 +1,12 @@
+<rpc-reply message-id="101"
+           xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+           xmlns:rtm="org:opendaylight:rpc-reply:test:ns:yang"
+           rtm:image-name="img.img">
+
+  <image-properties xmlns="org:opendaylight:rpc-reply:test:ns:yang">
+    <image-property>
+      <image-id>id12345_test_data</image-id>
+    </image-property>
+  </image-properties>
+  <status xmlns="org:opendaylight:rpc-reply:test:ns:yang">testing_data</status>
+</rpc-reply>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/simple.xml b/yang/yang-data-codec-xml/src/test/resources/schema/simple.xml
new file mode 100644 (file)
index 0000000..634bed7
--- /dev/null
@@ -0,0 +1,74 @@
+<root>
+    <container xmlns="urn:opendaylight:params:xml:ns:yang:controller:test">
+        <boolean>true</boolean>
+        <innerContainer>
+            <uint16>44</uint16>
+        </innerContainer>
+        <leafList>a</leafList>
+        <leafList>b</leafList>
+
+        <list>
+            <uint32InList>1</uint32InList>
+            <containerInList name="inContainer">
+                <uint32>32</uint32>
+                <uint16>16</uint16>
+                <identityr xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:test">prefix:b</identityr>
+                <anyX xmlns="urn:opendaylight:params:xml:ns:yang:controller:test">direct Value</anyX>
+                <uint16-ref>16</uint16-ref>
+                <instance-id xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:test">/prefix:container/prefix:list[prefix:uint32InList="1"]</instance-id>
+            </containerInList>
+            <stringAugmentedToList>augmentInList</stringAugmentedToList>
+            <stringAugmentedToListInCase1>augmentInListCase1</stringAugmentedToListInCase1>
+        </list>
+        <list>
+            <uint32InList>2</uint32InList>
+            <containerInList>
+                <uint32>32</uint32>
+                <uint16>16</uint16>
+                <identityr>b</identityr>
+                <anyX>
+                    <container xmlns="randomNamespace" xmlns:pref="prefixed:namespace">
+                        <pref:boolean>true</pref:boolean>
+                        <innerContainer xmlns:p="prefixed:namespace">
+                            <uint16 xmlns:pref2="prefixed:namespace">pref2:44</uint16>
+                        </innerContainer>
+                    </container>
+                    <container2 xmlns="randomNamespace2"/>
+                </anyX>
+            </containerInList>
+            <stringAugmentedToListInCase2>augmentInListCase2</stringAugmentedToListInCase2>
+        </list>
+        <list>
+            <uint32InList>4</uint32InList>
+            <containerInList>
+                <anyX>
+                    <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
+                        <schemas>
+                            <schema>
+                                <identifier>module</identifier>
+                                <version>2012-12-12</version>
+                                <format xmlns:x="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">x:yang</format>
+                            </schema>
+                        </schemas>
+                    </netconf-state>
+                </anyX>
+            </containerInList>
+        </list>
+        <list>
+            <uint32InList>3</uint32InList>
+        </list>
+
+        <augmentString1>choice1Case1</augmentString1>
+        <augmentInt1>41</augmentInt1>
+        <stringInAugmentedCaseInAugmentedChoiceFromAugment>deep</stringInAugmentedCaseInAugmentedChoiceFromAugment>
+
+        <augmentContainer>
+            <augmentStringInaugmentContainer>choice2Case1</augmentStringInaugmentContainer>
+        </augmentContainer>
+
+        <augmentUint32>999</augmentUint32>
+
+
+
+    </container>
+</root>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/simple2.xml b/yang/yang-data-codec-xml/src/test/resources/schema/simple2.xml
new file mode 100644 (file)
index 0000000..2c4a5d1
--- /dev/null
@@ -0,0 +1,44 @@
+<root>
+    <container xmlns="urn:opendaylight:params:xml:ns:yang:controller:test">
+        <augmentString1>choice1Case1</augmentString1>
+        <augmentInt1>41</augmentInt1>
+        <stringInAugmentedCaseInAugmentedChoice2>deep</stringInAugmentedCaseInAugmentedChoice2>
+
+        <augmentedList>
+            <augmentStringInaugmentList>aug1</augmentStringInaugmentList>
+        </augmentedList>
+
+        <augmentedList>
+            <augmentStringInaugmentList>aug2</augmentStringInaugmentList>
+            <augmentedContainerInAugmentedListInAugmentedCase>
+                <leafInAugmentedContainerInAugmentedListInAugmentedCase>
+                    66
+                </leafInAugmentedContainerInAugmentedListInAugmentedCase>
+            </augmentedContainerInAugmentedListInAugmentedCase>
+
+            <augmentedListInAugmentedListInAugmentedCase>
+                <leafInAugmentedListInAugmentedListInAugmentedCase>
+                    661
+                </leafInAugmentedListInAugmentedListInAugmentedCase>
+                <leafInAugmentedListInAugmentedListInAugmentedCase>
+                    662
+                </leafInAugmentedListInAugmentedListInAugmentedCase>
+            </augmentedListInAugmentedListInAugmentedCase>
+
+            <augmentedListInAugmentedListInAugmentedCase>
+                <leafInAugmentedListInAugmentedListInAugmentedCase>
+                    6621
+                </leafInAugmentedListInAugmentedListInAugmentedCase>
+            </augmentedListInAugmentedListInAugmentedCase>
+        </augmentedList>
+
+        <augmentedList>
+            <augmentStringInaugmentList>aug3</augmentStringInaugmentList>
+            <augmentedContainerInAugmentedListInAugmentedCase/>
+
+            <augmentedListInAugmentedListInAugmentedCase/>
+        </augmentedList>
+
+    </container>
+</root>
+
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/simple_xml_with_attributes.xml b/yang/yang-data-codec-xml/src/test/resources/schema/simple_xml_with_attributes.xml
new file mode 100644 (file)
index 0000000..163a313
--- /dev/null
@@ -0,0 +1,14 @@
+<root>
+    <container xmlns="urn:opendaylight:params:xml:ns:yang:controller:test" name="test" xmlns:foo="http://www.foo.com/" foo:baz="baz">
+
+        <list list="on list entry">
+            <uint32InList name="test" foo:baz="baz">3</uint32InList>
+        </list>
+
+        <boolean xmlns:leaf="test:namespace:in:leaf" leaf:a="b">false</boolean>
+
+        <leafList foo:b="b">a</leafList>
+
+    </container>
+</root>
+
diff --git a/yang/yang-data-codec-xml/src/test/resources/schema/test.yang b/yang/yang-data-codec-xml/src/test/resources/schema/test.yang
new file mode 100644 (file)
index 0000000..d5def9c
--- /dev/null
@@ -0,0 +1,243 @@
+// vi: set smarttab et sw=4 tabstop=4:
+module test {
+    yang-version 1;
+    namespace "urn:opendaylight:params:xml:ns:yang:controller:test";
+    prefix "test";
+
+    organization "Cisco Systems, Inc.";
+
+    revision "2014-3-13" {
+        description
+            "Initial revision";
+    }
+
+    identity a {}
+
+    identity b {
+        base "test:a";
+    }
+
+    grouping listGroup {
+        list list {
+            key "uint32InList";
+
+            leaf uint32InList {
+                type uint32;
+            }
+
+            container containerInList{
+                leaf uint32 {
+                    type uint32;
+                }
+                leaf uint16 {
+                    type uint16;
+                }
+
+                leaf identityr {
+                    type identityref {
+                        base "test:a";
+                     }
+                }
+
+                leaf uint16-ref {
+                    type leafref {
+                        path "../uint16";
+                     }
+                }
+
+                leaf instance-id {
+                    type instance-identifier;
+                }
+
+                anyxml anyX;
+            }
+        }
+     }
+
+     grouping innerContainerGrouping {
+        container innerContainer {
+            leaf uint16 {
+                type uint16;
+            }
+
+            container innerInnerContainer {
+
+                leaf uint16 {
+                    type uint16;
+                }
+
+                leaf uint32 {
+                    type uint32;
+                }
+            }
+        }
+     }
+
+    container container {
+        leaf uint32 {
+            type uint32;
+        }
+
+        leaf decimal64 {
+            type decimal64 {
+                fraction-digits 2;
+            }
+        }
+
+        leaf boolean {
+            type boolean;
+        }
+
+        leaf binary {
+            type binary;
+        }
+
+        leaf string {
+            type string;
+        }
+
+        uses listGroup {
+            augment "list/" {
+                leaf stringAugmentedToList{
+                    type string;
+                }
+
+                choice choiceInList {
+                    case caseInList1 {
+                        leaf stringAugmentedToListInCase1 {
+                            type string;
+                        }
+                    }
+                    case caseInList2 {
+                        leaf stringAugmentedToListInCase2 {
+                            type string;
+                        }
+                    }
+                }
+            }
+        }
+
+        list directList {
+            leaf stringInDirectList {
+                type string;
+            }
+        }
+
+        uses innerContainerGrouping;
+
+        choice choice{}
+        choice choice2{}
+
+        leaf-list leafList {
+            type string;
+        }
+
+        leaf identityRef {
+            type identityref {
+                base test-identity;
+            }
+        }
+    }
+
+    augment "/container/" {
+        leaf augmentUint32 {
+            type uint32;
+        }
+    }
+
+    augment "/container/directList/" {
+        leaf augmentedString {
+            type uint32;
+        }
+    }
+
+    augment "/container/choice/" {
+        case test-identity-augment {
+            when "/container/identityRef = 'test-identity'";
+            leaf augmentString1 {
+                type string;
+            }
+
+            leaf augmentInt1 {
+                type uint32;
+            }
+        }
+        case test-identity-augment2 {
+            when "/container/identityRef = 'test-identity2'";
+            leaf augmentString2 {
+                type string;
+            }
+
+            leaf augmentInt2 {
+                type uint32;
+            }
+        }
+    }
+
+    augment "/container/choice/test-identity-augment/" {
+
+        choice augmentedChoiceInCase {
+
+            case augmentedCaseInAugmentedChoice {
+                leaf stringInAugmentedCaseInAugmentedChoice {
+                    type string;
+                }
+            }
+
+            case augmentedCaseInAugmentedChoice2 {
+                leaf stringInAugmentedCaseInAugmentedChoice2 {
+                    type string;
+                }
+            }
+        }
+    }
+
+    augment "/container/choice/test-identity-augment/augmentedChoiceInCase/" {
+        case augmentedCaseInAugmentedChoiceFromAugment {
+            leaf stringInAugmentedCaseInAugmentedChoiceFromAugment {
+                type string;
+            }
+        }
+    }
+
+    augment "/container/choice2/" {
+        case test-identity-augment {
+            when "/container/identityRef = 'test-identity'";
+            container augmentContainer {
+                leaf augmentStringInaugmentContainer {
+                    type string;
+                }
+            }
+        }
+        case test-identity-augment2 {
+            when "/container/identityRef = 'test-identity2'";
+            list augmentedList {
+                leaf augmentStringInaugmentList {
+                    type string;
+                }
+            }
+        }
+    }
+
+
+    augment "/container/choice2/test-identity-augment2/augmentedList/" {
+
+        container augmentedContainerInAugmentedListInAugmentedCase {
+            leaf-list leafInAugmentedContainerInAugmentedListInAugmentedCase {
+                type uint32;
+            }
+        }
+
+        list augmentedListInAugmentedListInAugmentedCase {
+            leaf-list leafInAugmentedListInAugmentedListInAugmentedCase {
+                    type uint32;
+                }
+        }
+    }
+
+    identity test-identity {}
+    identity test-identity2 {
+        base test-identity;
+    }
+
+}
\ No newline at end of file