Backport yang-data-codec-binfmt to v3.0.x 17/90517/3
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 19 Jun 2020 10:48:40 +0000 (12:48 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 19 Jun 2020 12:06:51 +0000 (14:06 +0200)
This is a straight backport of all the changes done in 4.0.x
time-frame, so that we have the same codebase available in 3.0.x
stream.

JIRA: YANGTOOLS-1035
Change-Id: If661bd31df527832de7c52cc85420073b87e390f
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
52 files changed:
artifacts/pom.xml
docs/pom.xml
features/odl-yangtools-codec/pom.xml
yang/pom.xml
yang/yang-data-codec-binfmt/pom.xml [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractLithiumDataInput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractLithiumDataOutput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractMagnesiumDataInput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractMagnesiumDataOutput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractNormalizedNodeDataInput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractNormalizedNodeDataOutput.java [new file with mode: 0755]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/ForwardingDataInput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/ForwardingNormalizedNodeDataInput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/InvalidNormalizedNodeStreamException.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumNode.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumNormalizedNodeInputStreamReader.java [new file with mode: 0755]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumNormalizedNodeOutputStreamWriter.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumPathArgument.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumTokens.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumValue.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumDataInput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumDataOutput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumNode.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumPathArgument.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumValue.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NeonSR2NormalizedNodeInputStreamReader.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NeonSR2NormalizedNodeOutputStreamWriter.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NeonSR2Tokens.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeDataInput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeDataOutput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeStreamVersion.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/QNameFactory.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/SodiumSR1DataInput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/SodiumSR1DataOutput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/TokenTypes.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/VersionedNormalizedNodeDataInput.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractSerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AidSerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/BitsSerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/BooleanSerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/BytesSerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/IntSerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumWriteObjectMappingTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MapEntrySerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NipSerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeStreamReaderWriterTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/QNameFactoryTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/SampleNormalizedNodeSerializable.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/StringSerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/TestModel.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/UintSerializationTest.java [new file with mode: 0644]
yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/YiidSerializationTest.java [new file with mode: 0644]

index 711040b51208b620132bb54307efe2bf8b74533d..d2f5f91232752b315a8c8456c86dc9f2983fda04 100644 (file)
                 <artifactId>yang-data-jaxen</artifactId>
                 <version>3.0.12-SNAPSHOT</version>
             </dependency>
+            <dependency>
+                <groupId>org.opendaylight.yangtools</groupId>
+                <artifactId>yang-data-codec-binfmt</artifactId>
+                <version>3.0.12-SNAPSHOT</version>
+            </dependency>
             <dependency>
                 <groupId>org.opendaylight.yangtools</groupId>
                 <artifactId>yang-data-codec-gson</artifactId>
index 808baef5964e835aa3fc1805f163cb1e9329cf29..75e38d777d82e1329fc784827e96ec62fc24bebf 100644 (file)
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-data-jaxen</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-data-codec-binfmt</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-data-codec-gson</artifactId>
index 30cec5117682ac8df7e75c2f746fa922a51b5b76..fdcbde5ec26d01c63eb392000b44aea455a0786e 100644 (file)
             <type>xml</type>
             <classifier>features</classifier>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-data-codec-binfmt</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-data-codec-gson</artifactId>
index 87c9da014c1ee206478f8313bf2a2aa1a5486ee8..d3cb1092ac1dc2c0bc4f1bc55a43775c24f3ac92 100644 (file)
@@ -30,6 +30,7 @@
         <module>yang-data-util</module>
         <module>yang-data-impl</module>
         <module>yang-data-transform</module>
+        <module>yang-data-codec-binfmt</module>
         <module>yang-data-codec-gson</module>
         <module>yang-data-codec-xml</module>
         <module>yang-maven-plugin</module>
diff --git a/yang/yang-data-codec-binfmt/pom.xml b/yang/yang-data-codec-binfmt/pom.xml
new file mode 100644 (file)
index 0000000..6993aae
--- /dev/null
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ Copyright (c) 2019 PANTHEON.tech, s.r.o. 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.opendaylight.yangtools</groupId>
+        <artifactId>bundle-parent</artifactId>
+        <version>3.0.12-SNAPSHOT</version>
+        <relativePath>../../bundle-parent</relativePath>
+    </parent>
+
+    <artifactId>yang-data-codec-binfmt</artifactId>
+    <packaging>bundle</packaging>
+    <name>${project.artifactId}</name>
+    <description>DataInput/Output for NormalizedNodes</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-data-api</artifactId>
+        </dependency>
+        <!-- FIXME: add RFC7952 (metadata) support
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>rfc7952-data-api</artifactId>
+        </dependency-->
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-data-impl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-data-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-test-util</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Automatic-Module-Name>org.opendaylight.yangtools.yang.data.codec.binfmt</Automatic-Module-Name>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
+
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractLithiumDataInput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractLithiumDataInput.java
new file mode 100644 (file)
index 0000000..302b8f9
--- /dev/null
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2014, 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.binfmt;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.Sets;
+import java.io.DataInput;
+import java.io.IOException;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * NormalizedNodeInputStreamReader reads the byte stream and constructs the normalized node including its children
+ * nodes. This process goes in recursive manner, where each NodeTypes object signifies the start of the object, except
+ * END_NODE. If a node can have children, then that node's end is calculated based on appearance of END_NODE.
+ */
+abstract class AbstractLithiumDataInput extends AbstractNormalizedNodeDataInput {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractLithiumDataInput.class);
+
+    private final List<String> codedStringMap = new ArrayList<>();
+
+    private QName lastLeafSetQName;
+
+    AbstractLithiumDataInput(final DataInput input) {
+        super(input);
+    }
+
+    @Override
+    public final void streamNormalizedNode(final NormalizedNodeStreamWriter writer) throws IOException {
+        streamNormalizedNode(requireNonNull(writer), input.readByte());
+    }
+
+    private void streamNormalizedNode(final NormalizedNodeStreamWriter writer, final byte nodeType) throws IOException {
+        switch (nodeType) {
+            case LithiumNode.ANY_XML_NODE:
+                streamAnyxml(writer);
+                break;
+            case LithiumNode.AUGMENTATION_NODE:
+                streamAugmentation(writer);
+                break;
+            case LithiumNode.CHOICE_NODE:
+                streamChoice(writer);
+                break;
+            case LithiumNode.CONTAINER_NODE:
+                streamContainer(writer);
+                break;
+            case LithiumNode.LEAF_NODE:
+                streamLeaf(writer);
+                break;
+            case LithiumNode.LEAF_SET:
+                streamLeafSet(writer);
+                break;
+            case LithiumNode.ORDERED_LEAF_SET:
+                streamOrderedLeafSet(writer);
+                break;
+            case LithiumNode.LEAF_SET_ENTRY_NODE:
+                streamLeafSetEntry(writer);
+                break;
+            case LithiumNode.MAP_ENTRY_NODE:
+                streamMapEntry(writer);
+                break;
+            case LithiumNode.MAP_NODE:
+                streamMap(writer);
+                break;
+            case LithiumNode.ORDERED_MAP_NODE:
+                streamOrderedMap(writer);
+                break;
+            case LithiumNode.UNKEYED_LIST:
+                streamUnkeyedList(writer);
+                break;
+            case LithiumNode.UNKEYED_LIST_ITEM:
+                streamUnkeyedListItem(writer);
+                break;
+            default:
+                throw new InvalidNormalizedNodeStreamException("Unexpected node " + nodeType);
+        }
+    }
+
+    private void streamAnyxml(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifier identifier = readNodeIdentifier();
+        LOG.trace("Streaming anyxml node {}", identifier);
+
+        final DOMSource value = readDOMSource();
+        writer.startAnyxmlNode(identifier);
+        writer.domSourceValue(value);
+        writer.endNode();
+    }
+
+    private void streamAugmentation(final NormalizedNodeStreamWriter writer) throws IOException {
+        final AugmentationIdentifier augIdentifier = readAugmentationIdentifier();
+        LOG.trace("Streaming augmentation node {}", augIdentifier);
+        writer.startAugmentationNode(augIdentifier);
+        commonStreamContainer(writer);
+    }
+
+    private void streamChoice(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifier identifier = readNodeIdentifier();
+        LOG.trace("Streaming choice node {}", identifier);
+        writer.startChoiceNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
+        commonStreamContainer(writer);
+    }
+
+    private void streamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifier identifier = readNodeIdentifier();
+        LOG.trace("Streaming container node {}", identifier);
+        writer.startContainerNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
+        commonStreamContainer(writer);
+    }
+
+    private void streamLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
+        startLeaf(writer);
+        endLeaf(writer, readObject());
+    }
+
+    // Leaf inside a MapEntryNode, it can potentially be a key leaf, in which case we want to de-duplicate values.
+    private void streamLeaf(final NormalizedNodeStreamWriter writer, final NodeIdentifierWithPredicates entryId)
+            throws IOException {
+        final NodeIdentifier identifier = startLeaf(writer);
+        final Object value = readObject();
+        final Object entryValue = entryId.getValue(identifier.getNodeType());
+        endLeaf(writer, entryValue == null ? value : entryValue);
+    }
+
+    private NodeIdentifier startLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifier identifier = readNodeIdentifier();
+        LOG.trace("Streaming leaf node {}", identifier);
+        writer.startLeafNode(identifier);
+        return identifier;
+    }
+
+    private static void endLeaf(final NormalizedNodeStreamWriter writer, final Object value) throws IOException {
+        writer.scalarValue(value);
+        writer.endNode();
+    }
+
+    private void streamLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifier identifier = readNodeIdentifier();
+        LOG.trace("Streaming leaf set node {}", identifier);
+        writer.startLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
+        commonStreamLeafSet(writer, identifier);
+    }
+
+    private void streamOrderedLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifier identifier = readNodeIdentifier();
+        LOG.trace("Streaming ordered leaf set node {}", identifier);
+        writer.startOrderedLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
+        commonStreamLeafSet(writer, identifier);
+    }
+
+    private void commonStreamLeafSet(final NormalizedNodeStreamWriter writer, final NodeIdentifier identifier)
+            throws IOException {
+        lastLeafSetQName = identifier.getNodeType();
+        try {
+            commonStreamContainer(writer);
+        } finally {
+            // Make sure we never leak this
+            lastLeafSetQName = null;
+        }
+    }
+
+    private void streamLeafSetEntry(final NormalizedNodeStreamWriter writer) throws IOException {
+        final QName name = lastLeafSetQName != null ? lastLeafSetQName : readQName();
+        final Object value = readObject();
+        final NodeWithValue<Object> leafIdentifier = new NodeWithValue<>(name, value);
+        LOG.trace("Streaming leaf set entry node {}, value {}", leafIdentifier, value);
+        writer.startLeafSetEntryNode(leafIdentifier);
+        writer.scalarValue(value);
+        writer.endNode();
+    }
+
+    private void streamMap(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifier identifier = readNodeIdentifier();
+        LOG.trace("Streaming map node {}", identifier);
+        writer.startMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
+        commonStreamContainer(writer);
+    }
+
+    private void streamOrderedMap(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifier identifier = readNodeIdentifier();
+        LOG.trace("Streaming ordered map node {}", identifier);
+        writer.startOrderedMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
+        commonStreamContainer(writer);
+    }
+
+    private void streamMapEntry(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifierWithPredicates entryIdentifier = readNormalizedNodeWithPredicates();
+        LOG.trace("Streaming map entry node {}", entryIdentifier);
+        writer.startMapEntryNode(entryIdentifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
+
+        // Same loop as commonStreamContainer(), but ...
+        for (byte nodeType = input.readByte(); nodeType != LithiumNode.END_NODE; nodeType = input.readByte()) {
+            if (nodeType == LithiumNode.LEAF_NODE) {
+                // ... leaf nodes may need de-duplication
+                streamLeaf(writer, entryIdentifier);
+            } else {
+                streamNormalizedNode(writer, nodeType);
+            }
+        }
+        writer.endNode();
+    }
+
+    private void streamUnkeyedList(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifier identifier = readNodeIdentifier();
+        LOG.trace("Streaming unkeyed list node {}", identifier);
+        writer.startUnkeyedList(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
+        commonStreamContainer(writer);
+    }
+
+    private void streamUnkeyedListItem(final NormalizedNodeStreamWriter writer) throws IOException {
+        final NodeIdentifier identifier = readNodeIdentifier();
+        LOG.trace("Streaming unkeyed list item node {}", identifier);
+        writer.startUnkeyedListItem(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
+        commonStreamContainer(writer);
+    }
+
+    private void commonStreamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
+        for (byte nodeType = input.readByte(); nodeType != LithiumNode.END_NODE; nodeType = input.readByte()) {
+            streamNormalizedNode(writer, nodeType);
+        }
+        writer.endNode();
+    }
+
+    private DOMSource readDOMSource() throws IOException {
+        String xml = readObject().toString();
+        try {
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            factory.setNamespaceAware(true);
+            Element node = factory.newDocumentBuilder().parse(
+                    new InputSource(new StringReader(xml))).getDocumentElement();
+            return new DOMSource(node);
+        } catch (SAXException | ParserConfigurationException e) {
+            throw new IOException("Error parsing XML: " + xml, e);
+        }
+    }
+
+    final QName defaultReadQName() throws IOException {
+        // Read in the same sequence of writing
+        String localName = readCodedString();
+        String namespace = readCodedString();
+        String revision = Strings.emptyToNull(readCodedString());
+
+        return QNameFactory.create(localName, namespace, revision);
+    }
+
+    final String readCodedString() throws IOException {
+        final byte valueType = input.readByte();
+        switch (valueType) {
+            case LithiumTokens.IS_NULL_VALUE:
+                return null;
+            case LithiumTokens.IS_CODE_VALUE:
+                final int code = input.readInt();
+                try {
+                    return codedStringMap.get(code);
+                } catch (IndexOutOfBoundsException e) {
+                    throw new IOException("String code " + code + " was not found", e);
+                }
+            case LithiumTokens.IS_STRING_VALUE:
+                final String value = input.readUTF().intern();
+                codedStringMap.add(value);
+                return value;
+            default:
+                throw new IOException("Unhandled string value type " + valueType);
+        }
+    }
+
+    private Set<QName> readQNameSet() throws IOException {
+        // Read the children count
+        final int count = input.readInt();
+        final Set<QName> children = Sets.newHashSetWithExpectedSize(count);
+        for (int i = 0; i < count; i++) {
+            children.add(readQName());
+        }
+        return children;
+    }
+
+    abstract AugmentationIdentifier readAugmentationIdentifier() throws IOException;
+
+    abstract NodeIdentifier readNodeIdentifier() throws IOException;
+
+    final AugmentationIdentifier defaultReadAugmentationIdentifier() throws IOException {
+        return AugmentationIdentifier.create(readQNameSet());
+    }
+
+    private NodeIdentifierWithPredicates readNormalizedNodeWithPredicates() throws IOException {
+        final QName qname = readQName();
+        final int count = input.readInt();
+        switch (count) {
+            case 0:
+                return NodeIdentifierWithPredicates.of(qname);
+            case 1:
+                return NodeIdentifierWithPredicates.of(qname, readQName(), readObject());
+            default:
+                // ImmutableList is used by ImmutableOffsetMapTemplate for lookups, hence we use that.
+                final Builder<QName> keys = ImmutableList.builderWithExpectedSize(count);
+                final Object[] values = new Object[count];
+                for (int i = 0; i < count; i++) {
+                    keys.add(readQName());
+                    values[i] = readObject();
+                }
+
+                return NodeIdentifierWithPredicates.of(qname, ImmutableOffsetMapTemplate.ordered(keys.build())
+                    .instantiateWithValues(values));
+        }
+    }
+
+    private Object readObject() throws IOException {
+        byte objectType = input.readByte();
+        switch (objectType) {
+            case LithiumValue.BITS_TYPE:
+                return readObjSet();
+
+            case LithiumValue.BOOL_TYPE:
+                return input.readBoolean();
+
+            case LithiumValue.BYTE_TYPE:
+                return input.readByte();
+
+            case LithiumValue.INT_TYPE:
+                return input.readInt();
+
+            case LithiumValue.LONG_TYPE:
+                return input.readLong();
+
+            case LithiumValue.QNAME_TYPE:
+                return readQName();
+
+            case LithiumValue.SHORT_TYPE:
+                return input.readShort();
+
+            case LithiumValue.STRING_TYPE:
+                return input.readUTF();
+
+            case LithiumValue.STRING_BYTES_TYPE:
+                return readStringBytes();
+
+            case LithiumValue.BIG_DECIMAL_TYPE:
+                return new BigDecimal(input.readUTF());
+
+            case LithiumValue.BIG_INTEGER_TYPE:
+                return new BigInteger(input.readUTF());
+
+            case LithiumValue.BINARY_TYPE:
+                byte[] bytes = new byte[input.readInt()];
+                input.readFully(bytes);
+                return bytes;
+
+            case LithiumValue.YANG_IDENTIFIER_TYPE:
+                return readYangInstanceIdentifierInternal();
+
+            case LithiumValue.EMPTY_TYPE:
+            // Leaf nodes no longer allow null values and thus we no longer emit null values. Previously, the "empty"
+            // yang type was represented as null so we translate an incoming null value to Empty. It was possible for
+            // a BI user to set a string leaf to null and we're rolling the dice here but the chances for that are
+            // very low. We'd have to know the yang type but, even if we did, we can't let a null value pass upstream
+            // so we'd have to drop the leaf which might cause other issues.
+            case LithiumValue.NULL_TYPE:
+                return Empty.getInstance();
+
+            default:
+                return null;
+        }
+    }
+
+    private String readStringBytes() throws IOException {
+        byte[] bytes = new byte[input.readInt()];
+        input.readFully(bytes);
+        return new String(bytes, StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public final YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
+        return readYangInstanceIdentifierInternal();
+    }
+
+    private YangInstanceIdentifier readYangInstanceIdentifierInternal() throws IOException {
+        int size = input.readInt();
+        final Builder<PathArgument> pathArguments = ImmutableList.builderWithExpectedSize(size);
+        for (int i = 0; i < size; i++) {
+            pathArguments.add(readPathArgument());
+        }
+        return YangInstanceIdentifier.create(pathArguments.build());
+    }
+
+    private Set<String> readObjSet() throws IOException {
+        int count = input.readInt();
+        Set<String> children = new HashSet<>(count);
+        for (int i = 0; i < count; i++) {
+            children.add(readCodedString());
+        }
+        return children;
+    }
+
+    @Override
+    public final PathArgument readPathArgument() throws IOException {
+        // read Type
+        int type = input.readByte();
+
+        switch (type) {
+            case LithiumPathArgument.AUGMENTATION_IDENTIFIER:
+                return readAugmentationIdentifier();
+            case LithiumPathArgument.NODE_IDENTIFIER:
+                return readNodeIdentifier();
+            case LithiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES:
+                return readNormalizedNodeWithPredicates();
+            case LithiumPathArgument.NODE_IDENTIFIER_WITH_VALUE:
+                return new NodeWithValue<>(readQName(), readObject());
+            default:
+                // FIXME: throw hard error
+                return null;
+        }
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractLithiumDataOutput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractLithiumDataOutput.java
new file mode 100644 (file)
index 0000000..83daf6c
--- /dev/null
@@ -0,0 +1,401 @@
+/*
+ * 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.binfmt;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * "original" type mapping. Baseline is Lithium but it really was introduced in Oxygen, where {@code type empty} was
+ * remapped from null.
+ *
+ * <p>
+ * {@code uint8}, {@code uint16}, {@code uint32} use java.lang types with widening, hence their value types overlap with
+ * mapping of {@code int16}, {@code int32} and {@code int64}, making that difference indiscernible without YANG schema
+ * knowledge.
+ */
+abstract class AbstractLithiumDataOutput extends AbstractNormalizedNodeDataOutput {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractLithiumDataOutput.class);
+    private static final TransformerFactory TF = TransformerFactory.newInstance();
+    private static final ImmutableMap<Class<?>, Byte> KNOWN_TYPES = ImmutableMap.<Class<?>, Byte>builder()
+            .put(String.class, LithiumValue.STRING_TYPE)
+            .put(Byte.class, LithiumValue.BYTE_TYPE)
+            .put(Integer.class, LithiumValue.INT_TYPE)
+            .put(Long.class, LithiumValue.LONG_TYPE)
+            .put(Boolean.class, LithiumValue.BOOL_TYPE)
+            .put(QName.class, LithiumValue.QNAME_TYPE)
+            .put(Short.class, LithiumValue.SHORT_TYPE)
+            .put(BigInteger.class, LithiumValue.BIG_INTEGER_TYPE)
+            .put(BigDecimal.class, LithiumValue.BIG_DECIMAL_TYPE)
+            .put(byte[].class, LithiumValue.BINARY_TYPE)
+            .put(Empty.class, LithiumValue.EMPTY_TYPE)
+            .build();
+
+    private final Map<String, Integer> stringCodeMap = new HashMap<>();
+
+    private QName lastLeafSetQName;
+    private boolean inSimple;
+
+    AbstractLithiumDataOutput(final DataOutput output) {
+        super(output);
+    }
+
+    @Override
+    public final void startLeafNode(final NodeIdentifier name) throws IOException {
+        LOG.trace("Starting a new leaf node");
+        startNode(name, LithiumNode.LEAF_NODE);
+        inSimple = true;
+    }
+
+    @Override
+    public final void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        LOG.trace("Starting a new leaf set");
+        commonStartLeafSet(name, LithiumNode.LEAF_SET);
+    }
+
+    @Override
+    public final void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        LOG.trace("Starting a new ordered leaf set");
+        commonStartLeafSet(name, LithiumNode.ORDERED_LEAF_SET);
+    }
+
+    private void commonStartLeafSet(final NodeIdentifier name, final byte nodeType) throws IOException {
+        startNode(name, nodeType);
+        lastLeafSetQName = name.getNodeType();
+    }
+
+    @Override
+    public final void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
+        LOG.trace("Starting a new leaf set entry node");
+
+        output.writeByte(LithiumNode.LEAF_SET_ENTRY_NODE);
+
+        // lastLeafSetQName is set if the parent LeafSetNode was previously written. Otherwise this is a
+        // stand alone LeafSetEntryNode so write out it's name here.
+        if (lastLeafSetQName == null) {
+            writeQNameInternal(name.getNodeType());
+        }
+        inSimple = true;
+    }
+
+    @Override
+    public final void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        LOG.trace("Starting a new container node");
+        startNode(name, LithiumNode.CONTAINER_NODE);
+    }
+
+    @Override
+    public final void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint)
+            throws IOException {
+        LOG.trace("Starting a new yang modeled anyXml node");
+        startNode(name, LithiumNode.YANG_MODELED_ANY_XML_NODE);
+    }
+
+    @Override
+    public final void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        LOG.trace("Starting a new unkeyed list");
+        startNode(name, LithiumNode.UNKEYED_LIST);
+    }
+
+    @Override
+    public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        LOG.trace("Starting a new unkeyed list item");
+        startNode(name, LithiumNode.UNKEYED_LIST_ITEM);
+    }
+
+    @Override
+    public final void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        LOG.trace("Starting a new map node");
+        startNode(name, LithiumNode.MAP_NODE);
+    }
+
+    @Override
+    public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
+            throws IOException {
+        LOG.trace("Starting a new map entry node");
+        startNode(identifier, LithiumNode.MAP_ENTRY_NODE);
+        writeKeyValueMap(identifier.entrySet());
+    }
+
+    @Override
+    public final void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        LOG.trace("Starting a new ordered map node");
+        startNode(name, LithiumNode.ORDERED_MAP_NODE);
+    }
+
+    @Override
+    public final void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        LOG.trace("Starting a new choice node");
+        startNode(name, LithiumNode.CHOICE_NODE);
+    }
+
+    @Override
+    public final void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
+        requireNonNull(identifier, "Node identifier should not be null");
+        LOG.trace("Starting a new augmentation node");
+
+        output.writeByte(LithiumNode.AUGMENTATION_NODE);
+        writeAugmentationIdentifier(identifier);
+    }
+
+    @Override
+    public final void startAnyxmlNode(final NodeIdentifier name) throws IOException {
+        LOG.trace("Starting anyxml node");
+        startNode(name, LithiumNode.ANY_XML_NODE);
+        inSimple = true;
+    }
+
+    @Override
+    public final void scalarValue(final Object value) throws IOException {
+        writeObject(value);
+    }
+
+    @Override
+    public final void domSourceValue(final DOMSource value) throws IOException {
+        final StringWriter writer = new StringWriter();
+        try {
+            TF.newTransformer().transform(value, new StreamResult(writer));
+        } catch (TransformerException e) {
+            throw new IOException("Error writing anyXml", e);
+        }
+        writeObject(writer.toString());
+    }
+
+    @Override
+    public final void endNode() throws IOException {
+        LOG.trace("Ending the node");
+        if (!inSimple) {
+            lastLeafSetQName = null;
+            output.writeByte(LithiumNode.END_NODE);
+        }
+        inSimple = false;
+    }
+
+    @Override
+    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST",
+            justification = "The casts in the switch clauses are indirectly confirmed via the determination of 'type'.")
+    final void writePathArgumentInternal(final PathArgument pathArgument) throws IOException {
+        final byte type = LithiumPathArgument.getSerializablePathArgumentType(pathArgument);
+        output.writeByte(type);
+
+        switch (type) {
+            case LithiumPathArgument.NODE_IDENTIFIER:
+                NodeIdentifier nodeIdentifier = (NodeIdentifier) pathArgument;
+                writeQNameInternal(nodeIdentifier.getNodeType());
+                break;
+            case LithiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES:
+                NodeIdentifierWithPredicates nodeIdentifierWithPredicates =
+                    (NodeIdentifierWithPredicates) pathArgument;
+                writeQNameInternal(nodeIdentifierWithPredicates.getNodeType());
+                writeKeyValueMap(nodeIdentifierWithPredicates.entrySet());
+                break;
+            case LithiumPathArgument.NODE_IDENTIFIER_WITH_VALUE:
+                NodeWithValue<?> nodeWithValue = (NodeWithValue<?>) pathArgument;
+                writeQNameInternal(nodeWithValue.getNodeType());
+                writeObject(nodeWithValue.getValue());
+                break;
+            case LithiumPathArgument.AUGMENTATION_IDENTIFIER:
+                // No Qname in augmentation identifier
+                writeAugmentationIdentifier((AugmentationIdentifier) pathArgument);
+                break;
+            default:
+                throw new IllegalStateException("Unknown node identifier type is found : "
+                        + pathArgument.getClass().toString());
+        }
+    }
+
+    @Override
+    final void writeYangInstanceIdentifierInternal(final YangInstanceIdentifier identifier) throws IOException {
+        List<PathArgument> pathArguments = identifier.getPathArguments();
+        output.writeInt(pathArguments.size());
+
+        for (PathArgument pathArgument : pathArguments) {
+            writePathArgumentInternal(pathArgument);
+        }
+    }
+
+    final void defaultWriteAugmentationIdentifier(final @NonNull AugmentationIdentifier aid) throws IOException {
+        final Set<QName> qnames = aid.getPossibleChildNames();
+        // Write each child's qname separately, if list is empty send count as 0
+        if (!qnames.isEmpty()) {
+            output.writeInt(qnames.size());
+            for (QName qname : qnames) {
+                writeQNameInternal(qname);
+            }
+        } else {
+            LOG.debug("augmentation node does not have any child");
+            output.writeInt(0);
+        }
+    }
+
+    final void defaultWriteQName(final QName qname) throws IOException {
+        writeString(qname.getLocalName());
+        writeModule(qname.getModule());
+    }
+
+    final void defaultWriteModule(final QNameModule module) throws IOException {
+        writeString(module.getNamespace().toString());
+        final Optional<Revision> revision = module.getRevision();
+        if (revision.isPresent()) {
+            writeString(revision.get().toString());
+        } else {
+            writeByte(LithiumTokens.IS_NULL_VALUE);
+        }
+    }
+
+    abstract void writeModule(QNameModule module) throws IOException;
+
+    abstract void writeAugmentationIdentifier(@NonNull AugmentationIdentifier aid) throws IOException;
+
+    private void startNode(final PathArgument arg, final byte nodeType) throws IOException {
+        requireNonNull(arg, "Node identifier should not be null");
+        checkState(!inSimple, "Attempted to start a child in a simple node");
+
+        // First write the type of node
+        output.writeByte(nodeType);
+        // Write Start Tag
+        writeQNameInternal(arg.getNodeType());
+    }
+
+    private void writeObjSet(final Set<?> set) throws IOException {
+        output.writeInt(set.size());
+        for (Object o : set) {
+            checkArgument(o instanceof String, "Expected value type to be String but was %s (%s)", o.getClass(), o);
+            writeString((String) o);
+        }
+    }
+
+    private void writeObject(final Object value) throws IOException {
+        byte type = getSerializableType(value);
+        // Write object type first
+        output.writeByte(type);
+
+        switch (type) {
+            case LithiumValue.BOOL_TYPE:
+                output.writeBoolean((Boolean) value);
+                break;
+            case LithiumValue.QNAME_TYPE:
+                writeQNameInternal((QName) value);
+                break;
+            case LithiumValue.INT_TYPE:
+                output.writeInt((Integer) value);
+                break;
+            case LithiumValue.BYTE_TYPE:
+                output.writeByte((Byte) value);
+                break;
+            case LithiumValue.LONG_TYPE:
+                output.writeLong((Long) value);
+                break;
+            case LithiumValue.SHORT_TYPE:
+                output.writeShort((Short) value);
+                break;
+            case LithiumValue.BITS_TYPE:
+                writeObjSet((Set<?>) value);
+                break;
+            case LithiumValue.BINARY_TYPE:
+                byte[] bytes = (byte[]) value;
+                output.writeInt(bytes.length);
+                output.write(bytes);
+                break;
+            case LithiumValue.YANG_IDENTIFIER_TYPE:
+                writeYangInstanceIdentifierInternal((YangInstanceIdentifier) value);
+                break;
+            case LithiumValue.EMPTY_TYPE:
+                break;
+            case LithiumValue.STRING_BYTES_TYPE:
+                final byte[] valueBytes = value.toString().getBytes(StandardCharsets.UTF_8);
+                output.writeInt(valueBytes.length);
+                output.write(valueBytes);
+                break;
+            default:
+                output.writeUTF(value.toString());
+                break;
+        }
+    }
+
+    private void writeKeyValueMap(final Set<Entry<QName, Object>> entrySet) throws IOException {
+        if (!entrySet.isEmpty()) {
+            output.writeInt(entrySet.size());
+            for (Entry<QName, Object> entry : entrySet) {
+                writeQNameInternal(entry.getKey());
+                writeObject(entry.getValue());
+            }
+        } else {
+            output.writeInt(0);
+        }
+    }
+
+    private void writeString(final @NonNull String string) throws IOException {
+        final Integer value = stringCodeMap.get(verifyNotNull(string));
+        if (value == null) {
+            stringCodeMap.put(string, stringCodeMap.size());
+            writeByte(LithiumTokens.IS_STRING_VALUE);
+            writeUTF(string);
+        } else {
+            writeByte(LithiumTokens.IS_CODE_VALUE);
+            writeInt(value);
+        }
+    }
+
+    @VisibleForTesting
+    static final byte getSerializableType(final Object node) {
+        final Byte type = KNOWN_TYPES.get(requireNonNull(node).getClass());
+        if (type != null) {
+            if (type == LithiumValue.STRING_TYPE
+                    && ((String) node).length() >= LithiumValue.STRING_BYTES_LENGTH_THRESHOLD) {
+                return LithiumValue.STRING_BYTES_TYPE;
+            }
+            return type;
+        }
+
+        if (node instanceof Set) {
+            return LithiumValue.BITS_TYPE;
+        }
+
+        if (node instanceof YangInstanceIdentifier) {
+            return LithiumValue.YANG_IDENTIFIER_TYPE;
+        }
+
+        throw new IllegalArgumentException("Unknown value type " + node.getClass().getSimpleName());
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractMagnesiumDataInput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractMagnesiumDataInput.java
new file mode 100644 (file)
index 0000000..b25ab66
--- /dev/null
@@ -0,0 +1,852 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import java.io.DataInput;
+import java.io.IOException;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import javax.xml.transform.dom.DOMSource;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.rfc8528.data.api.MountPointIdentifier;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.opendaylight.yangtools.yang.common.Uint32;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.opendaylight.yangtools.yang.common.Uint8;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * Abstract base class for NormalizedNodeDataInput based on {@link MagnesiumNode}, {@link MagnesiumPathArgument} and
+ * {@link MagnesiumValue}.
+ */
+abstract class AbstractMagnesiumDataInput extends AbstractNormalizedNodeDataInput {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractMagnesiumDataInput.class);
+
+    // Known singleton objects
+    private static final @NonNull Byte INT8_0 = 0;
+    private static final @NonNull Short INT16_0 = 0;
+    private static final @NonNull Integer INT32_0 = 0;
+    private static final @NonNull Long INT64_0 = 0L;
+    private static final @NonNull Uint8 UINT8_0 = Uint8.valueOf(0);
+    private static final @NonNull Uint16 UINT16_0 = Uint16.valueOf(0);
+    private static final @NonNull Uint32 UINT32_0 = Uint32.valueOf(0);
+    private static final @NonNull Uint64 UINT64_0 = Uint64.valueOf(0);
+    private static final byte @NonNull[] BINARY_0 = new byte[0];
+    private static final @NonNull AugmentationIdentifier EMPTY_AID = AugmentationIdentifier.create(ImmutableSet.of());
+
+    private final List<AugmentationIdentifier> codedAugments = new ArrayList<>();
+    private final List<NodeIdentifier> codedNodeIdentifiers = new ArrayList<>();
+    private final List<QNameModule> codedModules = new ArrayList<>();
+    private final List<String> codedStrings = new ArrayList<>();
+
+    AbstractMagnesiumDataInput(final DataInput input) {
+        super(input);
+    }
+
+    @Override
+    public final void streamNormalizedNode(final NormalizedNodeStreamWriter writer) throws IOException {
+        streamNormalizedNode(requireNonNull(writer), null, input.readByte());
+    }
+
+    private void streamNormalizedNode(final NormalizedNodeStreamWriter writer, final PathArgument parent,
+            final byte nodeHeader) throws IOException {
+        switch (nodeHeader & MagnesiumNode.TYPE_MASK) {
+            case MagnesiumNode.NODE_LEAF:
+                streamLeaf(writer, parent, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_CONTAINER:
+                streamContainer(writer, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_LIST:
+                streamList(writer, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_MAP:
+                streamMap(writer, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_MAP_ORDERED:
+                streamMapOrdered(writer, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_LEAFSET:
+                streamLeafset(writer, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_LEAFSET_ORDERED:
+                streamLeafsetOrdered(writer, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_CHOICE:
+                streamChoice(writer, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_AUGMENTATION:
+                streamAugmentation(writer, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_ANYXML:
+                streamAnyxml(writer, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_ANYXML_MODELED:
+                streamAnyxmlModeled(writer, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_LIST_ENTRY:
+                streamListEntry(writer, parent, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_LEAFSET_ENTRY:
+                streamLeafsetEntry(writer, parent, nodeHeader);
+                break;
+            case MagnesiumNode.NODE_MAP_ENTRY:
+                streamMapEntry(writer, parent, nodeHeader);
+                break;
+            default:
+                throw new InvalidNormalizedNodeStreamException("Unexpected node header " + nodeHeader);
+        }
+    }
+
+    private void streamAnyxml(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
+        final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
+        LOG.trace("Streaming anyxml node {}", identifier);
+
+        final DOMSource value = readDOMSource();
+        writer.startAnyxmlNode(identifier);
+        writer.domSourceValue(value);
+        writer.endNode();
+    }
+
+    private void streamAnyxmlModeled(final NormalizedNodeStreamWriter writer, final byte nodeHeader)
+            throws IOException {
+        // TODO: decide how to deal with these
+        throw new UnsupportedOperationException("Reading YANG-modeled anyxml was never supported");
+    }
+
+    private void streamAugmentation(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
+        final AugmentationIdentifier augIdentifier = decodeAugmentationIdentifier(nodeHeader);
+        LOG.trace("Streaming augmentation node {}", augIdentifier);
+        writer.startAugmentationNode(augIdentifier);
+        commonStreamContainer(writer, augIdentifier);
+    }
+
+    private void streamChoice(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
+        final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
+        LOG.trace("Streaming choice node {}", identifier);
+        writer.startChoiceNode(identifier, UNKNOWN_SIZE);
+        commonStreamContainer(writer, identifier);
+    }
+
+    private void streamContainer(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
+        final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
+        LOG.trace("Streaming container node {}", identifier);
+        writer.startContainerNode(identifier, UNKNOWN_SIZE);
+        commonStreamContainer(writer, identifier);
+    }
+
+    private void streamLeaf(final NormalizedNodeStreamWriter writer, final PathArgument parent, final byte nodeHeader)
+            throws IOException {
+        final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
+        LOG.trace("Streaming leaf node {}", identifier);
+        writer.startLeafNode(identifier);
+
+        final Object value;
+        if ((nodeHeader & MagnesiumNode.PREDICATE_ONE) == MagnesiumNode.PREDICATE_ONE) {
+            if (!(parent instanceof NodeIdentifierWithPredicates)) {
+                throw new InvalidNormalizedNodeStreamException("Invalid predicate leaf " + identifier + " in parent "
+                        + parent);
+            }
+
+            value = ((NodeIdentifierWithPredicates) parent).getValue(identifier.getNodeType());
+            if (value == null) {
+                throw new InvalidNormalizedNodeStreamException("Failed to find predicate leaf " + identifier
+                    + " in parent " + parent);
+            }
+        } else {
+            value = readLeafValue();
+        }
+
+        writer.scalarValue(value);
+        writer.endNode();
+    }
+
+    private void streamLeafset(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
+        final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
+        LOG.trace("Streaming leaf set node {}", identifier);
+        writer.startLeafSet(identifier, UNKNOWN_SIZE);
+        commonStreamContainer(writer, identifier);
+    }
+
+    private void streamLeafsetOrdered(final NormalizedNodeStreamWriter writer, final byte nodeHeader)
+            throws IOException {
+        final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
+        LOG.trace("Streaming ordered leaf set node {}", identifier);
+        writer.startOrderedLeafSet(identifier, UNKNOWN_SIZE);
+
+        commonStreamContainer(writer, identifier);
+    }
+
+    private void streamLeafsetEntry(final NormalizedNodeStreamWriter writer, final PathArgument parent,
+            final byte nodeHeader) throws IOException {
+        final NodeIdentifier nodeId = decodeNodeIdentifier(nodeHeader, parent);
+        final Object value = readLeafValue();
+        final NodeWithValue<Object> leafIdentifier = new NodeWithValue<>(nodeId.getNodeType(), value);
+        LOG.trace("Streaming leaf set entry node {}", leafIdentifier);
+        writer.startLeafSetEntryNode(leafIdentifier);
+        writer.scalarValue(value);
+        writer.endNode();
+    }
+
+    private void streamList(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
+        final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
+        writer.startUnkeyedList(identifier, UNKNOWN_SIZE);
+        commonStreamContainer(writer, identifier);
+    }
+
+    private void streamListEntry(final NormalizedNodeStreamWriter writer, final PathArgument parent,
+            final byte nodeHeader) throws IOException {
+        final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader, parent);
+        LOG.trace("Streaming unkeyed list item node {}", identifier);
+        writer.startUnkeyedListItem(identifier, UNKNOWN_SIZE);
+        commonStreamContainer(writer, identifier);
+    }
+
+    private void streamMap(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
+        final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
+        LOG.trace("Streaming map node {}", identifier);
+        writer.startMapNode(identifier, UNKNOWN_SIZE);
+        commonStreamContainer(writer, identifier);
+    }
+
+    private void streamMapOrdered(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
+        final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
+        LOG.trace("Streaming ordered map node {}", identifier);
+        writer.startOrderedMapNode(identifier, UNKNOWN_SIZE);
+        commonStreamContainer(writer, identifier);
+    }
+
+    private void streamMapEntry(final NormalizedNodeStreamWriter writer, final PathArgument parent,
+            final byte nodeHeader) throws IOException {
+        final NodeIdentifier nodeId = decodeNodeIdentifier(nodeHeader, parent);
+
+        final int size;
+        switch (mask(nodeHeader, MagnesiumNode.PREDICATE_MASK)) {
+            case MagnesiumNode.PREDICATE_ZERO:
+                size = 0;
+                break;
+            case MagnesiumNode.PREDICATE_ONE:
+                size = 1;
+                break;
+            case MagnesiumNode.PREDICATE_1B:
+                size = input.readUnsignedByte();
+                break;
+            case MagnesiumNode.PREDICATE_4B:
+                size = input.readInt();
+                break;
+            default:
+                // ISE on purpose: this should never ever happen
+                throw new IllegalStateException("Failed to decode NodeIdentifierWithPredicates size from header "
+                        + nodeHeader);
+        }
+
+        final NodeIdentifierWithPredicates identifier = readNodeIdentifierWithPredicates(nodeId.getNodeType(), size);
+        LOG.trace("Streaming map entry node {}", identifier);
+        writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
+        commonStreamContainer(writer, identifier);
+    }
+
+    private void commonStreamContainer(final NormalizedNodeStreamWriter writer, final PathArgument parent)
+            throws IOException {
+        for (byte nodeType = input.readByte(); nodeType != MagnesiumNode.NODE_END; nodeType = input.readByte()) {
+            streamNormalizedNode(writer, parent, nodeType);
+        }
+        writer.endNode();
+    }
+
+    private @NonNull NodeIdentifier decodeNodeIdentifier() throws IOException {
+        final QNameModule module = decodeQNameModule();
+        final String localName = readRefString();
+        final NodeIdentifier nodeId;
+        try {
+            nodeId = QNameFactory.getNodeIdentifier(module, localName);
+        } catch (ExecutionException e) {
+            throw new InvalidNormalizedNodeStreamException("Illegal QName module=" + module + " localName="
+                    + localName, e);
+        }
+
+        codedNodeIdentifiers.add(nodeId);
+        return nodeId;
+    }
+
+    private NodeIdentifier decodeNodeIdentifier(final byte nodeHeader) throws IOException {
+        return decodeNodeIdentifier(nodeHeader, null);
+    }
+
+    private NodeIdentifier decodeNodeIdentifier(final byte nodeHeader, final PathArgument parent) throws IOException {
+        final int index;
+        switch (nodeHeader & MagnesiumNode.ADDR_MASK) {
+            case MagnesiumNode.ADDR_DEFINE:
+                return readNodeIdentifier();
+            case MagnesiumNode.ADDR_LOOKUP_1B:
+                index = input.readUnsignedByte();
+                break;
+            case MagnesiumNode.ADDR_LOOKUP_4B:
+                index = input.readInt();
+                break;
+            case MagnesiumNode.ADDR_PARENT:
+                if (parent instanceof NodeIdentifier) {
+                    return (NodeIdentifier) parent;
+                }
+                throw new InvalidNormalizedNodeStreamException("Invalid node identifier reference to parent " + parent);
+            default:
+                throw new InvalidNormalizedNodeStreamException("Unexpected node identifier addressing in header "
+                        + nodeHeader);
+        }
+
+        try {
+            return codedNodeIdentifiers.get(index);
+        } catch (IndexOutOfBoundsException e) {
+            throw new InvalidNormalizedNodeStreamException("Invalid QName reference " + index, e);
+        }
+    }
+
+    private AugmentationIdentifier decodeAugmentationIdentifier(final byte nodeHeader) throws IOException {
+        final int index;
+        switch (nodeHeader & MagnesiumNode.ADDR_MASK) {
+            case MagnesiumNode.ADDR_DEFINE:
+                return readAugmentationIdentifier();
+            case MagnesiumNode.ADDR_LOOKUP_1B:
+                index = input.readUnsignedByte();
+                break;
+            case MagnesiumNode.ADDR_LOOKUP_4B:
+                index = input.readInt();
+                break;
+            default:
+                throw new InvalidNormalizedNodeStreamException(
+                    "Unexpected augmentation identifier addressing in header " + nodeHeader);
+        }
+
+        try {
+            return codedAugments.get(index);
+        } catch (IndexOutOfBoundsException e) {
+            throw new InvalidNormalizedNodeStreamException("Invalid augmentation identifier reference " + index, e);
+        }
+    }
+
+    @Override
+    public final YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
+        final byte type = input.readByte();
+        if (type == MagnesiumValue.YIID) {
+            return readYangInstanceIdentifier(input.readInt());
+        } else if (type >= MagnesiumValue.YIID_0) {
+            // Note 'byte' is range limited, so it is always '&& type <= MagnesiumValue.YIID_31'
+            return readYangInstanceIdentifier(type - MagnesiumValue.YIID_0);
+        } else {
+            throw new InvalidNormalizedNodeStreamException("Unexpected YangInstanceIdentifier type " + type);
+        }
+    }
+
+    private @NonNull YangInstanceIdentifier readYangInstanceIdentifier(final int size) throws IOException {
+        if (size > 0) {
+            final Builder<PathArgument> builder = ImmutableList.builderWithExpectedSize(size);
+            for (int i = 0; i < size; ++i) {
+                builder.add(readPathArgument());
+            }
+            return YangInstanceIdentifier.create(builder.build());
+        } else if (size == 0) {
+            return YangInstanceIdentifier.empty();
+        } else {
+            throw new InvalidNormalizedNodeStreamException("Invalid YangInstanceIdentifier size " + size);
+        }
+    }
+
+    @Override
+    public final QName readQName() throws IOException {
+        final byte type = input.readByte();
+        switch (type) {
+            case MagnesiumValue.QNAME:
+                return decodeQName();
+            case MagnesiumValue.QNAME_REF_1B:
+                return decodeQNameRef1();
+            case MagnesiumValue.QNAME_REF_2B:
+                return decodeQNameRef2();
+            case MagnesiumValue.QNAME_REF_4B:
+                return decodeQNameRef4();
+            default:
+                throw new InvalidNormalizedNodeStreamException("Unexpected QName type " + type);
+        }
+    }
+
+    @Override
+    public final PathArgument readPathArgument() throws IOException {
+        final byte header = input.readByte();
+        switch (header & MagnesiumPathArgument.TYPE_MASK) {
+            case MagnesiumPathArgument.AUGMENTATION_IDENTIFIER:
+                return readAugmentationIdentifier(header);
+            case MagnesiumPathArgument.NODE_IDENTIFIER:
+                verifyPathIdentifierOnly(header);
+                return readNodeIdentifier(header);
+            case MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES:
+                return readNodeIdentifierWithPredicates(header);
+            case MagnesiumPathArgument.NODE_WITH_VALUE:
+                verifyPathIdentifierOnly(header);
+                return readNodeWithValue(header);
+            case MagnesiumPathArgument.MOUNTPOINT_IDENTIFIER:
+                verifyPathIdentifierOnly(header);
+                return MountPointIdentifier.create(readNodeIdentifier(header).getNodeType());
+            default:
+                throw new InvalidNormalizedNodeStreamException("Unexpected PathArgument header " + header);
+        }
+    }
+
+    private AugmentationIdentifier readAugmentationIdentifier() throws IOException {
+        final AugmentationIdentifier result = readAugmentationIdentifier(input.readInt());
+        codedAugments.add(result);
+        return result;
+    }
+
+    private AugmentationIdentifier readAugmentationIdentifier(final byte header) throws IOException {
+        final byte count = mask(header, MagnesiumPathArgument.AID_COUNT_MASK);
+        switch (count) {
+            case MagnesiumPathArgument.AID_COUNT_1B:
+                return readAugmentationIdentifier(input.readUnsignedByte());
+            case MagnesiumPathArgument.AID_COUNT_2B:
+                return readAugmentationIdentifier(input.readUnsignedShort());
+            case MagnesiumPathArgument.AID_COUNT_4B:
+                return readAugmentationIdentifier(input.readInt());
+            default:
+                return readAugmentationIdentifier(rshift(count, MagnesiumPathArgument.AID_COUNT_SHIFT));
+        }
+    }
+
+    private AugmentationIdentifier readAugmentationIdentifier(final int size) throws IOException {
+        if (size > 0) {
+            final List<QName> qnames = new ArrayList<>(size);
+            for (int i = 0; i < size; ++i) {
+                qnames.add(readQName());
+            }
+            return AugmentationIdentifier.create(ImmutableSet.copyOf(qnames));
+        } else if (size == 0) {
+            return EMPTY_AID;
+        } else {
+            throw new InvalidNormalizedNodeStreamException("Invalid augmentation identifier size " + size);
+        }
+    }
+
+    private NodeIdentifier readNodeIdentifier() throws IOException {
+        return decodeNodeIdentifier();
+    }
+
+    private NodeIdentifier readNodeIdentifier(final byte header) throws IOException {
+        switch (header & MagnesiumPathArgument.QNAME_MASK) {
+            case MagnesiumPathArgument.QNAME_DEF:
+                return decodeNodeIdentifier();
+            case MagnesiumPathArgument.QNAME_REF_1B:
+                return decodeNodeIdentifierRef1();
+            case MagnesiumPathArgument.QNAME_REF_2B:
+                return decodeNodeIdentifierRef2();
+            case MagnesiumPathArgument.QNAME_REF_4B:
+                return decodeNodeIdentifierRef4();
+            default:
+                throw new InvalidNormalizedNodeStreamException("Invalid QName coding in " + header);
+        }
+    }
+
+    private NodeIdentifierWithPredicates readNodeIdentifierWithPredicates(final byte header) throws IOException {
+        final QName qname = readNodeIdentifier(header).getNodeType();
+        switch (mask(header, MagnesiumPathArgument.SIZE_MASK)) {
+            case MagnesiumPathArgument.SIZE_1B:
+                return readNodeIdentifierWithPredicates(qname, input.readUnsignedByte());
+            case MagnesiumPathArgument.SIZE_2B:
+                return readNodeIdentifierWithPredicates(qname, input.readUnsignedShort());
+            case MagnesiumPathArgument.SIZE_4B:
+                return readNodeIdentifierWithPredicates(qname, input.readInt());
+            default:
+                return readNodeIdentifierWithPredicates(qname, rshift(header, MagnesiumPathArgument.SIZE_SHIFT));
+        }
+    }
+
+    private NodeIdentifierWithPredicates readNodeIdentifierWithPredicates(final QName qname, final int size)
+            throws IOException {
+        if (size == 1) {
+            return NodeIdentifierWithPredicates.of(qname, readQName(), readLeafValue());
+        } else if (size > 1) {
+            final ImmutableMap.Builder<QName, Object> builder = ImmutableMap.builderWithExpectedSize(size);
+            for (int i = 0; i < size; ++i) {
+                builder.put(readQName(), readLeafValue());
+            }
+            return NodeIdentifierWithPredicates.of(qname, builder.build());
+        } else if (size == 0) {
+            return NodeIdentifierWithPredicates.of(qname);
+        } else {
+            throw new InvalidNormalizedNodeStreamException("Invalid predicate count " + size);
+        }
+    }
+
+    private NodeWithValue<?> readNodeWithValue(final byte header) throws IOException {
+        final QName qname = readNodeIdentifier(header).getNodeType();
+        return new NodeWithValue<>(qname, readLeafValue());
+    }
+
+    private static void verifyPathIdentifierOnly(final byte header) throws InvalidNormalizedNodeStreamException {
+        if (mask(header, MagnesiumPathArgument.SIZE_MASK) != 0) {
+            throw new InvalidNormalizedNodeStreamException("Invalid path argument header " + header);
+        }
+    }
+
+    private @NonNull NodeIdentifier decodeNodeIdentifierRef1() throws IOException {
+        return lookupNodeIdentifier(input.readUnsignedByte());
+    }
+
+    private @NonNull NodeIdentifier decodeNodeIdentifierRef2() throws IOException {
+        return lookupNodeIdentifier(input.readUnsignedShort() + 256);
+    }
+
+    private @NonNull NodeIdentifier decodeNodeIdentifierRef4() throws IOException {
+        return lookupNodeIdentifier(input.readInt());
+    }
+
+    private @NonNull QName decodeQName() throws IOException {
+        return decodeNodeIdentifier().getNodeType();
+    }
+
+    private @NonNull QName decodeQNameRef1() throws IOException {
+        return lookupQName(input.readUnsignedByte());
+    }
+
+    private @NonNull QName decodeQNameRef2() throws IOException {
+        return lookupQName(input.readUnsignedShort() + 256);
+    }
+
+    private @NonNull QName decodeQNameRef4() throws IOException {
+        return lookupQName(input.readInt());
+    }
+
+    private @NonNull QNameModule decodeQNameModule() throws IOException {
+        final byte type = input.readByte();
+        final int index;
+        switch (type) {
+            case MagnesiumValue.MODREF_1B:
+                index = input.readUnsignedByte();
+                break;
+            case MagnesiumValue.MODREF_2B:
+                index = input.readUnsignedShort() + 256;
+                break;
+            case MagnesiumValue.MODREF_4B:
+                index = input.readInt();
+                break;
+            default:
+                return decodeQNameModuleDef(type);
+        }
+
+        try {
+            return codedModules.get(index);
+        } catch (IndexOutOfBoundsException e) {
+            throw new InvalidNormalizedNodeStreamException("Invalid QNameModule reference " + index, e);
+        }
+    }
+
+    // QNameModule definition, i.e. two encoded strings
+    private @NonNull QNameModule decodeQNameModuleDef(final byte type) throws IOException {
+        final String namespace = readRefString(type);
+
+        final byte refType = input.readByte();
+        final String revision = refType == MagnesiumValue.STRING_EMPTY ? null : readRefString(refType);
+        final QNameModule module;
+        try {
+            module = QNameFactory.createModule(namespace, revision);
+        } catch (UncheckedExecutionException e) {
+            throw new InvalidNormalizedNodeStreamException("Illegal QNameModule ns=" + namespace + " rev=" + revision,
+                e);
+        }
+
+        codedModules.add(module);
+        return module;
+    }
+
+    private @NonNull String readRefString() throws IOException {
+        return readRefString(input.readByte());
+    }
+
+    private @NonNull String readRefString(final byte type) throws IOException {
+        final String str;
+        switch (type) {
+            case MagnesiumValue.STRING_REF_1B:
+                return lookupString(input.readUnsignedByte());
+            case MagnesiumValue.STRING_REF_2B:
+                return lookupString(input.readUnsignedShort() + 256);
+            case MagnesiumValue.STRING_REF_4B:
+                return lookupString(input.readInt());
+            case MagnesiumValue.STRING_EMPTY:
+                return "";
+            case MagnesiumValue.STRING_2B:
+                str = readString2();
+                break;
+            case MagnesiumValue.STRING_4B:
+                str = readString4();
+                break;
+            case MagnesiumValue.STRING_CHARS:
+                str = readCharsString();
+                break;
+            case MagnesiumValue.STRING_UTF:
+                str = input.readUTF();
+                break;
+            default:
+                throw new InvalidNormalizedNodeStreamException("Unexpected String type " + type);
+        }
+
+        // TODO: consider interning Strings -- that would help with bits, but otherwise it's probably not worth it
+        codedStrings.add(verifyNotNull(str));
+        return str;
+    }
+
+    private @NonNull String readString() throws IOException {
+        final byte type = input.readByte();
+        switch (type) {
+            case MagnesiumValue.STRING_EMPTY:
+                return "";
+            case MagnesiumValue.STRING_UTF:
+                return input.readUTF();
+            case MagnesiumValue.STRING_2B:
+                return readString2();
+            case MagnesiumValue.STRING_4B:
+                return readString4();
+            case MagnesiumValue.STRING_CHARS:
+                return readCharsString();
+            default:
+                throw new InvalidNormalizedNodeStreamException("Unexpected String type " + type);
+        }
+    }
+
+    private @NonNull String readString2() throws IOException {
+        return readByteString(input.readUnsignedShort());
+    }
+
+    private @NonNull String readString4() throws IOException {
+        return readByteString(input.readInt());
+    }
+
+    private @NonNull String readByteString(final int size) throws IOException {
+        if (size > 0) {
+            final byte[] bytes = new byte[size];
+            input.readFully(bytes);
+            return new String(bytes, StandardCharsets.UTF_8);
+        } else if (size == 0) {
+            return "";
+        } else {
+            throw new InvalidNormalizedNodeStreamException("Invalid String bytes length " + size);
+        }
+    }
+
+    private @NonNull String readCharsString() throws IOException {
+        final int size = input.readInt();
+        if (size > 0) {
+            final char[] chars = new char[size];
+            for (int i = 0; i < size; ++i) {
+                chars[i] = input.readChar();
+            }
+            return String.valueOf(chars);
+        } else if (size == 0) {
+            return "";
+        } else {
+            throw new InvalidNormalizedNodeStreamException("Invalid String chars length " + size);
+        }
+    }
+
+    private @NonNull NodeIdentifier lookupNodeIdentifier(final int index) throws InvalidNormalizedNodeStreamException {
+        try {
+            return codedNodeIdentifiers.get(index);
+        } catch (IndexOutOfBoundsException e) {
+            throw new InvalidNormalizedNodeStreamException("Invalid QName reference " + index, e);
+        }
+    }
+
+    private @NonNull QName lookupQName(final int index) throws InvalidNormalizedNodeStreamException {
+        return lookupNodeIdentifier(index).getNodeType();
+    }
+
+    private @NonNull String lookupString(final int index) throws InvalidNormalizedNodeStreamException {
+        try {
+            return codedStrings.get(index);
+        } catch (IndexOutOfBoundsException e) {
+            throw new InvalidNormalizedNodeStreamException("Invalid String reference " + index, e);
+        }
+    }
+
+    private @NonNull DOMSource readDOMSource() throws IOException {
+        final String str = readString();
+        try {
+            return new DOMSource(UntrustedXML.newDocumentBuilder().parse(new InputSource(new StringReader(str)))
+                .getDocumentElement());
+        } catch (SAXException e) {
+            throw new IOException("Error parsing XML: " + str, e);
+        }
+    }
+
+    private @NonNull Object readLeafValue() throws IOException {
+        final byte type = input.readByte();
+        switch (type) {
+            case MagnesiumValue.BOOLEAN_FALSE:
+                return Boolean.FALSE;
+            case MagnesiumValue.BOOLEAN_TRUE:
+                return Boolean.TRUE;
+            case MagnesiumValue.EMPTY:
+                return Empty.getInstance();
+            case MagnesiumValue.INT8:
+                return input.readByte();
+            case MagnesiumValue.INT8_0:
+                return INT8_0;
+            case MagnesiumValue.INT16:
+                return input.readShort();
+            case MagnesiumValue.INT16_0:
+                return INT16_0;
+            case MagnesiumValue.INT32:
+                return input.readInt();
+            case MagnesiumValue.INT32_0:
+                return INT32_0;
+            case MagnesiumValue.INT32_2B:
+                return input.readShort() & 0xFFFF;
+            case MagnesiumValue.INT64:
+                return input.readLong();
+            case MagnesiumValue.INT64_0:
+                return INT64_0;
+            case MagnesiumValue.INT64_4B:
+                return input.readInt() & 0xFFFFFFFFL;
+            case MagnesiumValue.UINT8:
+                return Uint8.fromByteBits(input.readByte());
+            case MagnesiumValue.UINT8_0:
+                return UINT8_0;
+            case MagnesiumValue.UINT16:
+                return Uint16.fromShortBits(input.readShort());
+            case MagnesiumValue.UINT16_0:
+                return UINT16_0;
+            case MagnesiumValue.UINT32:
+                return Uint32.fromIntBits(input.readInt());
+            case MagnesiumValue.UINT32_0:
+                return UINT32_0;
+            case MagnesiumValue.UINT32_2B:
+                return Uint32.fromIntBits(input.readShort() & 0xFFFF);
+            case MagnesiumValue.UINT64:
+                return Uint64.fromLongBits(input.readLong());
+            case MagnesiumValue.UINT64_0:
+                return UINT64_0;
+            case MagnesiumValue.UINT64_4B:
+                return Uint64.fromLongBits(input.readInt() & 0xFFFFFFFFL);
+            case MagnesiumValue.BIGDECIMAL:
+                // FIXME: use string -> BigDecimal cache
+                return new BigDecimal(input.readUTF());
+            case MagnesiumValue.BIGINTEGER:
+                return readBigInteger();
+            case MagnesiumValue.STRING_EMPTY:
+                return "";
+            case MagnesiumValue.STRING_UTF:
+                return input.readUTF();
+            case MagnesiumValue.STRING_2B:
+                return readString2();
+            case MagnesiumValue.STRING_4B:
+                return readString4();
+            case MagnesiumValue.STRING_CHARS:
+                return readCharsString();
+            case MagnesiumValue.BINARY_0:
+                return BINARY_0;
+            case MagnesiumValue.BINARY_1B:
+                return readBinary(128 + input.readUnsignedByte());
+            case MagnesiumValue.BINARY_2B:
+                return readBinary(384 + input.readUnsignedShort());
+            case MagnesiumValue.BINARY_4B:
+                return readBinary(input.readInt());
+            case MagnesiumValue.YIID_0:
+                return YangInstanceIdentifier.empty();
+            case MagnesiumValue.YIID:
+                return readYangInstanceIdentifier(input.readInt());
+            case MagnesiumValue.QNAME:
+                return decodeQName();
+            case MagnesiumValue.QNAME_REF_1B:
+                return decodeQNameRef1();
+            case MagnesiumValue.QNAME_REF_2B:
+                return decodeQNameRef2();
+            case MagnesiumValue.QNAME_REF_4B:
+                return decodeQNameRef4();
+            case MagnesiumValue.BITS_0:
+                return ImmutableSet.of();
+            case MagnesiumValue.BITS_1B:
+                return readBits(input.readUnsignedByte() + 29);
+            case MagnesiumValue.BITS_2B:
+                return readBits(input.readUnsignedShort() + 285);
+            case MagnesiumValue.BITS_4B:
+                return readBits(input.readInt());
+
+            default:
+                if (type > MagnesiumValue.BINARY_0 && type <= MagnesiumValue.BINARY_127) {
+                    return readBinary(type - MagnesiumValue.BINARY_0);
+                } else if (type > MagnesiumValue.BITS_0 && type < MagnesiumValue.BITS_1B) {
+                    return readBits(type - MagnesiumValue.BITS_0);
+                } else if (type > MagnesiumValue.YIID_0) {
+                    // Note 'byte' is range limited, so it is always '&& type <= MagnesiumValue.YIID_31'
+                    return readYangInstanceIdentifier(type - MagnesiumValue.YIID_0);
+                } else {
+                    throw new InvalidNormalizedNodeStreamException("Invalid value type " + type);
+                }
+        }
+    }
+
+    abstract @NonNull BigInteger readBigInteger() throws IOException;
+
+    private byte @NonNull [] readBinary(final int size) throws IOException {
+        if (size > 0) {
+            final byte[] ret = new byte[size];
+            input.readFully(ret);
+            return ret;
+        } else if (size == 0) {
+            return BINARY_0;
+        } else {
+            throw new InvalidNormalizedNodeStreamException("Invalid binary length " + size);
+        }
+    }
+
+    private @NonNull ImmutableSet<String> readBits(final int size) throws IOException {
+        if (size > 0) {
+            final ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+            for (int i = 0; i < size; ++i) {
+                builder.add(readRefString());
+            }
+            return builder.build();
+        } else if (size == 0) {
+            return ImmutableSet.of();
+        } else {
+            throw new InvalidNormalizedNodeStreamException("Invalid bits length " + size);
+        }
+    }
+
+    private static byte mask(final byte header, final byte mask) {
+        return (byte) (header & mask);
+    }
+
+    private static int rshift(final byte header, final byte shift) {
+        return (header & 0xFF) >>> shift;
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractMagnesiumDataOutput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractMagnesiumDataOutput.java
new file mode 100644 (file)
index 0000000..9b3d5a3
--- /dev/null
@@ -0,0 +1,690 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.rfc8528.data.api.MountPointIdentifier;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.opendaylight.yangtools.yang.common.Uint32;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.opendaylight.yangtools.yang.common.Uint8;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract base class for NormalizedNodeDataOutput based on {@link MagnesiumNode}, {@link MagnesiumPathArgument} and
+ * {@link MagnesiumValue}.
+ */
+abstract class AbstractMagnesiumDataOutput extends AbstractNormalizedNodeDataOutput {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractMagnesiumDataOutput.class);
+
+    // Marker for encoding state when we have entered startLeafNode() within a startMapEntry() and that leaf corresponds
+    // to a key carried within NodeIdentifierWithPredicates.
+    private static final Object KEY_LEAF_STATE = new Object();
+    // Marker for nodes which have simple content and do not use END_NODE marker to terminate
+    private static final Object NO_ENDNODE_STATE = new Object();
+
+    private static final TransformerFactory TF = TransformerFactory.newInstance();
+
+    /**
+     * Stack tracking encoding state. In general we track the node identifier of the currently-open element, but there
+     * are a few other circumstances where we push other objects. See {@link #KEY_LEAF_STATE} and
+     * {@link #NO_ENDNODE_STATE}.
+     */
+    private final Deque<Object> stack = new ArrayDeque<>();
+
+    // Coding maps
+    private final Map<AugmentationIdentifier, Integer> aidCodeMap = new HashMap<>();
+    private final Map<QNameModule, Integer> moduleCodeMap = new HashMap<>();
+    private final Map<String, Integer> stringCodeMap = new HashMap<>();
+    private final Map<QName, Integer> qnameCodeMap = new HashMap<>();
+
+    AbstractMagnesiumDataOutput(final DataOutput output) {
+        super(output);
+    }
+
+    @Override
+    public final void startLeafNode(final NodeIdentifier name) throws IOException {
+        final Object current = stack.peek();
+        if (current instanceof NodeIdentifierWithPredicates) {
+            final QName qname = name.getNodeType();
+            if (((NodeIdentifierWithPredicates) current).containsKey(qname)) {
+                writeQNameNode(MagnesiumNode.NODE_LEAF | MagnesiumNode.PREDICATE_ONE, qname);
+                stack.push(KEY_LEAF_STATE);
+                return;
+            }
+        }
+
+        startSimpleNode(MagnesiumNode.NODE_LEAF, name);
+    }
+
+    @Override
+    public final void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        startQNameNode(MagnesiumNode.NODE_LEAFSET, name);
+    }
+
+    @Override
+    public final void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        startQNameNode(MagnesiumNode.NODE_LEAFSET_ORDERED, name);
+    }
+
+    @Override
+    public final void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
+        if (matchesParentQName(name.getNodeType())) {
+            output.writeByte(MagnesiumNode.NODE_LEAFSET_ENTRY);
+            stack.push(NO_ENDNODE_STATE);
+        } else {
+            startSimpleNode(MagnesiumNode.NODE_LEAFSET_ENTRY, name);
+        }
+    }
+
+    @Override
+    public final void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        startQNameNode(MagnesiumNode.NODE_CONTAINER, name);
+    }
+
+    @Override
+    public final void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        startQNameNode(MagnesiumNode.NODE_LIST, name);
+    }
+
+    @Override
+    public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        startInheritedNode(MagnesiumNode.NODE_LIST_ENTRY, name);
+    }
+
+    @Override
+    public final void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        startQNameNode(MagnesiumNode.NODE_MAP, name);
+    }
+
+    @Override
+    public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
+            throws IOException {
+        final int size = identifier.size();
+        if (size == 1) {
+            startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_ONE), identifier);
+        } else if (size == 0) {
+            startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_ZERO), identifier);
+        } else if (size < 256) {
+            startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_1B), identifier);
+            output.writeByte(size);
+        } else {
+            startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_4B), identifier);
+            output.writeInt(size);
+        }
+
+        writePredicates(identifier);
+    }
+
+    @Override
+    public final void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        startQNameNode(MagnesiumNode.NODE_MAP_ORDERED, name);
+    }
+
+    @Override
+    public final void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        startQNameNode(MagnesiumNode.NODE_CHOICE, name);
+    }
+
+    @Override
+    public final void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
+        final Integer code = aidCodeMap.get(identifier);
+        if (code == null) {
+            aidCodeMap.put(identifier, aidCodeMap.size());
+            output.writeByte(MagnesiumNode.NODE_AUGMENTATION | MagnesiumNode.ADDR_DEFINE);
+            final Set<QName> qnames = identifier.getPossibleChildNames();
+            output.writeInt(qnames.size());
+            for (QName qname : qnames) {
+                writeQNameInternal(qname);
+            }
+        } else {
+            writeNodeType(MagnesiumNode.NODE_AUGMENTATION, code);
+        }
+        stack.push(identifier);
+    }
+
+    @Override
+    public final void startAnyxmlNode(final NodeIdentifier name) throws IOException {
+        startSimpleNode(MagnesiumNode.NODE_ANYXML, name);
+    }
+
+    @Override
+    public final void domSourceValue(final DOMSource value) throws IOException {
+        final StringWriter writer = new StringWriter();
+        try {
+            TF.newTransformer().transform(value, new StreamResult(writer));
+        } catch (TransformerException e) {
+            throw new IOException("Error writing anyXml", e);
+        }
+        writeValue(writer.toString());
+    }
+
+    @Override
+    public final void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint)
+            throws IOException {
+        // FIXME: implement this
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final void endNode() throws IOException {
+        if (stack.pop() instanceof PathArgument) {
+            output.writeByte(MagnesiumNode.NODE_END);
+        }
+    }
+
+    @Override
+    public final void scalarValue(final Object value) throws IOException {
+        if (KEY_LEAF_STATE.equals(stack.peek())) {
+            LOG.trace("Inside a map entry key leaf, not emitting value {}", value);
+        } else {
+            writeObject(value);
+        }
+    }
+
+    @Override
+    final void writeQNameInternal(final QName qname) throws IOException {
+        final Integer code = qnameCodeMap.get(qname);
+        if (code == null) {
+            output.writeByte(MagnesiumValue.QNAME);
+            encodeQName(qname);
+        } else {
+            writeQNameRef(code);
+        }
+    }
+
+    @Override
+    final void writePathArgumentInternal(final PathArgument pathArgument) throws IOException {
+        if (pathArgument instanceof NodeIdentifier) {
+            writeNodeIdentifier((NodeIdentifier) pathArgument);
+        } else if (pathArgument instanceof NodeIdentifierWithPredicates) {
+            writeNodeIdentifierWithPredicates((NodeIdentifierWithPredicates) pathArgument);
+        } else if (pathArgument instanceof AugmentationIdentifier) {
+            writeAugmentationIdentifier((AugmentationIdentifier) pathArgument);
+        } else if (pathArgument instanceof NodeWithValue) {
+            writeNodeWithValue((NodeWithValue<?>) pathArgument);
+        } else if (pathArgument instanceof MountPointIdentifier) {
+            writeMountPointIdentifier((MountPointIdentifier) pathArgument);
+        } else {
+            throw new IOException("Unhandled PathArgument " + pathArgument);
+        }
+    }
+
+    private void writeAugmentationIdentifier(final AugmentationIdentifier identifier) throws IOException {
+        final Set<QName> qnames = identifier.getPossibleChildNames();
+        final int size = qnames.size();
+        if (size < 29) {
+            output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER
+                | size << MagnesiumPathArgument.AID_COUNT_SHIFT);
+        } else if (size < 256) {
+            output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_1B);
+            output.writeByte(size);
+        } else if (size < 65536) {
+            output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_2B);
+            output.writeShort(size);
+        } else {
+            output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_4B);
+            output.writeInt(size);
+        }
+
+        for (QName qname : qnames) {
+            writeQNameInternal(qname);
+        }
+    }
+
+    private void writeNodeIdentifier(final NodeIdentifier identifier) throws IOException {
+        writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_IDENTIFIER);
+    }
+
+    private void writeMountPointIdentifier(final MountPointIdentifier identifier) throws IOException {
+        writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.MOUNTPOINT_IDENTIFIER);
+    }
+
+    private void writeNodeIdentifierWithPredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
+        final int size = identifier.size();
+        if (size < 5) {
+            writePathArgumentQName(identifier.getNodeType(),
+                (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES
+                        | size << MagnesiumPathArgument.SIZE_SHIFT));
+        } else if (size < 256) {
+            writePathArgumentQName(identifier.getNodeType(),
+                (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_1B));
+            output.writeByte(size);
+        } else if (size < 65536) {
+            writePathArgumentQName(identifier.getNodeType(),
+                (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_2B));
+            output.writeShort(size);
+        } else {
+            writePathArgumentQName(identifier.getNodeType(),
+                (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_4B));
+            output.writeInt(size);
+        }
+
+        writePredicates(identifier);
+    }
+
+    private void writePredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
+        for (Entry<QName, Object> e : identifier.entrySet()) {
+            writeQNameInternal(e.getKey());
+            writeObject(e.getValue());
+        }
+    }
+
+    private void writeNodeWithValue(final NodeWithValue<?> identifier) throws IOException {
+        writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_WITH_VALUE);
+        writeObject(identifier.getValue());
+    }
+
+    private void writePathArgumentQName(final QName qname, final byte typeHeader) throws IOException {
+        final Integer code = qnameCodeMap.get(qname);
+        if (code != null) {
+            final int val = code;
+            if (val < 256) {
+                output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_1B);
+                output.writeByte(val);
+            } else if (val < 65792) {
+                output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_2B);
+                output.writeShort(val - 256);
+            } else {
+                output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_4B);
+                output.writeInt(val);
+            }
+        } else {
+            // implied '| MagnesiumPathArgument.QNAME_DEF'
+            output.writeByte(typeHeader);
+            encodeQName(qname);
+        }
+    }
+
+    @Override
+    final void writeYangInstanceIdentifierInternal(final YangInstanceIdentifier identifier) throws IOException {
+        writeValue(identifier);
+    }
+
+    private void writeObject(final @NonNull Object value) throws IOException {
+        if (value instanceof String) {
+            writeValue((String) value);
+        } else if (value instanceof Boolean) {
+            writeValue((Boolean) value);
+        } else if (value instanceof Byte) {
+            writeValue((Byte) value);
+        } else if (value instanceof Short) {
+            writeValue((Short) value);
+        } else if (value instanceof Integer) {
+            writeValue((Integer) value);
+        } else if (value instanceof Long) {
+            writeValue((Long) value);
+        } else if (value instanceof Uint8) {
+            writeValue((Uint8) value);
+        } else if (value instanceof Uint16) {
+            writeValue((Uint16) value);
+        } else if (value instanceof Uint32) {
+            writeValue((Uint32) value);
+        } else if (value instanceof Uint64) {
+            writeValue((Uint64) value);
+        } else if (value instanceof QName) {
+            writeQNameInternal((QName) value);
+        } else if (value instanceof YangInstanceIdentifier) {
+            writeValue((YangInstanceIdentifier) value);
+        } else if (value instanceof byte[]) {
+            writeValue((byte[]) value);
+        } else if (value instanceof Empty) {
+            output.writeByte(MagnesiumValue.EMPTY);
+        } else if (value instanceof Set) {
+            writeValue((Set<?>) value);
+        } else if (value instanceof BigDecimal) {
+            writeValue((BigDecimal) value);
+        } else if (value instanceof BigInteger) {
+            writeValue((BigInteger) value);
+        } else {
+            throw new IOException("Unhandled value type " + value.getClass());
+        }
+    }
+
+    private void writeValue(final boolean value) throws IOException {
+        output.writeByte(value ? MagnesiumValue.BOOLEAN_TRUE : MagnesiumValue.BOOLEAN_FALSE);
+    }
+
+    private void writeValue(final byte value) throws IOException {
+        if (value != 0) {
+            output.writeByte(MagnesiumValue.INT8);
+            output.writeByte(value);
+        } else {
+            output.writeByte(MagnesiumValue.INT8_0);
+        }
+    }
+
+    private void writeValue(final short value) throws IOException {
+        if (value != 0) {
+            output.writeByte(MagnesiumValue.INT16);
+            output.writeShort(value);
+        } else {
+            output.writeByte(MagnesiumValue.INT16_0);
+        }
+    }
+
+    private void writeValue(final int value) throws IOException {
+        if ((value & 0xFFFF0000) != 0) {
+            output.writeByte(MagnesiumValue.INT32);
+            output.writeInt(value);
+        } else if (value != 0) {
+            output.writeByte(MagnesiumValue.INT32_2B);
+            output.writeShort(value);
+        } else {
+            output.writeByte(MagnesiumValue.INT32_0);
+        }
+    }
+
+    private void writeValue(final long value) throws IOException {
+        if ((value & 0xFFFFFFFF00000000L) != 0) {
+            output.writeByte(MagnesiumValue.INT64);
+            output.writeLong(value);
+        } else if (value != 0) {
+            output.writeByte(MagnesiumValue.INT64_4B);
+            output.writeInt((int) value);
+        } else {
+            output.writeByte(MagnesiumValue.INT64_0);
+        }
+    }
+
+    private void writeValue(final Uint8 value) throws IOException {
+        final byte b = value.byteValue();
+        if (b != 0) {
+            output.writeByte(MagnesiumValue.UINT8);
+            output.writeByte(b);
+        } else {
+            output.writeByte(MagnesiumValue.UINT8_0);
+        }
+    }
+
+    private void writeValue(final Uint16 value) throws IOException {
+        final short s = value.shortValue();
+        if (s != 0) {
+            output.writeByte(MagnesiumValue.UINT16);
+            output.writeShort(s);
+        } else {
+            output.writeByte(MagnesiumValue.UINT16_0);
+        }
+    }
+
+    private void writeValue(final Uint32 value) throws IOException {
+        final int i = value.intValue();
+        if ((i & 0xFFFF0000) != 0) {
+            output.writeByte(MagnesiumValue.UINT32);
+            output.writeInt(i);
+        } else if (i != 0) {
+            output.writeByte(MagnesiumValue.UINT32_2B);
+            output.writeShort(i);
+        } else {
+            output.writeByte(MagnesiumValue.UINT32_0);
+        }
+    }
+
+    private void writeValue(final Uint64 value) throws IOException {
+        final long l = value.longValue();
+        if ((l & 0xFFFFFFFF00000000L) != 0) {
+            output.writeByte(MagnesiumValue.UINT64);
+            output.writeLong(l);
+        } else if (l != 0) {
+            output.writeByte(MagnesiumValue.UINT64_4B);
+            output.writeInt((int) l);
+        } else {
+            output.writeByte(MagnesiumValue.UINT64_0);
+        }
+    }
+
+    private void writeValue(final BigDecimal value) throws IOException {
+        output.writeByte(MagnesiumValue.BIGDECIMAL);
+        output.writeUTF(value.toString());
+    }
+
+    abstract void writeValue(BigInteger value) throws IOException;
+
+    private void writeValue(final String value) throws IOException {
+        if (value.isEmpty()) {
+            output.writeByte(MagnesiumValue.STRING_EMPTY);
+        } else if (value.length() <= Short.MAX_VALUE / 2) {
+            output.writeByte(MagnesiumValue.STRING_UTF);
+            output.writeUTF(value);
+        } else if (value.length() <= 1048576) {
+            final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
+            if (bytes.length < 65536) {
+                output.writeByte(MagnesiumValue.STRING_2B);
+                output.writeShort(bytes.length);
+            } else {
+                output.writeByte(MagnesiumValue.STRING_4B);
+                output.writeInt(bytes.length);
+            }
+            output.write(bytes);
+        } else {
+            output.writeByte(MagnesiumValue.STRING_CHARS);
+            output.writeInt(value.length());
+            output.writeChars(value);
+        }
+    }
+
+    private void writeValue(final byte[] value) throws IOException {
+        if (value.length < 128) {
+            output.writeByte(MagnesiumValue.BINARY_0 + value.length);
+        } else if (value.length < 384) {
+            output.writeByte(MagnesiumValue.BINARY_1B);
+            output.writeByte(value.length - 128);
+        } else if (value.length < 65920) {
+            output.writeByte(MagnesiumValue.BINARY_2B);
+            output.writeShort(value.length - 384);
+        } else {
+            output.writeByte(MagnesiumValue.BINARY_4B);
+            output.writeInt(value.length);
+        }
+        output.write(value);
+    }
+
+    private void writeValue(final YangInstanceIdentifier value) throws IOException {
+        final List<PathArgument> args = value.getPathArguments();
+        final int size = args.size();
+        if (size > 31) {
+            output.writeByte(MagnesiumValue.YIID);
+            output.writeInt(size);
+        } else {
+            output.writeByte(MagnesiumValue.YIID_0 + size);
+        }
+        for (PathArgument arg : args) {
+            writePathArgumentInternal(arg);
+        }
+    }
+
+    private void writeValue(final Set<?> value) throws IOException {
+        final int size = value.size();
+        if (size < 29) {
+            output.writeByte(MagnesiumValue.BITS_0 + size);
+        } else if (size < 285) {
+            output.writeByte(MagnesiumValue.BITS_1B);
+            output.writeByte(size - 29);
+        } else if (size < 65821) {
+            output.writeByte(MagnesiumValue.BITS_2B);
+            output.writeShort(size - 285);
+        } else {
+            output.writeByte(MagnesiumValue.BITS_4B);
+            output.writeInt(size);
+        }
+
+        for (Object bit : value) {
+            checkArgument(bit instanceof String, "Expected value type to be String but was %s", bit);
+            encodeString((String) bit);
+        }
+    }
+
+    // Check if the proposed QName matches the parent. This is only effective if the parent is identified by
+    // NodeIdentifier -- which is typically true
+    private boolean matchesParentQName(final QName qname) {
+        final Object current = stack.peek();
+        return current instanceof NodeIdentifier && qname.equals(((NodeIdentifier) current).getNodeType());
+    }
+
+    // Start an END_NODE-terminated node, which typically has a QName matching the parent. If that is the case we emit
+    // a parent reference instead of an explicit QName reference -- saving at least one byte
+    private void startInheritedNode(final byte type, final PathArgument name) throws IOException {
+        final QName qname = name.getNodeType();
+        if (matchesParentQName(qname)) {
+            output.write(type);
+        } else {
+            writeQNameNode(type, qname);
+        }
+        stack.push(name);
+    }
+
+    // Start an END_NODE-terminated node, which needs its QName encoded
+    private void startQNameNode(final byte type, final PathArgument name) throws IOException {
+        writeQNameNode(type, name.getNodeType());
+        stack.push(name);
+    }
+
+    // Start a simple node, which is not terminated through END_NODE and encode its QName
+    private void startSimpleNode(final byte type, final PathArgument name) throws IOException {
+        writeQNameNode(type, name.getNodeType());
+        stack.push(NO_ENDNODE_STATE);
+    }
+
+    // Encode a QName-based (i.e. NodeIdentifier*) node with a particular QName. This will either result in a QName
+    // definition, or a reference, where this is encoded along with the node type.
+    private void writeQNameNode(final int type, final @NonNull QName qname) throws IOException {
+        final Integer code = qnameCodeMap.get(qname);
+        if (code == null) {
+            output.writeByte(type | MagnesiumNode.ADDR_DEFINE);
+            encodeQName(qname);
+        } else {
+            writeNodeType(type, code);
+        }
+    }
+
+    // Write a node type + lookup
+    private void writeNodeType(final int type, final int code) throws IOException {
+        if (code <= 255) {
+            output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_1B);
+            output.writeByte(code);
+        } else {
+            output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_4B);
+            output.writeInt(code);
+        }
+    }
+
+    // Encode a QName using lookup tables, resuling either in a reference to an existing entry, or emitting two
+    // String values.
+    private void encodeQName(final @NonNull QName qname) throws IOException {
+        final Integer prev = qnameCodeMap.put(qname, qnameCodeMap.size());
+        if (prev != null) {
+            throw new IOException("Internal coding error: attempted to re-encode " + qname + "%s already encoded as "
+                    + prev);
+        }
+
+        final QNameModule module = qname.getModule();
+        final Integer code = moduleCodeMap.get(module);
+        if (code == null) {
+            moduleCodeMap.put(module, moduleCodeMap.size());
+            encodeString(module.getNamespace().toString());
+            final Optional<Revision> rev = module.getRevision();
+            if (rev.isPresent()) {
+                encodeString(rev.get().toString());
+            } else {
+                output.writeByte(MagnesiumValue.STRING_EMPTY);
+            }
+        } else {
+            writeModuleRef(code);
+        }
+        encodeString(qname.getLocalName());
+    }
+
+    // Encode a String using lookup tables, resulting either in a reference to an existing entry, or emitting as
+    // a literal value
+    private void encodeString(final @NonNull String str) throws IOException {
+        final Integer code = stringCodeMap.get(str);
+        if (code != null) {
+            writeRef(code);
+        } else {
+            stringCodeMap.put(str, stringCodeMap.size());
+            writeValue(str);
+        }
+    }
+
+    // Write a QName with a lookup table reference. This is a combination of asserting the value is a QName plus
+    // the effects of writeRef()
+    private void writeQNameRef(final int code) throws IOException {
+        final int val = code;
+        if (val < 256) {
+            output.writeByte(MagnesiumValue.QNAME_REF_1B);
+            output.writeByte(val);
+        } else if (val < 65792) {
+            output.writeByte(MagnesiumValue.QNAME_REF_2B);
+            output.writeShort(val - 256);
+        } else {
+            output.writeByte(MagnesiumValue.QNAME_REF_4B);
+            output.writeInt(val);
+        }
+    }
+
+    // Write a lookup table reference, which table is being referenced is implied by the caller
+    private void writeRef(final int code) throws IOException {
+        final int val = code;
+        if (val < 256) {
+            output.writeByte(MagnesiumValue.STRING_REF_1B);
+            output.writeByte(val);
+        } else if (val < 65792) {
+            output.writeByte(MagnesiumValue.STRING_REF_2B);
+            output.writeShort(val - 256);
+        } else {
+            output.writeByte(MagnesiumValue.STRING_REF_4B);
+            output.writeInt(val);
+        }
+    }
+
+    // Write a lookup module table reference, which table is being referenced is implied by the caller
+    private void writeModuleRef(final int code) throws IOException {
+        final int val = code;
+        if (val < 256) {
+            output.writeByte(MagnesiumValue.MODREF_1B);
+            output.writeByte(val);
+        } else if (val < 65792) {
+            output.writeByte(MagnesiumValue.MODREF_2B);
+            output.writeShort(val - 256);
+        } else {
+            output.writeByte(MagnesiumValue.MODREF_4B);
+            output.writeInt(val);
+        }
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractNormalizedNodeDataInput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractNormalizedNodeDataInput.java
new file mode 100644 (file)
index 0000000..43c066e
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import java.io.DataInput;
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+abstract class AbstractNormalizedNodeDataInput extends ForwardingDataInput implements NormalizedNodeDataInput {
+    // Visible for subclasses
+    final @NonNull DataInput input;
+
+    AbstractNormalizedNodeDataInput(final DataInput input) {
+        this.input = requireNonNull(input);
+    }
+
+    @Override
+    final DataInput delegate() {
+        return input;
+    }
+
+    @Override
+    public final SchemaPath readSchemaPath() throws IOException {
+        final boolean absolute = input.readBoolean();
+        final int size = input.readInt();
+
+        final Builder<QName> qnames = ImmutableList.builderWithExpectedSize(size);
+        for (int i = 0; i < size; ++i) {
+            qnames.add(readQName());
+        }
+        return SchemaPath.create(qnames.build(), absolute);
+    }
+
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractNormalizedNodeDataOutput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractNormalizedNodeDataOutput.java
new file mode 100755 (executable)
index 0000000..1860185
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * 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.binfmt;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+/**
+ * Abstract base class for implementing {@link NormalizedNodeDataOutput} contract. This class uses
+ * {@link NormalizedNodeStreamWriter} as an internal interface for performing the actual NormalizedNode writeout,
+ * i.e. it will defer to a {@link NormalizedNodeWriter} instance.
+ *
+ * <p>
+ * As such, this is an implementation detail not exposed from this package, hence implementations can rely on the
+ * stream being initialized with a header and version.
+ */
+abstract class AbstractNormalizedNodeDataOutput implements NormalizedNodeDataOutput, NormalizedNodeStreamWriter {
+    // Visible for subclasses
+    final DataOutput output;
+
+    private NormalizedNodeWriter normalizedNodeWriter;
+    private boolean headerWritten;
+
+    AbstractNormalizedNodeDataOutput(final DataOutput output) {
+        this.output = requireNonNull(output);
+    }
+
+
+    private void ensureHeaderWritten() throws IOException {
+        if (!headerWritten) {
+            output.writeByte(TokenTypes.SIGNATURE_MARKER);
+            output.writeShort(streamVersion());
+            headerWritten = true;
+        }
+    }
+
+    @Override
+    public final void write(final int value) throws IOException {
+        ensureHeaderWritten();
+        output.write(value);
+    }
+
+    @Override
+    public final void write(final byte[] bytes) throws IOException {
+        ensureHeaderWritten();
+        output.write(bytes);
+    }
+
+    @Override
+    public final void write(final byte[] bytes, final int off, final int len) throws IOException {
+        ensureHeaderWritten();
+        output.write(bytes, off, len);
+    }
+
+    @Override
+    public final void writeBoolean(final boolean value) throws IOException {
+        ensureHeaderWritten();
+        output.writeBoolean(value);
+    }
+
+    @Override
+    public final void writeByte(final int value) throws IOException {
+        ensureHeaderWritten();
+        output.writeByte(value);
+    }
+
+    @Override
+    public final void writeShort(final int value) throws IOException {
+        ensureHeaderWritten();
+        output.writeShort(value);
+    }
+
+    @Override
+    public final void writeChar(final int value) throws IOException {
+        ensureHeaderWritten();
+        output.writeChar(value);
+    }
+
+    @Override
+    public final void writeInt(final int value) throws IOException {
+        ensureHeaderWritten();
+        output.writeInt(value);
+    }
+
+    @Override
+    public final void writeLong(final long value) throws IOException {
+        ensureHeaderWritten();
+        output.writeLong(value);
+    }
+
+    @Override
+    public final void writeFloat(final float value) throws IOException {
+        ensureHeaderWritten();
+        output.writeFloat(value);
+    }
+
+    @Override
+    public final void writeDouble(final double value) throws IOException {
+        ensureHeaderWritten();
+        output.writeDouble(value);
+    }
+
+    @Override
+    public final void writeBytes(final String str) throws IOException {
+        ensureHeaderWritten();
+        output.writeBytes(str);
+    }
+
+    @Override
+    public final void writeChars(final String str) throws IOException {
+        ensureHeaderWritten();
+        output.writeChars(str);
+    }
+
+    @Override
+    public final void writeUTF(final String str) throws IOException {
+        ensureHeaderWritten();
+        output.writeUTF(str);
+    }
+
+    @Override
+    public final void writeQName(final QName qname) throws IOException {
+        ensureHeaderWritten();
+        writeQNameInternal(qname);
+    }
+
+    @Override
+    public final void writeNormalizedNode(final NormalizedNode<?, ?> node) throws IOException {
+        ensureHeaderWritten();
+        if (normalizedNodeWriter == null) {
+            normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(this);
+        }
+        normalizedNodeWriter.write(node);
+    }
+
+    @Override
+    public final void writePathArgument(final PathArgument pathArgument) throws IOException {
+        ensureHeaderWritten();
+        writePathArgumentInternal(pathArgument);
+    }
+
+    @Override
+    public final void writeYangInstanceIdentifier(final YangInstanceIdentifier identifier) throws IOException {
+        ensureHeaderWritten();
+        writeYangInstanceIdentifierInternal(identifier);
+    }
+
+    @Override
+    public final void writeSchemaPath(final SchemaPath path) throws IOException {
+        ensureHeaderWritten();
+
+        output.writeBoolean(path.isAbsolute());
+        final List<QName> qnames = path.getPath();
+        output.writeInt(qnames.size());
+        for (QName qname : qnames) {
+            writeQNameInternal(qname);
+        }
+    }
+
+    @Override
+    public final void close() throws IOException {
+        flush();
+    }
+
+    @Override
+    public void flush() throws IOException {
+        if (output instanceof OutputStream) {
+            ((OutputStream)output).flush();
+        }
+    }
+
+    abstract short streamVersion();
+
+    abstract void writeQNameInternal(@NonNull QName qname) throws IOException;
+
+    abstract void writePathArgumentInternal(PathArgument pathArgument) throws IOException;
+
+    abstract void writeYangInstanceIdentifierInternal(YangInstanceIdentifier identifier) throws IOException;
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/ForwardingDataInput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/ForwardingDataInput.java
new file mode 100644 (file)
index 0000000..e2994ef
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.io.DataInput;
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNull;
+
+// Not a ForwardingObject because delegate() can legally throw and we do not want redirect toString()
+abstract class ForwardingDataInput implements DataInput {
+
+    abstract @NonNull DataInput delegate() throws IOException;
+
+    @Override
+    @SuppressWarnings("checkstyle:parameterName")
+    public final void readFully(final byte[] b) throws IOException {
+        delegate().readFully(b);
+    }
+
+    @Override
+    @SuppressWarnings("checkstyle:parameterName")
+    public final void readFully(final byte[] b, final int off, final int len) throws IOException {
+        delegate().readFully(b, off, len);
+    }
+
+    @Override
+    @SuppressWarnings("checkstyle:parameterName")
+    public final int skipBytes(final int n) throws IOException {
+        return delegate().skipBytes(n);
+    }
+
+    @Override
+    public final boolean readBoolean() throws IOException {
+        return delegate().readBoolean();
+    }
+
+    @Override
+    public final byte readByte() throws IOException {
+        return delegate().readByte();
+    }
+
+    @Override
+    public final int readUnsignedByte() throws IOException {
+        return delegate().readUnsignedByte();
+    }
+
+    @Override
+    public final short readShort() throws IOException {
+        return delegate().readShort();
+    }
+
+    @Override
+    public final int readUnsignedShort() throws IOException {
+        return delegate().readUnsignedShort();
+    }
+
+    @Override
+    public final char readChar() throws IOException {
+        return delegate().readChar();
+    }
+
+    @Override
+    public final int readInt() throws IOException {
+        return delegate().readInt();
+    }
+
+    @Override
+    public final long readLong() throws IOException {
+        return delegate().readLong();
+    }
+
+    @Override
+    public final float readFloat() throws IOException {
+        return delegate().readFloat();
+    }
+
+    @Override
+    public final double readDouble() throws IOException {
+        return delegate().readDouble();
+    }
+
+    @Override
+    public final String readLine() throws IOException {
+        return delegate().readLine();
+    }
+
+    @Override
+    public final String readUTF() throws IOException {
+        return delegate().readUTF();
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/ForwardingNormalizedNodeDataInput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/ForwardingNormalizedNodeDataInput.java
new file mode 100644 (file)
index 0000000..a915487
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.ReusableStreamReceiver;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+abstract class ForwardingNormalizedNodeDataInput extends ForwardingDataInput implements NormalizedNodeDataInput {
+
+    @Override
+    abstract @NonNull NormalizedNodeDataInput delegate() throws IOException;
+
+    @Override
+    public final void streamNormalizedNode(final NormalizedNodeStreamWriter writer) throws IOException {
+        delegate().streamNormalizedNode(writer);
+    }
+
+    @Override
+    public final NormalizedNode<?, ?> readNormalizedNode() throws IOException {
+        return delegate().readNormalizedNode();
+    }
+
+    @Override
+    public final NormalizedNode<?, ?> readNormalizedNode(final ReusableStreamReceiver receiver) throws IOException {
+        return delegate().readNormalizedNode(receiver);
+    }
+
+    @Override
+    public final YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
+        return delegate().readYangInstanceIdentifier();
+    }
+
+    @Override
+    public final QName readQName() throws IOException {
+        return delegate().readQName();
+    }
+
+    @Override
+    public final PathArgument readPathArgument() throws IOException {
+        return delegate().readPathArgument();
+    }
+
+    @Override
+    public final SchemaPath readSchemaPath() throws IOException {
+        return delegate().readSchemaPath();
+    }
+
+    @Override
+    public final NormalizedNodeStreamVersion getVersion() throws IOException {
+        return delegate().getVersion();
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/InvalidNormalizedNodeStreamException.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/InvalidNormalizedNodeStreamException.java
new file mode 100644 (file)
index 0000000..6bd9b96
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2015 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.binfmt;
+
+import java.io.IOException;
+
+/**
+ * Exception thrown from NormalizedNodeInputStreamReader when the input stream does not contain
+ * valid serialized data.
+ *
+ * @author Thomas Pantelis
+ */
+public class InvalidNormalizedNodeStreamException extends IOException {
+    private static final long serialVersionUID = 1L;
+
+    public InvalidNormalizedNodeStreamException(final String message) {
+        super(message);
+    }
+
+    public InvalidNormalizedNodeStreamException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumNode.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumNode.java
new file mode 100644 (file)
index 0000000..4953bb5
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2014, 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.binfmt;
+
+/**
+ * Stream constants identifying individual node types.
+ */
+final class LithiumNode {
+    static final byte LEAF_NODE = 1;
+    static final byte LEAF_SET = 2;
+    static final byte LEAF_SET_ENTRY_NODE = 3;
+    static final byte CONTAINER_NODE = 4;
+    static final byte UNKEYED_LIST = 5;
+    static final byte UNKEYED_LIST_ITEM = 6;
+    static final byte MAP_NODE = 7;
+    static final byte MAP_ENTRY_NODE = 8;
+    static final byte ORDERED_MAP_NODE = 9;
+    static final byte CHOICE_NODE = 10;
+    static final byte AUGMENTATION_NODE = 11;
+    static final byte ANY_XML_NODE = 12;
+    static final byte END_NODE = 13;
+    static final byte ORDERED_LEAF_SET = 14;
+    static final byte YANG_MODELED_ANY_XML_NODE = 15;
+
+    private LithiumNode() {
+        // Utility class
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumNormalizedNodeInputStreamReader.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumNormalizedNodeInputStreamReader.java
new file mode 100755 (executable)
index 0000000..61f5030
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2014, 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.binfmt;
+
+import com.google.common.base.Strings;
+import java.io.DataInput;
+import java.io.IOException;
+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;
+
+/**
+ * Lithium (or Oxygen really) specialization of AbstractLithiumDataInput.
+ */
+final class LithiumNormalizedNodeInputStreamReader extends AbstractLithiumDataInput {
+    LithiumNormalizedNodeInputStreamReader(final DataInput input) {
+        super(input);
+    }
+
+    @Override
+    public NormalizedNodeStreamVersion getVersion() {
+        return NormalizedNodeStreamVersion.LITHIUM;
+    }
+
+    @Override
+    public QName readQName() throws IOException {
+        // Read in the same sequence of writing
+        String localName = readCodedString();
+        String namespace = readCodedString();
+        String revision = Strings.emptyToNull(readCodedString());
+
+        return QNameFactory.create(localName, namespace, revision);
+    }
+
+    @Override
+    AugmentationIdentifier readAugmentationIdentifier() throws IOException {
+        return defaultReadAugmentationIdentifier();
+    }
+
+    @Override
+    NodeIdentifier readNodeIdentifier() throws IOException {
+        return new NodeIdentifier(readQName());
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumNormalizedNodeOutputStreamWriter.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumNormalizedNodeOutputStreamWriter.java
new file mode 100644 (file)
index 0000000..29b812c
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2014, 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.binfmt;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+
+/**
+ * NormalizedNodeOutputStreamWriter will be used by distributed datastore to send normalized node in
+ * a stream.
+ * A stream writer wrapper around this class will write node objects to stream in recursive manner.
+ * for example - If you have a ContainerNode which has a two LeafNode as children, then
+ * you will first call
+ * {@link #startContainerNode(org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier, int)},
+ * then will call
+ * {@link #leafNode(org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier, Object)} twice
+ * and then, {@link #endNode()} to end container node.
+ *
+ * <p>Based on the each node, the node type is also written to the stream, that helps in reconstructing the object,
+ * while reading.
+ */
+final class LithiumNormalizedNodeOutputStreamWriter extends AbstractLithiumDataOutput {
+    LithiumNormalizedNodeOutputStreamWriter(final DataOutput output) {
+        super(output);
+    }
+
+    @Override
+    short streamVersion() {
+        return TokenTypes.LITHIUM_VERSION;
+    }
+
+    @Override
+    void writeQNameInternal(final QName qname) throws IOException {
+        defaultWriteQName(qname);
+    }
+
+    @Override
+    void writeModule(final QNameModule module) throws IOException {
+        defaultWriteModule(module);
+    }
+
+    @Override
+    void writeAugmentationIdentifier(final AugmentationIdentifier aid) throws IOException {
+        defaultWriteAugmentationIdentifier(aid);
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumPathArgument.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumPathArgument.java
new file mode 100644 (file)
index 0000000..1488a9f
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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.binfmt;
+
+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.PathArgument;
+
+final class LithiumPathArgument {
+    static final byte AUGMENTATION_IDENTIFIER = 1;
+    static final byte NODE_IDENTIFIER = 2;
+    static final byte NODE_IDENTIFIER_WITH_VALUE = 3;
+    static final byte NODE_IDENTIFIER_WITH_PREDICATES = 4;
+
+    private LithiumPathArgument() {
+        // Utility class
+    }
+
+    static byte getSerializablePathArgumentType(final PathArgument pathArgument) {
+        if (pathArgument instanceof NodeIdentifier) {
+            return NODE_IDENTIFIER;
+        } else if (pathArgument instanceof NodeIdentifierWithPredicates) {
+            return NODE_IDENTIFIER_WITH_PREDICATES;
+        } else if (pathArgument instanceof AugmentationIdentifier) {
+            return AUGMENTATION_IDENTIFIER;
+        } else if (pathArgument instanceof NodeWithValue) {
+            return NODE_IDENTIFIER_WITH_VALUE;
+        } else {
+            throw new IllegalArgumentException("Unknown type of PathArgument = " + pathArgument);
+        }
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumTokens.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumTokens.java
new file mode 100644 (file)
index 0000000..25bcfbe
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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.binfmt;
+
+/**
+ * Tokens related to Lithium/NeonSR2 encoding.
+ */
+final class LithiumTokens {
+    /**
+     * The value is a reference to a previously-defined entity, typically through {@link #IS_STRING_VALUE}.
+     */
+    static final byte IS_CODE_VALUE = 1;
+    /**
+     * The value is a String, which needs to be kept memoized for the purposes for being referenced by
+     * {@link #IS_CODE_VALUE}.
+     */
+    static final byte IS_STRING_VALUE = 2;
+    /**
+     * The value is an explicit null.
+     */
+    static final byte IS_NULL_VALUE = 3;
+
+    private LithiumTokens() {
+
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumValue.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumValue.java
new file mode 100644 (file)
index 0000000..c717b47
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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.binfmt;
+
+final class LithiumValue {
+    // The String length threshold beyond which a String should be encoded as bytes
+    static final int STRING_BYTES_LENGTH_THRESHOLD = Short.MAX_VALUE / 4;
+
+    static final byte SHORT_TYPE = 1;
+    static final byte BYTE_TYPE = 2;
+    static final byte INT_TYPE = 3;
+    static final byte LONG_TYPE = 4;
+    static final byte BOOL_TYPE = 5;
+    static final byte QNAME_TYPE = 6;
+    static final byte BITS_TYPE = 7;
+    static final byte YANG_IDENTIFIER_TYPE = 8;
+    static final byte STRING_TYPE = 9;
+    static final byte BIG_INTEGER_TYPE = 10;
+    static final byte BIG_DECIMAL_TYPE = 11;
+    static final byte BINARY_TYPE = 12;
+    // Leaf nodes no longer allow null values. The "empty" type is now represented as
+    // org.opendaylight.yangtools.yang.common.Empty. This is kept for backwards compatibility.
+    @Deprecated
+    static final byte NULL_TYPE = 13;
+    static final byte STRING_BYTES_TYPE = 14;
+    static final byte EMPTY_TYPE = 15;
+
+    private LithiumValue() {
+        // Utility class
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumDataInput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumDataInput.java
new file mode 100644 (file)
index 0000000..08adc46
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.math.BigInteger;
+
+final class MagnesiumDataInput extends AbstractMagnesiumDataInput {
+    MagnesiumDataInput(final DataInput input) {
+        super(input);
+    }
+
+    @Override
+    public NormalizedNodeStreamVersion getVersion() {
+        return NormalizedNodeStreamVersion.MAGNESIUM;
+    }
+
+    @Override
+    BigInteger readBigInteger() throws IOException {
+        throw new InvalidNormalizedNodeStreamException("BigInteger coding is not supported");
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumDataOutput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumDataOutput.java
new file mode 100644 (file)
index 0000000..051e82c
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.math.BigInteger;
+
+final class MagnesiumDataOutput extends AbstractMagnesiumDataOutput {
+    MagnesiumDataOutput(final DataOutput output) {
+        super(output);
+    }
+
+    @Override
+    short streamVersion() {
+        return TokenTypes.MAGNESIUM_VERSION;
+    }
+
+    @Override
+    void writeValue(final BigInteger value) throws IOException {
+        throw new IOException("BigInteger values are not supported");
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumNode.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumNode.java
new file mode 100644 (file)
index 0000000..ab8cc0b
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+/**
+ * Magnesium encoding Node types. Encoded as a single byte, split as follows:
+ * <pre>
+ *   7 6 5 4 3 2 1 0
+ *  +-+-+-+-+-+-+-+-+
+ *  | P | A |  Type |
+ *  +-+-+-+-+-+-+-+-+
+ * </pre>
+ * The fields being:
+ * <ul>
+ *   <li>Bits 7 and 6 (most significant): predicate presence. Only valid for NODE_MAP_ENTRY and NODE_LEAF</li>
+ *   <li>Bits 5 and 4: addressing mode</li>
+ *   <li>Bits 3-0 (least significant) node type</li>
+ * </ul>
+ */
+// TODO: restructure this into some concrete examples
+//- a leaf referencing a previously-encoded NodeIdentifier would take
+//6 bytes:
+//  (byte)    NodeTypes.LEAF_NODE
+//  (byte)    TokenTypes.IS_QNAME_CODE
+//  (int)     code value
+//where as new tokens can do that in as few as 2 bytes:
+//  (byte)    NodeType.(NODE_LEAF | ADDR_LOOKUP_1B)
+//  (byte)    code value
+//with worst-case being 5 bytes:
+//  (byte)    NodeType.(NODE_LEAF | ADDR_LOOKUP_4B)
+//  (int)     code value
+//- a map entry node referencing previously-encoded QNames and a single
+//predicate would take a base of 15 bytes (not counting value object):
+//  (byte)    NodeTypes.MAP_ENTRY_NODE
+//  (byte)    TokenTypes.IS_QNAME_CODE
+//  (int)     code value
+//  (int)     size of predicates
+//  (byte)    TokenTypes.IS_QNAME_CODE
+//  (int)     code value
+//whereas new tokens can do that in as few as 3 bytes:
+//  (byte)    NodeType.(NODE_MAP_ENTRY | ADDR_LOOKUP_1B | PREDICATE_ONE)
+//  (byte)    code value
+//  (byte)    code value
+//this ability is maintained for up to 255 predicates with:
+//  (byte)    NodeType.(NODE_MAP_ENTRY | ADDR_LOOKUP_1B | PREDICATE_1B)
+//  (byte)    code value
+//  (byte)    size of predicates
+//  (byte)    code value [0-255]
+//- a leaf representing a key inside a map entry has the ability to skip
+//value encoding by being as simple as:
+//  (byte)    NodeTYpe.(NODE_LEAF | ADDR_LOOKUP_1B | PREDICATE_ONE)
+//  (byte)    code value
+//
+final class MagnesiumNode {
+    /**
+     * End of node marker. Does not support addressing modes.
+     */
+    static final byte NODE_END             = 0x00; // N/A
+    /**
+     * A leaf node. Encoding can specify {@link #PREDICATE_ONE}, which indicates the value is skipped as the encoder
+     * has emitted a parent MapNode, whose identifier contains the value.
+     */
+    static final byte NODE_LEAF            = 0x01;
+    static final byte NODE_CONTAINER       = 0x02;
+    static final byte NODE_LIST            = 0x03;
+    static final byte NODE_MAP             = 0x04;
+    static final byte NODE_MAP_ORDERED     = 0x05;
+    static final byte NODE_LEAFSET         = 0x06;
+    static final byte NODE_LEAFSET_ORDERED = 0x07;
+    static final byte NODE_CHOICE          = 0x08;
+    static final byte NODE_AUGMENTATION    = 0x09;
+    static final byte NODE_ANYXML          = 0x0A;
+    static final byte NODE_LIST_ENTRY      = 0x0B;
+    static final byte NODE_LEAFSET_ENTRY   = 0x0C;
+    static final byte NODE_MAP_ENTRY       = 0x0D;
+
+    // TODO: either implement or remove this coding. While Lithium has emit code, it lacks the code do read such nodes,
+    //       which most probably means we do not need to bother ...
+    static final byte NODE_ANYXML_MODELED  = 0x0E;
+    // 0x0F reserved for anydata
+    static final byte TYPE_MASK            = 0x0F;
+
+
+    /**
+     * Inherit identifier from parent. This addressing mode is applicable in:
+     * <ul>
+     *   <li>{@link #NODE_END}, where an identifier is not applicable
+     *   <li>{@link #NODE_LIST_ENTRY}, where the NodeIdentifier is inherited from parent {@link #NODE_LIST}</li>
+     *   <li>{@link #NODE_MAP_ENTRY}, where the NodeIdentifier is inherited from parent {@link #NODE_MAP} or
+     *       {@link #NODE_MAP_ORDERED}</li>
+     *   <li>{@link #NODE_LEAFSET_ENTRY}, where the QName inherited from parent and the value is inferred from the
+     *       next {@link MagnesiumValue} encoded</li>
+     * </ul>
+     */
+    static final byte ADDR_PARENT     = 0x00;
+    /**
+     * Define a new QName-based identifier constant. For {@link #NODE_AUGMENTATION} this is a set of QNames. Assign
+     * a new linear key to this constant.
+     */
+    static final byte ADDR_DEFINE     = 0x10;
+    /**
+     * Reference a previously {@link #ADDR_DEFINE}d identifier constant. This node byte is followed by an unsigned
+     * byte, which holds the linear key previously defined (i.e. 0-255).
+     */
+    static final byte ADDR_LOOKUP_1B  = 0x20;
+    /**
+     * Reference a previously {@link #ADDR_DEFINE}d identifier constant. This node byte is followed by a signed int,
+     * which holds the linear key previously defined.
+     */
+    static final byte ADDR_LOOKUP_4B  = 0x30;
+    static final byte ADDR_MASK       = ADDR_LOOKUP_4B;
+
+    /**
+     * Predicate encoding: no predicates are present in a {@link #NODE_MAP_ENTRY}.
+     */
+    static final byte PREDICATE_ZERO = 0x00;
+
+    /**
+     * Predicate encoding: a single predicate is present in a {@link #NODE_MAP_ENTRY}. In case of {@link #NODE_LEAF}
+     * encoded as part of a {@link #NODE_MAP_ENTRY} this bit indicates the <strong>value</strong> is not encoded and
+     * should be looked up from the map entry's predicates.
+     *
+     * <p>
+     * The predicate is encoded as a {@link #ADDR_DEFINE} or {@link #ADDR_LOOKUP_1B}/{@link #ADDR_LOOKUP_4B},
+     * followed by an encoded {@link MagnesiumValue}.
+     */
+    static final byte PREDICATE_ONE   = 0x40;
+
+    /**
+     * Predicate encoding: 0-255 predicates are present, as specified by the following {@code unsigned byte}. This
+     * encoding is expected to be exceedingly rare. This should not be used to encode 0 or 1 predicate, those cases
+     * should be encoded as:
+     * <ul>
+     *   <li>no PREDICATE_* set when there are no predicates (probably not valid anyway)</li>
+     *   <li><{@link #PREDICATE_ONE} if there is only one predicate</li>
+     * </ul>
+     */
+    static final byte PREDICATE_1B    = (byte) 0x80;
+
+    /**
+     * Predicate encoding 0 - {@link Integer#MAX_VALUE} predicates are present, as specified by the following
+     * {@code int}. This should not be used where 0-255 predicates are present.
+     */
+    static final byte PREDICATE_4B    = (byte) (PREDICATE_ONE | PREDICATE_1B);
+    static final byte PREDICATE_MASK  = PREDICATE_4B;
+
+    private MagnesiumNode() {
+
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumPathArgument.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumPathArgument.java
new file mode 100644 (file)
index 0000000..c835ae6
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+/**
+ * Path Argument types used in Magnesium encoding. These are encoded as a single byte, three bits of which are reserved
+ * for PathArgument type itself:
+ * <pre>
+ *   7 6 5 4 3 2 1 0
+ *  +-+-+-+-+-+-+-+-+
+ *  |         | Type|
+ *  +-+-+-+-+-+-+-+-+
+ * </pre>
+ * There are five type defined:
+ * <ul>
+ *   <li>{@link #AUGMENTATION_IDENTIFIER}, which additionally holds the number of QName elements encoded:
+ *     <pre>
+ *        7 6 5 4 3 2 1 0
+ *       +-+-+-+-+-+-+-+-+
+ *       |  Count  |0 0 0|
+ *       +-+-+-+-+-+-+-+-+
+ *     </pre>
+ *     Where count is coded as an unsigned integer, with {@link #AID_COUNT_1B} and {@link #AID_COUNT_2B} and
+ *     {@link #AID_COUNT_4B} indicating extended coding with up to 4 additional bytes. This byte is followed by
+ *     {@code count} {@link MagnesiumValue} QNames.
+ *     <pre>
+ *       7 6 5 4 3 2 1 0
+ *      +-+-+-+-+-+-+-+-+
+ *      |0 0 0| Q |0 0 1|
+ *      +-+-+-+-+-+-+-+-+
+ *     </pre>
+ *     Where QName coding is achieved via {@link #QNAME_DEF}, {@link #QNAME_REF_1B}, {@link #QNAME_REF_2B} and
+ *     {@link #QNAME_REF_4B}.
+ *   </li>
+ *   <li>{@link #NODE_IDENTIFIER_WITH_PREDICATES}, which encodes a QName same way NodeIdentifier does:
+ *     <pre>
+ *       7 6 5 4 3 2 1 0
+ *      +-+-+-+-+-+-+-+-+
+ *      | Size| Q |0 1 0|
+ *      +-+-+-+-+-+-+-+-+
+ *      </pre>
+ *      but additionally encodes number of predicates contained using {@link #SIZE_0} through {@link #SIZE_4}. If that
+ *      number cannot be expressed, {@link #SIZE_1B}, {@value #SIZE_2B} and {@link #SIZE_4B} indicate number and format
+ *      of additional bytes that hold number of predicates.
+ *
+ *      <p>
+ *      This is then followed by the specified number of QName/Object key/value pairs based on {@link MagnesiumValue}
+ *      encoding.
+ *   </li>
+ *   <li>{@link #NODE_WITH_VALUE}, which encodes a QName same way NodeIdentifier does:
+ *     <pre>
+ *       7 6 5 4 3 2 1 0
+ *      +-+-+-+-+-+-+-+-+
+ *      |0 0 0| Q |0 1 1|
+ *      +-+-+-+-+-+-+-+-+
+ *     </pre>
+ *     but is additionally followed by a single encoded value, as per {@link MagnesiumValue}.
+ *   </li>
+ *   <li>{@link #MOUNTPOINT_IDENTIFIER}, which encodes a QName same way NodeIdentifier does:
+ *     <pre>
+ *       7 6 5 4 3 2 1 0
+ *      +-+-+-+-+-+-+-+-+
+ *      |0 0 0| Q |1 0 0|
+ *      +-+-+-+-+-+-+-+-+
+ *     </pre>
+ *   </li>
+ * </ul>
+ */
+final class MagnesiumPathArgument {
+    // 3 bits reserved for type...
+    static final byte AUGMENTATION_IDENTIFIER         = 0x00;
+    static final byte NODE_IDENTIFIER                 = 0x01;
+    static final byte NODE_IDENTIFIER_WITH_PREDICATES = 0x02;
+    static final byte NODE_WITH_VALUE                 = 0x03;
+    static final byte MOUNTPOINT_IDENTIFIER           = 0x04;
+
+    // ... leaving three values currently unused
+    // 0x05 reserved
+    // 0x06 reserved
+    // 0x07 reserved
+
+    static final byte TYPE_MASK                       = 0x07;
+
+    // In case of AUGMENTATION_IDENTIFIER, top 5 bits are used to encode the number of path arguments, except last three
+    // values. This means that up to AugmentationIdentifiers with up to 28 components have this length encoded inline,
+    // otherwise we encode them in following 1 (unsigned), 2 (unsigned) or 4 (signed) bytes
+    static final byte AID_COUNT_1B                    = (byte) 0xE8;
+    static final byte AID_COUNT_2B                    = (byte) 0xF0;
+    static final byte AID_COUNT_4B                    = (byte) 0xF8;
+    static final byte AID_COUNT_MASK                  = AID_COUNT_4B;
+    static final byte AID_COUNT_SHIFT                 = 3;
+
+    // For normal path path arguments we can either define a QName reference or follow a 1-4 byte reference.
+    static final byte QNAME_DEF                       = 0x00;
+    static final byte QNAME_REF_1B                    = 0x08; // Unsigned
+    static final byte QNAME_REF_2B                    = 0x10; // Unsigned
+    static final byte QNAME_REF_4B                    = 0x18; // Signed
+    static final byte QNAME_MASK                      = QNAME_REF_4B;
+
+    // For NodeIdentifierWithPredicates we also carry the number of subsequent path arguments. The case of 0-4 arguments
+    // is indicated directly, otherwise there is 1-4 bytes carrying the reference.
+    static final byte SIZE_0                          = 0x00;
+    static final byte SIZE_1                          = 0x20;
+    static final byte SIZE_2                          = 0x40;
+    static final byte SIZE_3                          = 0x60;
+    static final byte SIZE_4                          = (byte) 0x80;
+    static final byte SIZE_1B                         = (byte) 0xA0;
+    static final byte SIZE_2B                         = (byte) 0xC0;
+    static final byte SIZE_4B                         = (byte) 0xE0;
+    static final byte SIZE_MASK                       = SIZE_4B;
+    static final byte SIZE_SHIFT                      = 5;
+
+    private MagnesiumPathArgument() {
+
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumValue.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MagnesiumValue.java
new file mode 100644 (file)
index 0000000..62fae59
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.io.DataOutput;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.opendaylight.yangtools.yang.common.Uint32;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.opendaylight.yangtools.yang.common.Uint8;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+/**
+ * Magnesium encoding value types. Serialized as a single byte.
+ */
+/*
+ * Note these constants are organized by their absolute value, which is slightly counter-intuitive when trying to make
+ * sense of what is going on.
+ *
+ * TODO: create some sort of facility which would provide symbolic names for debugging and documentation purposes.
+ */
+final class MagnesiumValue {
+    /**
+     * {@link Boolean#FALSE} value.
+     */
+    static final byte BOOLEAN_FALSE  = 0x00;
+    /**
+     * {@link Boolean#TRUE} value.
+     */
+    static final byte BOOLEAN_TRUE   = 0x01;
+    /**
+     * An {@link Empty} value.
+     */
+    static final byte EMPTY          = 0x02;
+    /**
+     * A Byte, followed by a byte holding the value.
+     */
+    static final byte INT8           = 0x03;
+    /**
+     * A Short, followed by a {@code short} holding the value.
+     */
+    static final byte INT16          = 0x04;
+    /**
+     * An Integer, followed by an {@code int} holding the value.
+     */
+    static final byte INT32          = 0x05;
+    /**
+     * A Long, followed by an {@code long} holding the value.
+     */
+    static final byte INT64          = 0x06;
+    /**
+     * A Uint8, followed by an {@code unsigned byte} holding the value.
+     */
+    static final byte UINT8          = 0x07;
+    /**
+     * A Uint16, followed by a {@code unsigned short} holding the value.
+     */
+    static final byte UINT16         = 0x08;
+    /**
+     * A Uint32, followed by an {@code unsigned int} holding the value.
+     */
+    static final byte UINT32         = 0x09;
+    /**
+     * A Uint64, followed by an {@code unsigned long} holding the value.
+     */
+    static final byte UINT64         = 0x0A;
+    /**
+     * A {@link String}, encoded through {@link DataOutput#writeUTF(String)}. Note this is generally true of any
+     * string with less then 16384 characters.
+     */
+    static final byte STRING_UTF     = 0x0B;
+    /**
+     * A {@link String}, encoded as an {@code unsigned short} followed by that many UTF8-encoded bytes.
+     */
+    static final byte STRING_2B      = 0x0C;
+    /**
+     * A {@link String}, encoded as an {@code int >= 0} followed by that many UTF8-encoded bytes.
+     */
+    static final byte STRING_4B      = 0x0D;
+    /**
+     * A {@link String}, encoded as an {@code int >= 0} followed by that many UTF16 characters, i.e. as produced by
+     * {@link DataOutput#writeChars(String)}.
+     */
+    static final byte STRING_CHARS   = 0x0E;
+    /**
+     * Utility 'reference coding' codepoint with {@code unsigned byte} offset. This is not a value type, but is used in
+     * context of various schema-related encodings like constant strings, QNameModule and similar.
+     */
+    static final byte STRING_REF_1B  = 0x0F;
+    /**
+     * Utility 'reference coding' codepoint with {@code unsigned short} offset. This is not a value type, but is used in
+     * context of various schema-related encodings like constant strings, QNameModule and similar.
+     */
+    static final byte STRING_REF_2B  = 0x10;
+    /**
+     * Utility 'reference coding' codepoint with {@code int} offset. This is not a value type, but is used in context of
+     * various schema-related encodings like constant strings, QNameModule and similar.
+     */
+    static final byte STRING_REF_4B  = 0x11;
+    /**
+     * A {@code byte[])}, encoded as a single {@code unsigned byte} followed by 128-383 bytes. Note that smaller
+     * arrays are encoded via {@link #BINARY_0} - {@link #BINARY_127} range.
+     */
+    static final byte BINARY_1B      = 0x12;
+    /**
+     * A {@code byte[])}, encoded as a single {@code unsigned short} followed by 384-65919 bytes. See also
+     * {@link #BINARY_1B}.
+     */
+    static final byte BINARY_2B      = 0x13;
+    /**
+     * A {@code byte[])}, encoded as a single {@code int} followed by that many bytes bytes. See also
+     * {@link #BINARY_2B}.
+     */
+    static final byte BINARY_4B      = 0x14;
+    /**
+     * A {@link YangInstanceIdentifier}, encoded as a single {@code int}, followed by that many components. See
+     * also {@link #YIID_0}, which offers optimized encoding for up to 31 components. Components are encoded using
+     * {@link MagnesiumPathArgument} coding.
+     */
+    static final byte YIID           = 0x15;
+    /**
+     * A QName literal. Encoded as QNameModule + String. This literal is expected to be memoized on receiver side, which
+     * assigns the next linear integer identifier. The sender will memoize it too and further references to this QName
+     * will be made via {@link #QNAME_REF_1B}, {@link #QNAME_REF_2B} or {@link #QNAME_REF_4B}.
+     *
+     * <p>
+     * Note that QNameModule (and String in this context) encoding works similarly -- it can only occur as part of a
+     * QName (coming from here or {@link MagnesiumPathArgument}) and is subject to the same memoization.
+     *
+     * <p>
+     * For example, given two QNames {@code foo = QName.create("foo", "abc")} and
+     * {@code bar = QName.create("foo", "def")}, if they are written in order {@code foo, bar, foo}, then the following
+     * events are emitted:
+     * <pre>
+     *   QNAME                (define QName, assign shorthand Q0)
+     *   STRING_UTF   "foo"   ("foo", assign shorthand S0, implies define QNameModule, assign shorthand M0)
+     *   STRING_EMPTY         (foo's non-existent revision)
+     *   STRING_UTF   "abc"   ("abc", assign shorthand S1)
+     *   QNAME                (define QName, assign shorthand Q1)
+     *   MODREF_1B    (byte)0 (reference M0)
+     *   STRING_UTF   "def"   ("def", assign shorthand S2)
+     *   QNAME_REF_1B (byte)0 (reference Q0)
+     * </pre>
+     */
+    // Design note: STRING_EMPTY is required to *NOT* establish a shortcut, as that is less efficient (and hence does
+    //              not make sense from the sender, the receiver or the serialization protocol itself.
+    static final byte QNAME          = 0x16;
+    /**
+     * Reference a QName previously defined via {@link #QNAME}. Reference number is encoded as {@code unsigned byte}.
+     */
+    static final byte QNAME_REF_1B   = 0x17;
+    /**
+     * Reference a QName previously defined via {@link #QNAME}. Reference number is encoded as {@code unsigned short}.
+     */
+    static final byte QNAME_REF_2B   = 0x18;
+    /**
+     * Reference a QName previously defined via {@link #QNAME}. Reference number is encoded as {@code int}.
+     */
+    static final byte QNAME_REF_4B   = 0x19;
+    /**
+     * Reference a previously defined QNameModule. Reference number is encoded as {@code unsigned byte}.
+     */
+    static final byte MODREF_1B      = 0x1A;
+    /**
+     * Reference a previously defined QNameModule. Reference number is encoded as {@code unsigned short}.
+     */
+    static final byte MODREF_2B      = 0x1B;
+    /**
+     * Reference a previously defined QNameModule. Reference number is encoded as {@code int}.
+     */
+    static final byte MODREF_4B      = 0x1C;
+
+    /**
+     * A {@link BigDecimal}, encoded through {@link DataOutput#writeUTF(String)}.
+     */
+    // This is legacy compatibility. At some point we will remove support for writing these.
+    static final byte BIGDECIMAL     = 0x1D;
+    /**
+     * A {@link BigInteger}, encoded through {@link DataOutput#writeUTF(String)}.
+     */
+    // This is legacy compatibility. At some point we will remove support for writing these.
+    static final byte BIGINTEGER     = 0x1E;
+
+    // 0x1F reserved
+
+    /**
+     * Byte value {@code 0}.
+     */
+    static final byte INT8_0         = 0x20;
+    /**
+     * Short value {@code 0}.
+     */
+    static final byte INT16_0        = 0x21;
+    /**
+     * Integer value {@code 0}.
+     */
+    static final byte INT32_0        = 0x22;
+    /**
+     * Long value {@code 0}.
+     */
+    static final byte INT64_0        = 0x23;
+    /**
+     * {@link Uint8#ZERO} value.
+     */
+    static final byte UINT8_0        = 0x24;
+    /**
+     * {@link Uint16#ZERO} value.
+     */
+    static final byte UINT16_0       = 0x25;
+    /**
+     * {@link Uint32#ZERO} value.
+     */
+    static final byte UINT32_0       = 0x26;
+    /**
+     * {@link Uint64#ZERO} value.
+     */
+    static final byte UINT64_0       = 0x27;
+    /**
+     * Empty String value ({@code ""}).
+     */
+    static final byte STRING_EMPTY   = 0x28;
+    /**
+     * {@link #INT32} with a 2-byte operand.
+     */
+    static final byte INT32_2B       = 0x29;
+    /**
+     * {@link #UINT32} with a 2-byte operand.
+     */
+    static final byte UINT32_2B      = 0x2A;
+    /**
+     * {@link #INT64} with a 4-byte operand.
+     */
+    static final byte INT64_4B       = 0x2B;
+    /**
+     * {@link #UINT64} with a 4-byte operand.
+     */
+    static final byte UINT64_4B      = 0x2C;
+
+    // 0x2D - 0x39 reserved
+
+    /**
+     * Empty bits value. This code point starts the range, where the number of bits can be extracted as
+     * {@code code & 0x1F)}. Last three values of this range are used to encode more than 28 entries.
+     */
+    static final byte BITS_0         = 0x40;
+    /**
+     * A bits value of up to 255 entries. Number of values is encoded as the following {@code unsigned byte}.
+     */
+    static final byte BITS_1B        = 0x5D;
+    /**
+     * A bits value of up to 65535 entries. Number of values is encoded as the following {@code unsigned short}.
+     */
+    static final byte BITS_2B        = 0x5E;
+    /**
+     * A bits value. Number of values is encoded as the following {@code int}.
+     */
+    static final byte BITS_4B        = 0x5F;
+
+    /**
+     * {@link YangInstanceIdentifier} with zero components. This code point starts the range ending with
+     * {@link #YIID_31}, where the number of components can be extracted as {@code code & 0x1F}. Identifiers with
+     * more than 31 components are encoded using {@link #YIID}.
+     */
+    static final byte YIID_0         = 0x60;
+    /**
+     * {@link YangInstanceIdentifier} with 31 components. See {@link #YIID_0}.
+     */
+    static final byte YIID_31        = 0x7F;
+
+    /**
+     * A {@code byte[]} with 0 bytes. This code point starts the range ending with {@link #BINARY_127}, where
+     * the number of bytes can be extracted as {@code code & 0x7F}. Arrays longer than 127 bytes are encoded using
+     * {@link #BINARY_1B}, {@link #BINARY_2B} and {@link #BINARY_4B} as needed.
+     */
+    static final byte BINARY_0       = (byte) 0x80;
+    /**
+     * A {@code byte[]} with 127 bytes. See {@link #BINARY_0}.
+     */
+    static final byte BINARY_127     = (byte) 0xFF;
+
+    private MagnesiumValue() {
+
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NeonSR2NormalizedNodeInputStreamReader.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NeonSR2NormalizedNodeInputStreamReader.java
new file mode 100644 (file)
index 0000000..0120efb
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import static com.google.common.base.Verify.verify;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+
+/**
+ * Neon SR2 specialization of AbstractLithiumDataInput. Unlike its Lithium counterpart, this format uses coding for
+ * QNameModules, QNames, NodeIdentifiers and AugmentationIdentifiers, thus reducing stream duplication.
+ */
+final class NeonSR2NormalizedNodeInputStreamReader extends AbstractLithiumDataInput {
+    private final ArrayList<NodeIdentifier> codedNodeIdentifiers = new ArrayList<>();
+    private final List<AugmentationIdentifier> codedAugments = new ArrayList<>();
+    private final List<QNameModule> codedModules = new ArrayList<>();
+    private final List<QName> codedQNames = new ArrayList<>();
+
+    NeonSR2NormalizedNodeInputStreamReader(final DataInput input) {
+        super(input);
+    }
+
+    @Override
+    public NormalizedNodeStreamVersion getVersion() {
+        return NormalizedNodeStreamVersion.NEON_SR2;
+    }
+
+    @Override
+    public QName readQName() throws IOException {
+        final byte valueType = readByte();
+        switch (valueType) {
+            case NeonSR2Tokens.IS_QNAME_CODE:
+                return codedQName(readInt());
+            case NeonSR2Tokens.IS_QNAME_VALUE:
+                return rawQName();
+            default:
+                throw new IOException("Unhandled QName value type " + valueType);
+        }
+    }
+
+    @Override
+    AugmentationIdentifier readAugmentationIdentifier() throws IOException {
+        final byte valueType = readByte();
+        switch (valueType) {
+            case NeonSR2Tokens.IS_AUGMENT_CODE:
+                return codedAugmentId(readInt());
+            case NeonSR2Tokens.IS_AUGMENT_VALUE:
+                return rawAugmentId();
+            default:
+                throw new IOException("Unhandled AugmentationIdentifier value type " + valueType);
+        }
+    }
+
+    @Override
+    NodeIdentifier readNodeIdentifier() throws IOException {
+        // NodeIdentifier rides on top of QName, with this method really saying 'interpret next QName as NodeIdentifier'
+        // to do that we inter-mingle with readQName()
+        final byte valueType = readByte();
+        switch (valueType) {
+            case NeonSR2Tokens.IS_QNAME_CODE:
+                return codedNodeIdentifier(readInt());
+            case NeonSR2Tokens.IS_QNAME_VALUE:
+                return rawNodeIdentifier();
+            default:
+                throw new IOException("Unhandled NodeIdentifier value type " + valueType);
+        }
+    }
+
+    private QNameModule readModule() throws IOException {
+        final byte valueType = readByte();
+        switch (valueType) {
+            case NeonSR2Tokens.IS_MODULE_CODE:
+                return codedModule(readInt());
+            case NeonSR2Tokens.IS_MODULE_VALUE:
+                return rawModule();
+            default:
+                throw new IOException("Unhandled QNameModule value type " + valueType);
+        }
+    }
+
+    private NodeIdentifier codedNodeIdentifier(final int code) throws IOException {
+        final NodeIdentifier existing = codedNodeIdentifiers.size() > code ? codedNodeIdentifiers.get(code) : null;
+        return existing != null ? existing : storeNodeIdentifier(code, codedQName(code));
+    }
+
+    private NodeIdentifier rawNodeIdentifier() throws IOException {
+        // Capture size before it incremented
+        final int code = codedQNames.size();
+        return storeNodeIdentifier(code, rawQName());
+    }
+
+    private NodeIdentifier storeNodeIdentifier(final int code, final QName qname) {
+        final NodeIdentifier ret = NodeIdentifier.create(qname);
+        final int size = codedNodeIdentifiers.size();
+
+        if (code >= size) {
+            // Null-fill others
+            codedNodeIdentifiers.ensureCapacity(code + 1);
+            for (int i = size; i < code; ++i) {
+                codedNodeIdentifiers.add(null);
+            }
+
+            codedNodeIdentifiers.add(ret);
+        } else {
+            final NodeIdentifier check = codedNodeIdentifiers.set(code, ret);
+            verify(check == null);
+        }
+
+        return ret;
+    }
+
+    private QName codedQName(final int code) throws IOException {
+        return getCode("QName", codedQNames, code);
+    }
+
+    private QName rawQName() throws IOException {
+        final String localName = readCodedString();
+        final QNameModule module = readModule();
+        final QName qname = QNameFactory.create(module, localName);
+        codedQNames.add(qname);
+        return qname;
+    }
+
+    private AugmentationIdentifier codedAugmentId(final int code) throws IOException {
+        return getCode("QName set", codedAugments, code);
+    }
+
+    private AugmentationIdentifier rawAugmentId() throws IOException {
+        final AugmentationIdentifier aid = defaultReadAugmentationIdentifier();
+        codedAugments.add(aid);
+        return aid;
+    }
+
+    private QNameModule codedModule(final int code) throws IOException {
+        return getCode("Module", codedModules, code);
+    }
+
+    private QNameModule rawModule() throws IOException {
+        final String namespace = readCodedString();
+        final String revision = readCodedString();
+        final QNameModule mod = QNameFactory.createModule(namespace, revision);
+        codedModules.add(mod);
+        return mod;
+    }
+
+    private static <T> T getCode(final String name, final List<T> list, final int code) throws IOException {
+        try {
+            return list.get(code);
+        } catch (IndexOutOfBoundsException e) {
+            throw new IOException(name + " code " + code + " was not found", e);
+        }
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NeonSR2NormalizedNodeOutputStreamWriter.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NeonSR2NormalizedNodeOutputStreamWriter.java
new file mode 100644 (file)
index 0000000..e9b3236
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+
+/**
+ * NormalizedNodeOutputStreamWriter will be used by distributed datastore to send normalized node in
+ * a stream.
+ * A stream writer wrapper around this class will write node objects to stream in recursive manner.
+ * for example - If you have a ContainerNode which has a two LeafNode as children, then
+ * you will first call
+ * {@link #startContainerNode(org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier, int)},
+ * then will call
+ * {@link #leafNode(org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier, Object)} twice
+ * and then, {@link #endNode()} to end container node.
+ *
+ * <p>Based on the each node, the node type is also written to the stream, that helps in reconstructing the object,
+ * while reading.
+ */
+final class NeonSR2NormalizedNodeOutputStreamWriter extends AbstractLithiumDataOutput {
+    private final Map<AugmentationIdentifier, Integer> aidCodeMap = new HashMap<>();
+    private final Map<QNameModule, Integer> moduleCodeMap = new HashMap<>();
+    private final Map<QName, Integer> qnameCodeMap = new HashMap<>();
+
+    NeonSR2NormalizedNodeOutputStreamWriter(final DataOutput output) {
+        super(output);
+    }
+
+    @Override
+    short streamVersion() {
+        return TokenTypes.NEON_SR2_VERSION;
+    }
+
+    @Override
+    void writeQNameInternal(final QName qname) throws IOException {
+        final Integer value = qnameCodeMap.get(qname);
+        if (value == null) {
+            // Fresh QName, remember it and emit as three strings
+            qnameCodeMap.put(qname, qnameCodeMap.size());
+            writeByte(NeonSR2Tokens.IS_QNAME_VALUE);
+            defaultWriteQName(qname);
+        } else {
+            // We have already seen this QName: write its code
+            writeByte(NeonSR2Tokens.IS_QNAME_CODE);
+            writeInt(value);
+        }
+    }
+
+    @Override
+    void writeAugmentationIdentifier(final AugmentationIdentifier aid) throws IOException {
+        final Integer value = aidCodeMap.get(aid);
+        if (value == null) {
+            // Fresh AugmentationIdentifier, remember it and emit as three strings
+            aidCodeMap.put(aid, aidCodeMap.size());
+            writeByte(NeonSR2Tokens.IS_AUGMENT_VALUE);
+            defaultWriteAugmentationIdentifier(aid);
+        } else {
+            // We have already seen this AugmentationIdentifier: write its code
+            writeByte(NeonSR2Tokens.IS_AUGMENT_CODE);
+            writeInt(value);
+        }
+    }
+
+    @Override
+    void writeModule(final QNameModule module) throws IOException {
+        final Integer value = moduleCodeMap.get(module);
+        if (value == null) {
+            // Fresh QNameModule, remember it and emit as three strings
+            moduleCodeMap.put(module, moduleCodeMap.size());
+            writeByte(NeonSR2Tokens.IS_MODULE_VALUE);
+            defaultWriteModule(module);
+        } else {
+            // We have already seen this QNameModule: write its code
+            writeByte(NeonSR2Tokens.IS_MODULE_CODE);
+            writeInt(value);
+        }
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NeonSR2Tokens.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NeonSR2Tokens.java
new file mode 100644 (file)
index 0000000..a75f1a5
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+/**
+ * Tokens used in Neon SR2 encoding. Note that Neon SR2 builds on top of Lithium, hence the token values must never
+ * overlap.
+ */
+final class NeonSR2Tokens {
+    static final byte IS_QNAME_CODE = 4;
+    static final byte IS_QNAME_VALUE = 5;
+    static final byte IS_AUGMENT_CODE = 6;
+    static final byte IS_AUGMENT_VALUE = 7;
+    static final byte IS_MODULE_CODE = 8;
+    static final byte IS_MODULE_VALUE = 9;
+
+    private NeonSR2Tokens() {
+
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeDataInput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeDataInput.java
new file mode 100644 (file)
index 0000000..c15948b
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2014, 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.binfmt;
+
+import com.google.common.annotations.Beta;
+import java.io.DataInput;
+import java.io.IOException;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.ReusableStreamReceiver;
+import org.opendaylight.yangtools.yang.data.impl.schema.ReusableImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+/**
+ * Interface for reading {@link NormalizedNode}s, {@link YangInstanceIdentifier}s, {@link PathArgument}s
+ * and {@link SchemaPath}s.
+ */
+@Beta
+public interface NormalizedNodeDataInput extends DataInput {
+    /**
+     * Interpret current stream position as a NormalizedNode, stream its events into a NormalizedNodeStreamWriter.
+     *
+     * @param writer Writer to emit events to
+     * @throws IOException if an error occurs
+     * @throws IllegalStateException if the dictionary has been detached
+     * @throws NullPointerException if {@code writer} is null
+     */
+    void streamNormalizedNode(NormalizedNodeStreamWriter writer) throws IOException;
+
+    /**
+     * Read a normalized node from the reader.
+     *
+     * @return Next node from the stream, or null if end of stream has been reached.
+     * @throws IOException if an error occurs
+     * @throws IllegalStateException if the dictionary has been detached
+     */
+    default NormalizedNode<?, ?> readNormalizedNode() throws IOException {
+        return readNormalizedNode(ReusableImmutableNormalizedNodeStreamWriter.create());
+    }
+
+    /**
+     * Read a normalized node from the reader, using specified writer to construct the result.
+     *
+     * @param receiver Reusable receiver to, expected to be reset
+     * @return Next node from the stream, or null if end of stream has been reached.
+     * @throws IOException if an error occurs
+     * @throws IllegalStateException if the dictionary has been detached
+     */
+    default NormalizedNode<?, ?> readNormalizedNode(final ReusableStreamReceiver receiver) throws IOException {
+        try {
+            streamNormalizedNode(receiver);
+            return receiver.getResult();
+        } finally {
+            receiver.reset();
+        }
+    }
+
+    YangInstanceIdentifier readYangInstanceIdentifier() throws IOException;
+
+    @NonNull QName readQName() throws IOException;
+
+    PathArgument readPathArgument() throws IOException;
+
+    SchemaPath readSchemaPath() throws IOException;
+
+    /**
+     * Return the version of the underlying input stream.
+     *
+     * @return Stream version
+     * @throws IOException if the version cannot be ascertained
+     */
+    NormalizedNodeStreamVersion getVersion() throws IOException;
+
+    default Optional<NormalizedNode<?, ?>> readOptionalNormalizedNode() throws IOException {
+        return readBoolean() ? Optional.of(readNormalizedNode()) : Optional.empty();
+    }
+
+    /**
+     * Creates a new {@link NormalizedNodeDataInput} instance that reads from the given input. This method first reads
+     * and validates that the input contains a valid NormalizedNode stream.
+     *
+     * @param input the DataInput to read from
+     * @return a new {@link NormalizedNodeDataInput} instance
+     * @throws InvalidNormalizedNodeStreamException if the stream version is not supported
+     * @throws IOException if an error occurs reading from the input
+     */
+    static @NonNull NormalizedNodeDataInput newDataInput(final @NonNull DataInput input) throws IOException {
+        return new VersionedNormalizedNodeDataInput(input).delegate();
+    }
+
+    /**
+     * Creates a new {@link NormalizedNodeDataInput} instance that reads from the given input. This method does not
+     * perform any initial validation of the input stream.
+     *
+     * @param input the DataInput to read from
+     * @return a new {@link NormalizedNodeDataInput} instance
+     * @deprecated Use {@link #newDataInput(DataInput)} instead.
+     */
+    // FIXME: 5.0.0: deprecate for removal
+    @Deprecated
+    static @NonNull NormalizedNodeDataInput newDataInputWithoutValidation(final @NonNull DataInput input) {
+        return new VersionedNormalizedNodeDataInput(input);
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeDataOutput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeDataOutput.java
new file mode 100644 (file)
index 0000000..a8cb9b1
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2014, 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.binfmt;
+
+import com.google.common.annotations.Beta;
+import java.io.DataOutput;
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+/**
+ * Interface for emitting {@link NormalizedNode}s, {@link YangInstanceIdentifier}s, {@link PathArgument}s
+ * and {@link SchemaPath}s.
+ */
+@Beta
+@NonNullByDefault
+public interface NormalizedNodeDataOutput extends AutoCloseable, DataOutput {
+    void writeQName(QName qname) throws IOException;
+
+    void writeNormalizedNode(NormalizedNode<?, ?> normalizedNode) throws IOException;
+
+    void writePathArgument(PathArgument pathArgument) throws IOException;
+
+    void writeYangInstanceIdentifier(YangInstanceIdentifier identifier) throws IOException;
+
+    void writeSchemaPath(SchemaPath path) throws IOException;
+
+    @Override
+    void close() throws IOException;
+
+    default void writeOptionalNormalizedNode(final @Nullable NormalizedNode<?, ?> normalizedNode) throws IOException {
+        if (normalizedNode != null) {
+            writeBoolean(true);
+            writeNormalizedNode(normalizedNode);
+        } else {
+            writeBoolean(false);
+        }
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeStreamVersion.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeStreamVersion.java
new file mode 100644 (file)
index 0000000..588953e
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import com.google.common.annotations.Beta;
+import java.io.DataOutput;
+import java.math.BigInteger;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.opendaylight.yangtools.yang.data.api.schema.ValueNode;
+
+/**
+ * Enumeration of all stream versions this implementation supports on both input and output.
+ */
+@Beta
+@NonNullByDefault
+public enum NormalizedNodeStreamVersion {
+    /**
+     * Original stream version, as shipped in OpenDaylight Lithium simultaneous release. The caveat here is that this
+     * version has augmented in OpenDaylight Oxygen to retrofit a non-null representation of the empty type.
+     */
+    // FIXME: 5.0.0: consider deprecating this version
+    LITHIUM {
+        @Override
+        public NormalizedNodeDataOutput newDataOutput(final DataOutput output) {
+            return new LithiumNormalizedNodeOutputStreamWriter(output);
+        }
+    },
+    /**
+     * Updated stream version, as shipped in OpenDaylight Neon SR2 release. Improves identifier encoding over
+     * {@link #LITHIUM}, so that QName caching is more effective.
+     */
+    NEON_SR2 {
+        @Override
+        public NormalizedNodeDataOutput newDataOutput(final DataOutput output) {
+            return new NeonSR2NormalizedNodeOutputStreamWriter(output);
+        }
+    },
+    /**
+     * First shipping in Sodium SR1. Improved stream coding to eliminate redundancies present in {@link #NEON_SR2}.
+     * Supports {code Uint8} et al. as well as {@link BigInteger}.
+     */
+    SODIUM_SR1 {
+        @Override
+        public NormalizedNodeDataOutput newDataOutput(final DataOutput output) {
+            return new SodiumSR1DataOutput(output);
+        }
+    },
+    /**
+     * First shipping is Magnesium. Does not support {@link BigInteger} mirroring it being superseded by {@link Uint64}
+     * in {@link ValueNode#getValue()}.
+     */
+    MAGNESIUM {
+        @Override
+        public NormalizedNodeDataOutput newDataOutput(final DataOutput output) {
+            return new MagnesiumDataOutput(output);
+        }
+    };
+
+    /**
+     * Return the current runtime version. Guaranteed to not throw {@link UnsupportedOperationException} from
+     * {@link #newDataOutput(DataOutput)}.
+     *
+     * @return Current runtime version.
+     */
+    public static NormalizedNodeStreamVersion current() {
+        return SODIUM_SR1;
+    }
+
+    /**
+     * Creates a new {@link NormalizedNodeDataOutput} instance that writes to the given output.
+     *
+     * @param output the DataOutput to write to
+     * @return a new {@link NormalizedNodeDataOutput} instance
+     * @throws NullPointerException if {@code output} is null
+     * @throws UnsupportedOperationException if this version cannot be created in this runtime
+     */
+    public abstract NormalizedNodeDataOutput newDataOutput(DataOutput output);
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/QNameFactory.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/QNameFactory.java
new file mode 100644 (file)
index 0000000..a8065a2
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2014, 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.binfmt;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import java.net.URI;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+
+final class QNameFactory {
+    private static final class StringQName implements Immutable {
+        private final @NonNull String localName;
+        private final @NonNull String namespace;
+        private final @Nullable String revision;
+
+        StringQName(final String localName, final String namespace, final String revision) {
+            this.localName = requireNonNull(localName);
+            this.namespace = requireNonNull(namespace);
+            this.revision = revision;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(localName, namespace, revision);
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof StringQName)) {
+                return false;
+            }
+            final StringQName other = (StringQName) obj;
+            return localName.equals(other.localName) && namespace.equals(other.namespace)
+                    && Objects.equals(revision, other.revision);
+        }
+
+        QName toQName() {
+            return revision != null ? QName.create(namespace, revision, localName) : QName.create(namespace, localName);
+        }
+    }
+
+    private static final class ModuleQName implements Immutable {
+        private final @NonNull QNameModule module;
+        private final @NonNull String localName;
+
+        ModuleQName(final QNameModule module, final String localName) {
+            this.module = requireNonNull(module);
+            this.localName = requireNonNull(localName);
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * module.hashCode() + localName.hashCode();
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof ModuleQName)) {
+                return false;
+            }
+            final ModuleQName other = (ModuleQName) obj;
+            return localName.equals(other.localName) && module.equals(other.module);
+        }
+
+        QName toQName() {
+            return QName.create(module, localName);
+        }
+    }
+
+    private static final class StringModule implements Immutable {
+        private final @NonNull String namespace;
+        private final @Nullable String revision;
+
+        StringModule(final String namespace, final String revision) {
+            this.namespace = requireNonNull(namespace);
+            this.revision = revision;
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * namespace.hashCode() + Objects.hashCode(revision);
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof StringModule)) {
+                return false;
+            }
+            final StringModule other = (StringModule) obj;
+            return namespace.equals(other.namespace) && Objects.equals(revision, other.revision);
+        }
+
+        QNameModule toQNameModule() {
+            return QNameModule.create(URI.create(namespace), Revision.ofNullable(revision));
+        }
+    }
+
+    private static final int MAX_QNAME_CACHE_SIZE = Integer.getInteger(
+        "org.opendaylight.controller.cluster.datastore.node.utils.qname-cache.max-size", 10000);
+    private static final int MAX_MODULE_CACHE_SIZE = Integer.getInteger(
+        "org.opendaylight.controller.cluster.datastore.node.utils.module-cache.max-size", 2000);
+
+    private static final LoadingCache<StringQName, QName> STRING_CACHE = CacheBuilder.newBuilder()
+            .maximumSize(MAX_QNAME_CACHE_SIZE).weakValues().build(new CacheLoader<StringQName, QName>() {
+                @Override
+                public QName load(final StringQName key) {
+                    return key.toQName().intern();
+                }
+            });
+    private static final LoadingCache<ModuleQName, QName> QNAME_CACHE = CacheBuilder.newBuilder()
+            .maximumSize(MAX_QNAME_CACHE_SIZE).weakValues().build(new CacheLoader<ModuleQName, QName>() {
+                @Override
+                public QName load(final ModuleQName key) {
+                    return key.toQName().intern();
+                }
+            });
+    private static final LoadingCache<StringModule, QNameModule> MODULE_CACHE = CacheBuilder.newBuilder()
+            .maximumSize(MAX_MODULE_CACHE_SIZE).weakValues().build(new CacheLoader<StringModule, QNameModule>() {
+                @Override
+                public QNameModule load(final StringModule key) {
+                    return key.toQNameModule().intern();
+                }
+            });
+    private static final LoadingCache<ModuleQName, NodeIdentifier> NODEID_CACHE = CacheBuilder.newBuilder()
+            .maximumSize(MAX_QNAME_CACHE_SIZE).weakValues().build(new CacheLoader<ModuleQName, NodeIdentifier>() {
+                @Override
+                public NodeIdentifier load(final ModuleQName key) throws ExecutionException {
+                    return NodeIdentifier.create(QNAME_CACHE.get(key));
+                }
+            });
+
+    private QNameFactory() {
+
+    }
+
+    static QName create(final String localName, final String namespace, final @Nullable String revision) {
+        return STRING_CACHE.getUnchecked(new StringQName(localName, namespace, revision));
+    }
+
+    static QName create(final QNameModule module, final String localName) {
+        return QNAME_CACHE.getUnchecked(new ModuleQName(module, localName));
+    }
+
+    static QNameModule createModule(final String namespace, final @Nullable String revision) {
+        return MODULE_CACHE.getUnchecked(new StringModule(namespace, revision));
+    }
+
+    static @NonNull NodeIdentifier getNodeIdentifier(final QNameModule module, final String localName)
+            throws ExecutionException {
+        return NODEID_CACHE.get(new ModuleQName(module, localName));
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/SodiumSR1DataInput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/SodiumSR1DataInput.java
new file mode 100644 (file)
index 0000000..7f738ea
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.math.BigInteger;
+
+final class SodiumSR1DataInput extends AbstractMagnesiumDataInput {
+    SodiumSR1DataInput(final DataInput input) {
+        super(input);
+    }
+
+    @Override
+    public NormalizedNodeStreamVersion getVersion() {
+        return NormalizedNodeStreamVersion.SODIUM_SR1;
+    }
+
+    @Override
+    BigInteger readBigInteger() throws IOException {
+        // FIXME: use string -> BigInteger cache
+        return new BigInteger(input.readUTF());
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/SodiumSR1DataOutput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/SodiumSR1DataOutput.java
new file mode 100644 (file)
index 0000000..f902fcd
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.math.BigInteger;
+
+final class SodiumSR1DataOutput extends AbstractMagnesiumDataOutput {
+    SodiumSR1DataOutput(final DataOutput output) {
+        super(output);
+    }
+
+    @Override
+    short streamVersion() {
+        return TokenTypes.SODIUM_SR1_VERSION;
+    }
+
+    @Override
+    void writeValue(final BigInteger value) throws IOException {
+        output.writeByte(MagnesiumValue.BIGINTEGER);
+        output.writeUTF(value.toString());
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/TokenTypes.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/TokenTypes.java
new file mode 100644 (file)
index 0000000..d29017c
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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.binfmt;
+
+final class TokenTypes {
+    static final byte SIGNATURE_MARKER = (byte) 0xab;
+
+    /**
+     * Original stream version. Uses a per-stream dictionary for strings. QNames are serialized as three strings.
+     */
+    static final short LITHIUM_VERSION = 1;
+    /**
+     * Revised stream version. Unlike {@link #LITHIUM_VERSION}, QNames and QNameModules are using a per-stream
+     * dictionary, too.
+     */
+    static final short NEON_SR2_VERSION = 2;
+    /**
+     * From-scratch designed version shipping in Sodium SR1.
+     */
+    static final short SODIUM_SR1_VERSION = 3;
+    /**
+     * Magnesium version. Structurally matches {@link #SODIUM_SR1_VERSION}, but does not allow BigIntegers to be
+     * present.
+     */
+    static final short MAGNESIUM_VERSION = 4;
+
+    private TokenTypes() {
+        // Utility class
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/VersionedNormalizedNodeDataInput.java b/yang/yang-data-codec-binfmt/src/main/java/org/opendaylight/yangtools/yang/data/codec/binfmt/VersionedNormalizedNodeDataInput.java
new file mode 100644 (file)
index 0000000..099f56d
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.DataInput;
+import java.io.IOException;
+
+final class VersionedNormalizedNodeDataInput extends ForwardingNormalizedNodeDataInput {
+    private DataInput input;
+    private NormalizedNodeDataInput delegate;
+
+    VersionedNormalizedNodeDataInput(final DataInput input) {
+        this.input = requireNonNull(input);
+    }
+
+    @Override
+    NormalizedNodeDataInput delegate() throws IOException {
+        if (delegate != null) {
+            return delegate;
+        }
+
+        final byte marker = input.readByte();
+        if (marker != TokenTypes.SIGNATURE_MARKER) {
+            throw defunct("Invalid signature marker: %d", marker);
+        }
+
+        final short version = input.readShort();
+        final NormalizedNodeDataInput ret;
+        switch (version) {
+            case TokenTypes.LITHIUM_VERSION:
+                ret = new LithiumNormalizedNodeInputStreamReader(input);
+                break;
+            case TokenTypes.NEON_SR2_VERSION:
+                ret = new NeonSR2NormalizedNodeInputStreamReader(input);
+                break;
+            case TokenTypes.SODIUM_SR1_VERSION:
+                ret = new SodiumSR1DataInput(input);
+                break;
+            case TokenTypes.MAGNESIUM_VERSION:
+                ret = new MagnesiumDataInput(input);
+                break;
+            default:
+                throw defunct("Unhandled stream version %s", version);
+        }
+
+        setDelegate(ret);
+        return ret;
+    }
+
+    private InvalidNormalizedNodeStreamException defunct(final String format, final Object... args) {
+        final InvalidNormalizedNodeStreamException ret = new InvalidNormalizedNodeStreamException(
+            String.format(format, args));
+        // Make sure the stream is not touched
+        setDelegate(new ForwardingNormalizedNodeDataInput() {
+            @Override
+            NormalizedNodeDataInput delegate() throws IOException {
+                throw new InvalidNormalizedNodeStreamException("Stream is not usable", ret);
+            }
+        });
+        return ret;
+    }
+
+    private void setDelegate(final NormalizedNodeDataInput delegate) {
+        this.delegate = requireNonNull(delegate);
+        input = null;
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractSerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AbstractSerializationTest.java
new file mode 100644 (file)
index 0000000..fe9da1f
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import com.google.common.io.ByteStreams;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.runners.Parameterized.Parameter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+public abstract class AbstractSerializationTest {
+
+    @Parameter(0)
+    public NormalizedNodeStreamVersion version;
+
+    final <T extends NormalizedNode<?, ?>> T assertEquals(final T node, final int expectedSize) {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(baos))) {
+            nnout.writeNormalizedNode(node);
+        } catch (IOException e) {
+            throw new AssertionError("Failed to serialize", e);
+        }
+
+        final byte[] bytes = baos.toByteArray();
+        Assert.assertEquals(expectedSize, bytes.length);
+
+        final NormalizedNode<?, ?> deser;
+        try {
+            deser = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes)).readNormalizedNode();
+        } catch (IOException e) {
+            throw new AssertionError("Failed to deserialize", e);
+        }
+        Assert.assertEquals(node, deser);
+        return node;
+    }
+
+    final <T> T assertEquals(final T value, final int expectedSize) {
+        return assertEquals(ImmutableNodes.leafNode(TestModel.TEST_QNAME, value), expectedSize).getValue();
+    }
+
+    final <T extends PathArgument> T assertEquals(final T arg, final int expectedSize) {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(baos))) {
+            nnout.writePathArgument(arg);
+        } catch (IOException e) {
+            throw new AssertionError("Failed to serialize", e);
+        }
+
+        final byte[] bytes = baos.toByteArray();
+        Assert.assertEquals(expectedSize, bytes.length);
+
+        final PathArgument deser;
+        try {
+            deser = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes)).readPathArgument();
+        } catch (IOException e) {
+            throw new AssertionError("Failed to deserialize", e);
+        }
+        Assert.assertEquals(arg, deser);
+        return (T) deser;
+    }
+
+    final void assertEqualsTwice(final PathArgument arg, final int expectedSize) {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(baos))) {
+            nnout.writePathArgument(arg);
+            nnout.writePathArgument(arg);
+        } catch (IOException e) {
+            throw new AssertionError("Failed to serialize", e);
+        }
+
+        final byte[] bytes = baos.toByteArray();
+        Assert.assertEquals(expectedSize, bytes.length);
+
+        try {
+            final NormalizedNodeDataInput in = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+            Assert.assertEquals(arg, in.readPathArgument());
+            Assert.assertEquals(arg, in.readPathArgument());
+        } catch (IOException e) {
+            throw new AssertionError("Failed to deserialize", e);
+        }
+    }
+
+    final void assertSame(final Object value, final int expectedSize) {
+        Assert.assertSame(value, assertEquals(value, expectedSize));
+    }
+
+    final void assertSame(final PathArgument arg, final int expectedSize) {
+        Assert.assertSame(arg, assertEquals(arg, expectedSize));
+    }
+
+    static final List<QName> generateQNames(final int size) {
+        final List<QName> qnames = new ArrayList<>(size);
+        for (int i = 0; i < size; ++i) {
+            qnames.add(QName.create(TestModel.TEST_QNAME, "a" + Integer.toHexString(i)));
+        }
+        return qnames;
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AidSerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/AidSerializationTest.java
new file mode 100644 (file)
index 0000000..ae4f288
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+
+@RunWith(Parameterized.class)
+public class AidSerializationTest extends AbstractSerializationTest {
+    @Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.LITHIUM,    8,  97, 530, 4162, 1_175_362, 2_158_407 },
+            new Object[] { NormalizedNodeStreamVersion.NEON_SR2,   9, 100, 421, 3145,   913_225,   913_231 },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1, 4,  94, 332, 2376,   716_618,   912_975 },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM,  4,  94, 332, 2376,   716_618,   912_975 });
+    }
+
+    @Parameter(1)
+    public int emptySize;
+    @Parameter(2)
+    public int oneSize;
+    @Parameter(3)
+    public int size29;
+    @Parameter(4)
+    public int size256;
+    @Parameter(5)
+    public int size65536;
+    @Parameter(6)
+    public int twiceSize65536;
+
+    @Test
+    public void testEmptyIdentifier() {
+        assertSame(fillIdentifier(0), emptySize);
+    }
+
+    @Test
+    public void testOneIdentifier() {
+        assertEquals(fillIdentifier(1), oneSize);
+    }
+
+    @Test
+    public void test29() {
+        assertEquals(fillIdentifier(29), size29);
+    }
+
+    @Test
+    public void test256() {
+        assertEquals(fillIdentifier(256), size256);
+    }
+
+    @Test
+    public void test65536() {
+        final AugmentationIdentifier id = fillIdentifier(65536);
+        assertEquals(id, size65536);
+        assertEqualsTwice(id, twiceSize65536);
+    }
+
+    private static AugmentationIdentifier fillIdentifier(final int size) {
+        final AugmentationIdentifier ret = AugmentationIdentifier.create(ImmutableSet.copyOf(generateQNames(size)));
+        Assert.assertEquals(size, ret.getPossibleChildNames().size());
+        return ret;
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/BitsSerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/BitsSerializationTest.java
new file mode 100644 (file)
index 0000000..57ef65b
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import com.google.common.io.ByteStreams;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+@RunWith(Parameterized.class)
+public class BitsSerializationTest extends AbstractSerializationTest {
+    @Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.LITHIUM,    100, 104, 229, 1538, 456_764, 785_890 },
+            new Object[] { NormalizedNodeStreamVersion.NEON_SR2,   102, 106, 231, 1540, 456_766, 785_882 },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1,  96, 100, 226, 1536, 456_764, 654_045 },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM,   96, 100, 226, 1536, 456_764, 654_045 });
+    }
+
+    @Parameter(1)
+    public int emptySize;
+    @Parameter(2)
+    public int oneSize;
+    @Parameter(3)
+    public int size29;
+    @Parameter(4)
+    public int size285;
+    @Parameter(5)
+    public int size65821;
+    @Parameter(6)
+    public int twiceSize65821;
+
+    @Test
+    public void testEmptyBytes() {
+        assertSame(ImmutableSet.of(), emptySize);
+    }
+
+    @Test
+    public void testOne() {
+        assertEquals(ImmutableSet.of("a"), oneSize);
+    }
+
+    @Test
+    public void test29() {
+        assertEquals(fillBits(29), size29);
+    }
+
+    @Test
+    public void test285() {
+        assertEquals(fillBits(285), size285);
+    }
+
+    @Test
+    public void test65821() {
+        assertEquals(fillBits(65821), size65821);
+    }
+
+    @Test
+    public void testTwice65536() {
+        final LeafNode<ImmutableSet<String>> leaf = ImmutableNodes.leafNode(TestModel.TEST_QNAME, fillBits(65821));
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(baos))) {
+            nnout.writeNormalizedNode(leaf);
+            nnout.writeNormalizedNode(leaf);
+        } catch (IOException e) {
+            throw new AssertionError("Failed to serialize", e);
+        }
+
+        final byte[] bytes = baos.toByteArray();
+        Assert.assertEquals(twiceSize65821, bytes.length);
+
+        try {
+            final NormalizedNodeDataInput input = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+            Assert.assertEquals(leaf, input.readNormalizedNode());
+            Assert.assertEquals(leaf, input.readNormalizedNode());
+        } catch (IOException e) {
+            throw new AssertionError("Failed to deserialize", e);
+        }
+    }
+
+    private static ImmutableSet<String> fillBits(final int size) {
+        final Builder<String> builder = ImmutableSet.builderWithExpectedSize(size);
+        for (int i = 0; i < size; ++i) {
+            builder.add(Integer.toHexString(i));
+        }
+        final ImmutableSet<String> ret = builder.build();
+        Assert.assertEquals(size, ret.size());
+        return ret;
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/BooleanSerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/BooleanSerializationTest.java
new file mode 100644 (file)
index 0000000..37c7408
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class BooleanSerializationTest extends AbstractSerializationTest {
+    @Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.LITHIUM,    97 },
+            new Object[] { NormalizedNodeStreamVersion.NEON_SR2,   99 },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1, 96 },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM,  96 });
+    }
+
+    @Parameter(1)
+    public int size;
+
+    @Test
+    public void testTrue() {
+        assertEquals(Boolean.TRUE, size);
+    }
+
+    @Test
+    public void testFalse() {
+        assertEquals(Boolean.FALSE, size);
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/BytesSerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/BytesSerializationTest.java
new file mode 100644 (file)
index 0000000..7a8c58e
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.util.Arrays;
+import java.util.concurrent.ThreadLocalRandom;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class BytesSerializationTest extends AbstractSerializationTest {
+    private static final byte[] BINARY_128 = randomBytes(128);
+    private static final byte[] BINARY_384 = randomBytes(384);
+    private static final byte[] BINARY_65920 = randomBytes(65920);
+
+    @Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.LITHIUM,    100, 101, 228, 484, 66020 },
+            new Object[] { NormalizedNodeStreamVersion.NEON_SR2,   102, 103, 230, 486, 66022 },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1,  96,  97, 225, 482, 66020 },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM,   96,  97, 225, 482, 66020 });
+    }
+
+    @Parameter(1)
+    public int emptySize;
+    @Parameter(2)
+    public int oneSize;
+    @Parameter(3)
+    public int size128;
+    @Parameter(4)
+    public int size384;
+    @Parameter(5)
+    public int size65920;
+
+    @Test
+    public void testEmptyBytes() {
+        assertEquals(new byte[0], emptySize);
+    }
+
+    @Test
+    public void testOne() {
+        assertEquals(randomBytes(1), oneSize);
+    }
+
+    @Test
+    public void test128() {
+        assertEquals(BINARY_128, size128);
+    }
+
+    @Test
+    public void test384() {
+        assertEquals(BINARY_384, size384);
+    }
+
+    @Test
+    public void test65920() {
+        assertEquals(BINARY_65920, size65920);
+    }
+
+    private static byte[] randomBytes(final int size) {
+        final byte[] ret = new byte[size];
+        ThreadLocalRandom.current().nextBytes(ret);
+        return ret;
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/IntSerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/IntSerializationTest.java
new file mode 100644 (file)
index 0000000..f2572fb
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IntSerializationTest extends AbstractSerializationTest {
+    @Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.LITHIUM,    97, 97, 98,   98, 100, 100, 100, 104, 104, 104 },
+            new Object[] { NormalizedNodeStreamVersion.NEON_SR2,   99, 99, 100, 100, 102, 102, 102, 106, 106, 106 },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1, 96, 97, 96,   98,  96,  98, 100,  96, 100, 104 },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM,  96, 97, 96,   98,  96,  98, 100,  96, 100, 104 });
+    }
+
+    @Parameter(1)
+    public int expectedByte;
+    @Parameter(2)
+    public int expectedByteOne;
+    @Parameter(3)
+    public int expectedShort;
+    @Parameter(4)
+    public int expectedShortOne;
+    @Parameter(5)
+    public int expectedInt;
+    @Parameter(6)
+    public int expectedIntOne;
+    @Parameter(7)
+    public int expectedIntMax;
+    @Parameter(8)
+    public int expectedLong;
+    @Parameter(9)
+    public int expectedLongOne;
+    @Parameter(10)
+    public int expectedLongMax;
+
+    @Test
+    public void testByte() {
+        assertSame((byte) 0, expectedByte);
+        assertSame((byte) 1, expectedByteOne);
+        assertSame(Byte.MAX_VALUE, expectedByteOne);
+    }
+
+    @Test
+    public void testShort() {
+        assertSame((short) 0, expectedShort);
+        assertSame((short) 1, expectedShortOne);
+        assertSame(Short.MAX_VALUE, expectedShortOne);
+    }
+
+    @Test
+    public void testInt() {
+        assertSame(0, expectedInt);
+        assertSame(1, expectedIntOne);
+        assertSame(Integer.MAX_VALUE, expectedIntMax);
+    }
+
+    @Test
+    public void testLong() {
+        assertSame(0L, expectedLong);
+        assertSame(1L, expectedLongOne);
+        assertSame(Long.MAX_VALUE, expectedLongMax);
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumWriteObjectMappingTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/LithiumWriteObjectMappingTest.java
new file mode 100644 (file)
index 0000000..c82d60b
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.binfmt;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class LithiumWriteObjectMappingTest {
+    @Test
+    public void testStringType() {
+        assertEquals(LithiumValue.STRING_TYPE, AbstractLithiumDataOutput.getSerializableType("foobar"));
+        final String largeString = largeString(LithiumValue.STRING_BYTES_LENGTH_THRESHOLD);
+        assertEquals(LithiumValue.STRING_BYTES_TYPE, AbstractLithiumDataOutput.getSerializableType(largeString));
+    }
+
+    private static String largeString(final int minSize) {
+        final int pow = (int) (Math.log(minSize * 2) / Math.log(2));
+        StringBuilder sb = new StringBuilder("X");
+        for (int i = 0; i < pow; i++) {
+            sb.append(sb);
+        }
+        return sb.toString();
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MapEntrySerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/MapEntrySerializationTest.java
new file mode 100644 (file)
index 0000000..0a4b7b3
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import com.google.common.collect.Maps;
+import java.util.Arrays;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+
+@RunWith(Parameterized.class)
+public class MapEntrySerializationTest extends AbstractSerializationTest {
+    @Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.LITHIUM,    100, 139, 178, 10_324 },
+            new Object[] { NormalizedNodeStreamVersion.NEON_SR2,   102, 127, 152,  6_742 },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1,  96, 110, 125,  3_927 },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM,   96, 110, 125,  3_927 });
+    }
+
+    @Parameter(1)
+    public int emptySize;
+    @Parameter(2)
+    public int oneSize;
+    @Parameter(3)
+    public int twoSize;
+    @Parameter(4)
+    public int size256;
+
+    @Test
+    public void testEmptyIdentifier() {
+        assertEquals(createEntry(0), emptySize);
+    }
+
+    @Test
+    public void testOneIdentifier() {
+        assertEquals(createEntry(1), oneSize);
+    }
+
+    @Test
+    public void testTwoIdentifiers() {
+        assertEquals(createEntry(2), twoSize);
+    }
+
+    @Test
+    public void test256() {
+        assertEquals(createEntry(256), size256);
+    }
+
+    private static MapEntryNode createEntry(final int size) {
+        final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder = Builders.mapEntryBuilder();
+        final Map<QName, Object> predicates = Maps.newHashMapWithExpectedSize(size);
+        for (QName qname : generateQNames(size)) {
+            builder.withChild(ImmutableNodes.leafNode(qname, "a"));
+            predicates.put(qname, "a");
+        }
+
+        return builder.withNodeIdentifier(NodeIdentifierWithPredicates.of(TestModel.TEST_QNAME, predicates)).build();
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NipSerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NipSerializationTest.java
new file mode 100644 (file)
index 0000000..71b05d6
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+
+@RunWith(Parameterized.class)
+public class NipSerializationTest extends AbstractSerializationTest {
+    @Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.LITHIUM,     99, 118, 194, 5203, 1_443_411, 2_693_479 },
+            new Object[] { NormalizedNodeStreamVersion.NEON_SR2,   101, 116, 176, 4181, 1_180_245, 1_772_383 },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1,  95, 107, 156, 3409,   982_867, 1_443_164 },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM,   95, 107, 156, 3409,   982_867, 1_443_164 });
+    }
+
+    @Parameter(1)
+    public int emptySize;
+    @Parameter(2)
+    public int oneSize;
+    @Parameter(3)
+    public int size5;
+    @Parameter(4)
+    public int size256;
+    @Parameter(5)
+    public int size65792;
+    @Parameter(6)
+    public int twiceSize65792;
+
+    @Test
+    public void testEmptyIdentifier() {
+        assertEquals(createIdentifier(0), emptySize);
+    }
+
+    @Test
+    public void testOneIdentifier() {
+        assertEquals(createIdentifier(1), oneSize);
+    }
+
+    @Test
+    public void test5() {
+        assertEquals(createIdentifier(5), size5);
+    }
+
+    @Test
+    public void test256() {
+        assertEquals(createIdentifier(256), size256);
+    }
+
+    @Test
+    public void test65536() {
+        assertEquals(createIdentifier(65792), size65792);
+    }
+
+    @Test
+    public void testTwice65792() {
+        final NodeIdentifierWithPredicates nip = createIdentifier(65792);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(baos))) {
+            nnout.writePathArgument(nip);
+            nnout.writePathArgument(nip);
+        } catch (IOException e) {
+            throw new AssertionError("Failed to serialize", e);
+        }
+
+        final byte[] bytes = baos.toByteArray();
+        Assert.assertEquals(twiceSize65792, bytes.length);
+
+        try {
+            final NormalizedNodeDataInput input = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+            Assert.assertEquals(nip, input.readPathArgument());
+            Assert.assertEquals(nip, input.readPathArgument());
+        } catch (IOException e) {
+            throw new AssertionError("Failed to deserialize", e);
+        }
+    }
+
+    private static NodeIdentifierWithPredicates createIdentifier(final int size) {
+        final Map<QName, Object> predicates = Maps.newHashMapWithExpectedSize(size);
+        for (QName qname : generateQNames(size)) {
+            predicates.put(qname, "a");
+        }
+        return NodeIdentifierWithPredicates.of(TestModel.TEST_QNAME, predicates);
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeStreamReaderWriterTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/NormalizedNodeStreamReaderWriterTest.java
new file mode 100644 (file)
index 0000000..2f952c4
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2014, 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.binfmt;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+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.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.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+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.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetEntryNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+
+@RunWith(Parameterized.class)
+public class NormalizedNodeStreamReaderWriterTest {
+    enum Unsigned implements Function<String, Number> {
+        BIG_INTEGER {
+            @Override
+            public BigInteger apply(final String str) {
+                return new BigInteger(str);
+            }
+        },
+        UINT64 {
+            @Override
+            public Uint64 apply(final String str) {
+                return Uint64.valueOf(str);
+            }
+        };
+    }
+
+    @Parameters(name = "{0} {1}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.LITHIUM,    Unsigned.BIG_INTEGER,
+                1_050_286, 9_577_973, 171, 1_553, 103, 237,  98 },
+            new Object[] { NormalizedNodeStreamVersion.NEON_SR2,   Unsigned.BIG_INTEGER,
+                1_049_950, 5_577_993, 161, 1_163, 105, 235, 100 },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1, Unsigned.BIG_INTEGER,
+                1_049_619, 2_289_103, 139,   826, 103, 229,  99 },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1, Unsigned.UINT64,
+                1_049_618, 2_289_103, 139,   825, 103, 229,  99 },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM,  Unsigned.UINT64,
+                1_049_618, 2_289_103, 139,   825, 103, 229,  99 });
+    }
+
+    @Parameter(0)
+    public NormalizedNodeStreamVersion version;
+    @Parameter(1)
+    public Unsigned uint64;
+    @Parameter(2)
+    public int normalizedNodeStreamingSize;
+    @Parameter(3)
+    public int hugeEntriesSize;
+    @Parameter(4)
+    public int yiidStreamingSize;
+    @Parameter(5)
+    public int nnYiidStreamingSize;
+    @Parameter(6)
+    public int writePathArgumentSize;
+    @Parameter(7)
+    public int anyxmlStreamingSize;
+    @Parameter(8)
+    public int schemaPathSize;
+
+    @Test
+    public void testNormalizedNodeStreaming() throws IOException {
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(bos));
+
+        NormalizedNode<?, ?> testContainer = createTestContainer();
+        nnout.writeNormalizedNode(testContainer);
+
+        QName toaster = QName.create("http://netconfcentral.org/ns/toaster","2009-11-20","toaster");
+        QName darknessFactor = QName.create("http://netconfcentral.org/ns/toaster","2009-11-20","darknessFactor");
+        QName description = QName.create("http://netconfcentral.org/ns/toaster","2009-11-20","description");
+        ContainerNode toasterNode = Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(toaster))
+                .withChild(ImmutableNodes.leafNode(darknessFactor, "1000"))
+                .withChild(ImmutableNodes.leafNode(description, largeString(20))).build();
+
+        ContainerNode toasterContainer = Builders.containerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME)).withChild(toasterNode).build();
+        nnout.writeNormalizedNode(toasterContainer);
+
+        final byte[] bytes = bos.toByteArray();
+        assertEquals(normalizedNodeStreamingSize, bytes.length);
+
+        NormalizedNodeDataInput nnin = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+
+        NormalizedNode<?, ?> node = nnin.readNormalizedNode();
+        Assert.assertEquals(testContainer, node);
+
+        node = nnin.readNormalizedNode();
+        Assert.assertEquals(toasterContainer, node);
+    }
+
+    private NormalizedNode<?, ?> createTestContainer() {
+        byte[] bytes1 = {1, 2, 3};
+        LeafSetEntryNode<Object> entry1 = ImmutableLeafSetEntryNodeBuilder.create().withNodeIdentifier(
+                new NodeWithValue<>(TestModel.BINARY_LEAF_LIST_QNAME, bytes1)).withValue(bytes1).build();
+
+        byte[] bytes2 = {};
+        LeafSetEntryNode<Object> entry2 = ImmutableLeafSetEntryNodeBuilder.create().withNodeIdentifier(
+                new NodeWithValue<>(TestModel.BINARY_LEAF_LIST_QNAME, bytes2)).withValue(bytes2).build();
+
+        return TestModel.createBaseTestContainerBuilder(uint64)
+                .withChild(ImmutableLeafSetNodeBuilder.create().withNodeIdentifier(
+                        new NodeIdentifier(TestModel.BINARY_LEAF_LIST_QNAME))
+                        .withChild(entry1).withChild(entry2).build())
+                .withChild(ImmutableNodes.leafNode(TestModel.SOME_BINARY_DATA_QNAME, new byte[]{1, 2, 3, 4}))
+                .withChild(ImmutableNodes.leafNode(TestModel.EMPTY_QNAME, Empty.getInstance()))
+                .withChild(Builders.orderedMapBuilder()
+                      .withNodeIdentifier(new NodeIdentifier(TestModel.ORDERED_LIST_QNAME))
+                      .withChild(ImmutableNodes.mapEntry(TestModel.ORDERED_LIST_ENTRY_QNAME,
+                              TestModel.ID_QNAME, 11)).build()).build();
+    }
+
+    @Test
+    public void testYangInstanceIdentifierStreaming() throws IOException  {
+        YangInstanceIdentifier path = YangInstanceIdentifier.builder(TestModel.TEST_PATH)
+                .node(TestModel.OUTER_LIST_QNAME).nodeWithKey(
+                        TestModel.INNER_LIST_QNAME, TestModel.ID_QNAME, 10).build();
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(bos));
+
+        nnout.writeYangInstanceIdentifier(path);
+
+        final byte[] bytes = bos.toByteArray();
+        assertEquals(yiidStreamingSize, bytes.length);
+
+        NormalizedNodeDataInput nnin = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+
+        YangInstanceIdentifier newPath = nnin.readYangInstanceIdentifier();
+        Assert.assertEquals(path, newPath);
+    }
+
+    @Test
+    public void testNormalizedNodeAndYangInstanceIdentifierStreaming() throws IOException {
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        NormalizedNodeDataOutput writer = version.newDataOutput(
+            ByteStreams.newDataOutput(bos));
+
+        NormalizedNode<?, ?> testContainer = TestModel.createBaseTestContainerBuilder(uint64).build();
+        writer.writeNormalizedNode(testContainer);
+
+        YangInstanceIdentifier path = YangInstanceIdentifier.builder(TestModel.TEST_PATH)
+                .node(TestModel.OUTER_LIST_QNAME).nodeWithKey(
+                        TestModel.INNER_LIST_QNAME, TestModel.ID_QNAME, 10).build();
+
+        writer.writeYangInstanceIdentifier(path);
+
+        final byte[] bytes = bos.toByteArray();
+        assertEquals(nnYiidStreamingSize, bytes.length);
+
+        NormalizedNodeDataInput reader = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+
+        NormalizedNode<?,?> node = reader.readNormalizedNode();
+        Assert.assertEquals(testContainer, node);
+
+        YangInstanceIdentifier newPath = reader.readYangInstanceIdentifier();
+        Assert.assertEquals(path, newPath);
+
+        writer.close();
+    }
+
+    @Test(expected = InvalidNormalizedNodeStreamException.class, timeout = 10000)
+    public void testInvalidNormalizedNodeStream() throws IOException {
+        byte[] invalidBytes = {1, 2, 3};
+        NormalizedNodeDataInput reader = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(invalidBytes));
+
+        reader.readNormalizedNode();
+    }
+
+    @Test(expected = InvalidNormalizedNodeStreamException.class, timeout = 10000)
+    public void testInvalidYangInstanceIdentifierStream() throws IOException {
+        byte[] invalidBytes = {1,2,3};
+        NormalizedNodeDataInput reader = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(invalidBytes));
+
+        reader.readYangInstanceIdentifier();
+    }
+
+    @Test
+    public void testWithSerializable() {
+        NormalizedNode<?, ?> input = TestModel.createTestContainer(uint64);
+        SampleNormalizedNodeSerializable serializable = new SampleNormalizedNodeSerializable(version, input);
+        SampleNormalizedNodeSerializable clone = clone(serializable);
+        Assert.assertEquals(input, clone.getInput());
+    }
+
+    @Test
+    public void testAnyXmlStreaming() throws Exception {
+        String xml = "<foo xmlns=\"http://www.w3.org/TR/html4/\" x=\"123\"><bar>one</bar><bar>two</bar></foo>";
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+
+        Node xmlNode = factory.newDocumentBuilder().parse(
+                new InputSource(new StringReader(xml))).getDocumentElement();
+
+        assertEquals("http://www.w3.org/TR/html4/", xmlNode.getNamespaceURI());
+
+        NormalizedNode<?, ?> anyXmlContainer = ImmutableContainerNodeBuilder.create().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME)).withChild(
+                        Builders.anyXmlBuilder().withNodeIdentifier(new NodeIdentifier(TestModel.ANY_XML_QNAME))
+                            .withValue(new DOMSource(xmlNode)).build()).build();
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(bos));
+
+        nnout.writeNormalizedNode(anyXmlContainer);
+
+        final byte[] bytes = bos.toByteArray();
+        assertEquals(anyxmlStreamingSize, bytes.length);
+
+        NormalizedNodeDataInput nnin = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+
+        ContainerNode deserialized = (ContainerNode)nnin.readNormalizedNode();
+
+        Optional<DataContainerChild<? extends PathArgument, ?>> child =
+                deserialized.getChild(new NodeIdentifier(TestModel.ANY_XML_QNAME));
+        assertEquals("AnyXml child present", true, child.isPresent());
+
+        StreamResult xmlOutput = new StreamResult(new StringWriter());
+        Transformer transformer = TransformerFactory.newInstance().newTransformer();
+        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+        transformer.transform(((AnyXmlNode)child.get()).getValue(), xmlOutput);
+
+        assertEquals("XML", xml, xmlOutput.getWriter().toString());
+        assertEquals("http://www.w3.org/TR/html4/",
+            ((AnyXmlNode)child.get()).getValue().getNode().getNamespaceURI());
+    }
+
+    @Test
+    public void testSchemaPathSerialization() throws IOException {
+        final SchemaPath expected = SchemaPath.create(true, TestModel.ANY_XML_QNAME);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(bos))) {
+            nnout.writeSchemaPath(expected);
+        }
+
+        final byte[] bytes = bos.toByteArray();
+        assertEquals(schemaPathSize, bytes.length);
+
+        NormalizedNodeDataInput nnin = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+        assertEquals(expected, nnin.readSchemaPath());
+    }
+
+    @Test
+    public void testWritePathArgument() throws IOException {
+        final NodeIdentifier expected = new NodeIdentifier(TestModel.BOOLEAN_LEAF_QNAME);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+        try (NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(bos))) {
+            nnout.writePathArgument(expected);
+        }
+
+        final byte[] bytes = bos.toByteArray();
+        assertEquals(writePathArgumentSize, bytes.length);
+
+        NormalizedNodeDataInput nnin = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+        assertEquals(expected, nnin.readPathArgument());
+    }
+
+    /*
+     * This tests the encoding of a MapNode with a lot of entries, each of them having the key leaf (a string)
+     * and an integer.
+     */
+    @Test
+    public void testHugeEntries() throws IOException {
+        final CollectionNodeBuilder<MapEntryNode, MapNode> mapBuilder = Builders.mapBuilder()
+                .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME));
+        final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> entryBuilder =
+                Builders.mapEntryBuilder().withChild(ImmutableNodes.leafNode(TestModel.DESC_QNAME, (byte) 42));
+
+        for (int i = 0; i < 100_000; ++i) {
+            final String key = "xyzzy" + i;
+            mapBuilder.addChild(entryBuilder
+                .withNodeIdentifier(NodeIdentifierWithPredicates.of(TestModel.TEST_QNAME,
+                    TestModel.CHILD_NAME_QNAME, key))
+                .withChild(ImmutableNodes.leafNode(TestModel.CHILD_NAME_QNAME, key))
+                .build());
+        }
+
+        final MapNode expected = mapBuilder.build();
+        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+        try (NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(bos))) {
+            nnout.writeNormalizedNode(expected);
+        }
+
+        final byte[] bytes = bos.toByteArray();
+        assertEquals(hugeEntriesSize, bytes.length);
+
+        NormalizedNodeDataInput nnin = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+        assertEquals(expected, nnin.readNormalizedNode());
+    }
+
+    @Test
+    public void testAugmentationIdentifier() throws IOException {
+        final List<QName> qnames = new ArrayList<>();
+        for (int i = 0; i < 257; ++i) {
+            qnames.add(QName.create(TestModel.TEST_QNAME, "a" + Integer.toHexString(i)));
+        }
+
+        for (int i = 0; i < qnames.size(); ++i) {
+            assertAugmentationIdentifier(AugmentationIdentifier.create(ImmutableSet.copyOf(qnames.subList(0, i))));
+        }
+
+        for (int i = qnames.size(); i < 65536; ++i) {
+            qnames.add(QName.create(TestModel.TEST_QNAME, "a" + Integer.toHexString(i)));
+        }
+        assertAugmentationIdentifier(AugmentationIdentifier.create(ImmutableSet.copyOf(qnames)));
+    }
+
+    private void assertAugmentationIdentifier(final AugmentationIdentifier expected) throws IOException {
+        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(bos))) {
+            nnout.writePathArgument(expected);
+        }
+
+        final byte[] bytes = bos.toByteArray();
+
+        NormalizedNodeDataInput nnin = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+        PathArgument arg = nnin.readPathArgument();
+        assertEquals(expected, arg);
+    }
+
+    private static <T extends Serializable> T clone(final T obj) {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
+        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
+            oos.writeObject(obj);
+        } catch (IOException e) {
+            throw new AssertionError("Failed to serialize object", e);
+        }
+
+        final byte[] bytes = baos.toByteArray();
+        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
+            return (T) ois.readObject();
+        } catch (ClassNotFoundException | IOException e) {
+            throw new AssertionError("Failed to deserialize object", e);
+        }
+    }
+
+    private static String largeString(final int pow) {
+        StringBuilder sb = new StringBuilder("X");
+        for (int i = 0; i < pow; i++) {
+            sb.append(sb);
+        }
+        return sb.toString();
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/QNameFactoryTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/QNameFactoryTest.java
new file mode 100644 (file)
index 0000000..cdc0a8f
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2014, 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.binfmt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.Revision;
+
+public class QNameFactoryTest {
+    @Test
+    public void testBasic() {
+        QName expected = TestModel.AUG_NAME_QNAME;
+        QName created = lookup(expected);
+        assertNotSame(expected, created);
+        assertEquals(expected, created);
+
+        QName cached = lookup(expected);
+        assertSame(created, cached);
+    }
+
+    private static QName lookup(final QName qname) {
+        return QNameFactory.create(qname.getLocalName(), qname.getNamespace().toString(),
+            qname.getRevision().map(Revision::toString).orElse(null));
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/SampleNormalizedNodeSerializable.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/SampleNormalizedNodeSerializable.java
new file mode 100644 (file)
index 0000000..c7666e5
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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.binfmt;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class SampleNormalizedNodeSerializable implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private transient NormalizedNodeStreamVersion version;
+    private NormalizedNode<?, ?> input;
+
+    public SampleNormalizedNodeSerializable(final NormalizedNodeStreamVersion version,
+            final NormalizedNode<?, ?> input) {
+        this.version = requireNonNull(version);
+        this.input = input;
+    }
+
+    public NormalizedNode<?, ?> getInput() {
+        return input;
+    }
+
+    private void readObject(final ObjectInputStream stream) throws IOException {
+        final NormalizedNodeDataInput in = NormalizedNodeDataInput.newDataInput(stream);
+        this.input = in.readNormalizedNode();
+        this.version = in.getVersion();
+    }
+
+    private void writeObject(final ObjectOutputStream stream) throws IOException {
+        version.newDataOutput(stream).writeNormalizedNode(input);
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/StringSerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/StringSerializationTest.java
new file mode 100644 (file)
index 0000000..a982390
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.google.common.base.Strings;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StringSerializationTest extends AbstractSerializationTest {
+    private static final String STR_MEDIUM = Strings.repeat("a", 32767);
+    private static final String STR_HUGE = Strings.repeat("©", 16777216);
+
+    @Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.LITHIUM,     98,  99, 32867, 33554532 },
+            new Object[] { NormalizedNodeStreamVersion.NEON_SR2,   100, 101, 32869, 33554534 },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1,  96,  99, 32865, 33554532 },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM,   96,  99, 32865, 33554532 });
+    }
+
+    @Parameter(1)
+    public int emptySize;
+    @Parameter(2)
+    public int oneSize;
+    @Parameter(3)
+    public int mediumSize;
+    @Parameter(4)
+    public int hugeSize;
+
+    @Test
+    public void testEmptyString() {
+        assertEquals("", emptySize);
+    }
+
+    @Test
+    public void testEmptySame() {
+        assumeTrue(version.compareTo(NormalizedNodeStreamVersion.SODIUM_SR1) >= 0);
+        assertSame("", emptySize);
+    }
+
+    @Test
+    public void testOne() {
+        assertEquals("a", oneSize);
+    }
+
+    @Test
+    public void testMedium() {
+        assertEquals(STR_MEDIUM, mediumSize);
+    }
+
+    @Test
+    public void testHuge() {
+        assertEquals(STR_HUGE, hugeSize);
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/TestModel.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/TestModel.java
new file mode 100644 (file)
index 0000000..0e8463e
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2014, 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.binfmt;
+
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapEntry;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapEntryBuilder;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapNodeBuilder;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.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.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.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetEntryNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
+
+public final class TestModel {
+
+    static final QName TEST_QNAME = QName.create(
+            "urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test",
+            "2014-03-13", "test");
+
+    static final QName AUG_NAME_QNAME = QName.create(
+            "urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:aug",
+            "2014-03-13", "name");
+
+    private static final QName AUG_CONT_QNAME = QName.create(
+            "urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:aug",
+            "2014-03-13", "cont");
+
+    static final QName DESC_QNAME = QName.create(TEST_QNAME, "desc");
+    static final QName SOME_BINARY_DATA_QNAME = QName.create(TEST_QNAME, "some-binary-data");
+    static final QName BINARY_LEAF_LIST_QNAME = QName.create(TEST_QNAME, "binary_leaf_list");
+    private static final QName SOME_REF_QNAME = QName.create(TEST_QNAME, "some-ref");
+    private static final QName MYIDENTITY_QNAME = QName.create(TEST_QNAME, "myidentity");
+    private static final QName SWITCH_FEATURES_QNAME = QName.create(TEST_QNAME, "switch-features");
+
+    private static final QName AUGMENTED_LIST_QNAME = QName.create(TEST_QNAME, "augmented-list");
+    static final QName AUGMENTED_LIST_ENTRY_QNAME = QName.create(TEST_QNAME, "augmented-list-entry");
+
+    static final QName OUTER_LIST_QNAME = QName.create(TEST_QNAME, "outer-list");
+    static final QName INNER_LIST_QNAME = QName.create(TEST_QNAME, "inner-list");
+    static final QName ID_QNAME = QName.create(TEST_QNAME, "id");
+    private static final QName NAME_QNAME = QName.create(TEST_QNAME, "name");
+    static final QName BOOLEAN_LEAF_QNAME = QName.create(TEST_QNAME, "boolean-leaf");
+    private static final QName SHORT_LEAF_QNAME = QName.create(TEST_QNAME, "short-leaf");
+    private static final QName BYTE_LEAF_QNAME = QName.create(TEST_QNAME, "byte-leaf");
+    private static final QName BIGINTEGER_LEAF_QNAME = QName.create(TEST_QNAME, "biginteger-leaf");
+    private static final QName BIGDECIMAL_LEAF_QNAME = QName.create(TEST_QNAME, "bigdecimal-leaf");
+    static final QName ORDERED_LIST_QNAME = QName.create(TEST_QNAME, "ordered-list");
+    static final QName ORDERED_LIST_ENTRY_QNAME = QName.create(TEST_QNAME, "ordered-list-leaf");
+    private static final QName UNKEYED_LIST_QNAME = QName.create(TEST_QNAME, "unkeyed-list");
+    private static final QName SHOE_QNAME = QName.create(TEST_QNAME, "shoe");
+    static final QName ANY_XML_QNAME = QName.create(TEST_QNAME, "any");
+    static final QName EMPTY_QNAME = QName.create(TEST_QNAME, "empty-leaf");
+
+    static final YangInstanceIdentifier TEST_PATH = YangInstanceIdentifier.of(TEST_QNAME);
+    private static final QName TWO_THREE_QNAME = QName.create(TEST_QNAME, "two");
+    private static final QName TWO_QNAME = QName.create(TEST_QNAME, "two");
+
+    private static final Integer ONE_ID = 1;
+    private static final Integer TWO_ID = 2;
+    private static final String TWO_ONE_NAME = "one";
+    private static final String TWO_TWO_NAME = "two";
+    private static final String DESC = "Hello there";
+    private static final Boolean ENABLED = true;
+    private static final Short SHORT_ID = 1;
+    private static final Byte BYTE_ID = 1;
+    // Family specific constants
+    private static final QName FAMILY_QNAME = QName.create(
+        "urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:notification-test", "2014-04-17", "family");
+    private static final QName CHILDREN_QNAME = QName.create(FAMILY_QNAME, "children");
+    static final QName CHILD_NUMBER_QNAME = QName.create(FAMILY_QNAME, "child-number");
+    static final QName CHILD_NAME_QNAME = QName.create(FAMILY_QNAME, "child-name");
+
+    private static final String FIRST_CHILD_NAME = "first child";
+
+    private static final MapEntryNode BAR_NODE = mapEntryBuilder(
+            OUTER_LIST_QNAME, ID_QNAME, TWO_ID)
+            .withChild(mapNodeBuilder(INNER_LIST_QNAME)
+                    .withChild(mapEntry(INNER_LIST_QNAME, NAME_QNAME, TWO_ONE_NAME))
+                    .withChild(mapEntry(INNER_LIST_QNAME, NAME_QNAME, TWO_TWO_NAME))
+                    .build())
+            .build();
+
+    private TestModel() {
+        throw new UnsupportedOperationException();
+    }
+
+    public static DataContainerNodeBuilder<NodeIdentifier, ContainerNode> createBaseTestContainerBuilder(
+            final Function<String, Number> uint64) {
+        // Create a list of shoes
+        // This is to test leaf list entry
+        final LeafSetEntryNode<Object> nike = ImmutableLeafSetEntryNodeBuilder.create().withNodeIdentifier(
+                new NodeWithValue<>(SHOE_QNAME, "nike")).withValue("nike").build();
+
+        final LeafSetEntryNode<Object> puma = ImmutableLeafSetEntryNodeBuilder.create().withNodeIdentifier(
+                new NodeWithValue<>(SHOE_QNAME, "puma")).withValue("puma").build();
+
+        final LeafSetNode<Object> shoes = ImmutableLeafSetNodeBuilder.create().withNodeIdentifier(
+                new NodeIdentifier(SHOE_QNAME)).withChild(nike).withChild(puma).build();
+
+        // Test a leaf-list where each entry contains an identity
+        final LeafSetEntryNode<Object> cap1 =
+                ImmutableLeafSetEntryNodeBuilder
+                        .create()
+                        .withNodeIdentifier(
+                                new NodeWithValue<>(QName.create(
+                                        TEST_QNAME, "capability"), DESC_QNAME))
+                        .withValue(DESC_QNAME).build();
+
+        final LeafSetNode<Object> capabilities =
+                ImmutableLeafSetNodeBuilder
+                        .create()
+                        .withNodeIdentifier(
+                                new NodeIdentifier(QName.create(
+                                        TEST_QNAME, "capability"))).withChild(cap1).build();
+
+        ContainerNode switchFeatures =
+                ImmutableContainerNodeBuilder
+                        .create()
+                        .withNodeIdentifier(
+                                new NodeIdentifier(SWITCH_FEATURES_QNAME))
+                        .withChild(capabilities).build();
+
+        // Create a leaf list with numbers
+        final LeafSetEntryNode<Object> five =
+                ImmutableLeafSetEntryNodeBuilder
+                        .create()
+                        .withNodeIdentifier(
+                                new NodeWithValue<>(QName.create(
+                                        TEST_QNAME, "number"), 5)).withValue(5).build();
+        final LeafSetEntryNode<Object> fifteen =
+                ImmutableLeafSetEntryNodeBuilder
+                        .create()
+                        .withNodeIdentifier(
+                                new NodeWithValue<>(QName.create(
+                                        TEST_QNAME, "number"), 15)).withValue(15).build();
+        final LeafSetNode<Object> numbers =
+                ImmutableLeafSetNodeBuilder
+                        .create()
+                        .withNodeIdentifier(
+                                new NodeIdentifier(QName.create(
+                                        TEST_QNAME, "number"))).withChild(five).withChild(fifteen)
+                        .build();
+
+
+        // Create augmentations
+        MapEntryNode augMapEntry = createAugmentedListEntry(1, "First Test");
+
+        // Create a bits leaf
+        NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>>
+                myBits = Builders.leafBuilder()
+                .withNodeIdentifier(new NodeIdentifier(QName.create(TEST_QNAME, "my-bits")))
+                .withValue(ImmutableSet.of("foo", "bar"));
+
+        // Create unkeyed list entry
+        UnkeyedListEntryNode unkeyedListEntry =
+                Builders.unkeyedListEntryBuilder().withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(
+                    UNKEYED_LIST_QNAME)).withChild(ImmutableNodes.leafNode(NAME_QNAME, "unkeyed-entry-name")).build();
+
+        // Create YangInstanceIdentifier with all path arg types.
+        YangInstanceIdentifier instanceID = YangInstanceIdentifier.create(
+                new NodeIdentifier(QName.create(TEST_QNAME, "qname")),
+                NodeIdentifierWithPredicates.of(QName.create(TEST_QNAME, "list-entry"),
+                        QName.create(TEST_QNAME, "key"), 10),
+                new AugmentationIdentifier(ImmutableSet.of(
+                        QName.create(TEST_QNAME, "aug1"), QName.create(TEST_QNAME, "aug2"))),
+                new NodeWithValue<>(QName.create(TEST_QNAME, "leaf-list-entry"), "foo"));
+
+        Map<QName, Object> keyValues = new HashMap<>();
+        keyValues.put(CHILDREN_QNAME, FIRST_CHILD_NAME);
+
+
+        // Create the document
+        return ImmutableContainerNodeBuilder
+                .create()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TEST_QNAME))
+                .withChild(myBits.build())
+                .withChild(ImmutableNodes.leafNode(DESC_QNAME, DESC))
+                .withChild(ImmutableNodes.leafNode(BOOLEAN_LEAF_QNAME, ENABLED))
+                .withChild(ImmutableNodes.leafNode(SHORT_LEAF_QNAME, SHORT_ID))
+                .withChild(ImmutableNodes.leafNode(BYTE_LEAF_QNAME, BYTE_ID))
+                .withChild(ImmutableNodes.leafNode(TestModel.BIGINTEGER_LEAF_QNAME, uint64.apply("100")))
+                .withChild(ImmutableNodes.leafNode(TestModel.BIGDECIMAL_LEAF_QNAME, BigDecimal.valueOf(1.2)))
+                .withChild(ImmutableNodes.leafNode(SOME_REF_QNAME, instanceID))
+                .withChild(ImmutableNodes.leafNode(MYIDENTITY_QNAME, DESC_QNAME))
+                .withChild(Builders.unkeyedListBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(UNKEYED_LIST_QNAME))
+                        .withChild(unkeyedListEntry).build())
+                .withChild(Builders.choiceBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TWO_THREE_QNAME))
+                        .withChild(ImmutableNodes.leafNode(TWO_QNAME, "two")).build())
+                .withChild(Builders.orderedMapBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(ORDERED_LIST_QNAME))
+                        .withValue(ImmutableList.<MapEntryNode>builder().add(
+                                mapEntryBuilder(ORDERED_LIST_QNAME, ORDERED_LIST_ENTRY_QNAME, "1").build(),
+                                mapEntryBuilder(ORDERED_LIST_QNAME, ORDERED_LIST_ENTRY_QNAME, "2").build()).build())
+                        .build())
+                .withChild(shoes)
+                .withChild(numbers)
+                .withChild(switchFeatures)
+                .withChild(mapNodeBuilder(AUGMENTED_LIST_QNAME).withChild(augMapEntry).build())
+                .withChild(mapNodeBuilder(OUTER_LIST_QNAME)
+                                .withChild(mapEntry(OUTER_LIST_QNAME, ID_QNAME, ONE_ID))
+                                .withChild(BAR_NODE).build()
+                );
+    }
+
+    static ContainerNode createTestContainer(final Function<String, Number> uint64) {
+        return createBaseTestContainerBuilder(uint64).build();
+    }
+
+    private static MapEntryNode createAugmentedListEntry(final int id, final String name) {
+
+        Set<QName> childAugmentations = new HashSet<>();
+        childAugmentations.add(AUG_CONT_QNAME);
+
+        ContainerNode augCont =
+                ImmutableContainerNodeBuilder
+                        .create()
+                        .withNodeIdentifier(
+                                new YangInstanceIdentifier.NodeIdentifier(AUG_CONT_QNAME))
+                        .withChild(ImmutableNodes.leafNode(AUG_NAME_QNAME, name)).build();
+
+
+        final YangInstanceIdentifier.AugmentationIdentifier augmentationIdentifier =
+                new YangInstanceIdentifier.AugmentationIdentifier(childAugmentations);
+
+        final AugmentationNode augmentationNode =
+                Builders.augmentationBuilder()
+                        .withNodeIdentifier(augmentationIdentifier).withChild(augCont)
+                        .build();
+
+        return ImmutableMapEntryNodeBuilder
+                .create()
+                .withNodeIdentifier(
+                        YangInstanceIdentifier.NodeIdentifierWithPredicates.of(
+                                AUGMENTED_LIST_QNAME, ID_QNAME, id))
+                .withChild(ImmutableNodes.leafNode(ID_QNAME, id))
+                .withChild(augmentationNode).build();
+    }
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/UintSerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/UintSerializationTest.java
new file mode 100644 (file)
index 0000000..b88bf9c
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.opendaylight.yangtools.yang.common.Uint32;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.opendaylight.yangtools.yang.common.Uint8;
+
+@RunWith(Parameterized.class)
+public class UintSerializationTest extends AbstractSerializationTest {
+    @Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1 },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM });
+    }
+
+    @Test
+    public void testUint8() {
+        assertSame(Uint8.valueOf(0), 96);
+        assertSame(Uint8.valueOf(1), 97);
+    }
+
+    @Test
+    public void testUint16() {
+        assertSame(Uint16.valueOf(0), 96);
+        assertSame(Uint16.valueOf(1), 98);
+    }
+
+    @Test
+    public void testUint32() {
+        assertSame(Uint32.valueOf(0), 96);
+        assertSame(Uint32.valueOf(1), 98);
+        assertEquals(Uint32.valueOf(0xffffffffL), 100);
+    }
+
+    @Test
+    public void testUint64() {
+        assertSame(Uint64.valueOf(0), 96);
+        assertSame(Uint64.valueOf(1), 100);
+        assertEquals(Uint64.fromLongBits(0xffffffffffffffffL), 104);
+    }
+
+}
diff --git a/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/YiidSerializationTest.java b/yang/yang-data-codec-binfmt/src/test/java/org/opendaylight/yangtools/yang/data/codec/binfmt/YiidSerializationTest.java
new file mode 100644 (file)
index 0000000..9c335ab
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.binfmt;
+
+import com.google.common.io.ByteStreams;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceIdentifierBuilder;
+
+@RunWith(Parameterized.class)
+public class YiidSerializationTest extends AbstractSerializationTest {
+    @Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(
+            new Object[] { NormalizedNodeStreamVersion.LITHIUM,
+                100, 116, 596, 611, 612, 628, 4196, 4436, 1_052_772, 1_246_036, 2_298_693
+            },
+            new Object[] { NormalizedNodeStreamVersion.NEON_SR2,
+                102, 108, 288, 489, 294, 502, 1638, 3414,   394_854,   982_870, 1_377_611
+            },
+            new Object[] { NormalizedNodeStreamVersion.SODIUM_SR1,
+                96,   98, 158, 359, 164, 372,  612, 2388,   131_684,   719_700,   916_815
+            },
+            new Object[] { NormalizedNodeStreamVersion.MAGNESIUM,
+                96,   98, 158, 359, 164, 372,  612, 2388,   131_684 ,  719_700,   916_815
+            });
+    }
+
+    @Parameter(1)
+    public int emptySize;
+    @Parameter(2)
+    public int oneSize;
+    @Parameter(3)
+    public int size31;
+    @Parameter(4)
+    public int uniqueSize31;
+    @Parameter(5)
+    public int size32;
+    @Parameter(6)
+    public int uniqueSize32;
+    @Parameter(7)
+    public int size256;
+    @Parameter(8)
+    public int uniqueSize256;
+    @Parameter(9)
+    public int size65792;
+    @Parameter(10)
+    public int uniqueSize65792;
+    @Parameter(11)
+    public int twiceSize65792;
+
+    @Test
+    public void testEmptyIdentifier() {
+        assertSame(YangInstanceIdentifier.empty(), emptySize);
+    }
+
+    @Test
+    public void testOneIdentifier() {
+        assertEquals(YangInstanceIdentifier.of(TestModel.TEST_QNAME), oneSize);
+    }
+
+    @Test
+    public void test31() {
+        assertEquals(fillIdentifier(31), size31);
+        assertEquals(fillUniqueIdentifier(31), uniqueSize31);
+    }
+
+    @Test
+    public void test32() {
+        assertEquals(fillIdentifier(32), size32);
+        assertEquals(fillUniqueIdentifier(32), uniqueSize32);
+    }
+
+    @Test
+    public void test256() {
+        assertEquals(fillIdentifier(256), size256);
+        assertEquals(fillUniqueIdentifier(256), uniqueSize256);
+    }
+
+    @Test
+    public void test65792() {
+        assertEquals(fillIdentifier(65792), size65792);
+        assertEquals(fillUniqueIdentifier(65792), uniqueSize65792);
+    }
+
+    @Test
+    public void testTwice65536() {
+        final YangInstanceIdentifier yiid = fillUniqueIdentifier(65792);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (NormalizedNodeDataOutput nnout = version.newDataOutput(ByteStreams.newDataOutput(baos))) {
+            nnout.writeYangInstanceIdentifier(yiid);
+            nnout.writeYangInstanceIdentifier(yiid);
+        } catch (IOException e) {
+            throw new AssertionError("Failed to serialize", e);
+        }
+
+        final byte[] bytes = baos.toByteArray();
+        Assert.assertEquals(twiceSize65792, bytes.length);
+
+        try {
+            final NormalizedNodeDataInput input = NormalizedNodeDataInput.newDataInput(ByteStreams.newDataInput(bytes));
+            Assert.assertEquals(yiid, input.readYangInstanceIdentifier());
+            Assert.assertEquals(yiid, input.readYangInstanceIdentifier());
+        } catch (IOException e) {
+            throw new AssertionError("Failed to deserialize", e);
+        }
+    }
+
+    private static YangInstanceIdentifier fillIdentifier(final int size) {
+        final InstanceIdentifierBuilder builder = YangInstanceIdentifier.builder();
+        for (int i = 0; i < size; ++i) {
+            builder.node(TestModel.TEST_QNAME);
+        }
+        final YangInstanceIdentifier ret = builder.build();
+        Assert.assertEquals(size, ret.getPathArguments().size());
+        return ret;
+    }
+
+    private static YangInstanceIdentifier fillUniqueIdentifier(final int size) {
+        final InstanceIdentifierBuilder builder = YangInstanceIdentifier.builder();
+        for (int i = 0; i < size; ++i) {
+            builder.node(QName.create(TestModel.TEST_QNAME, "a" + Integer.toHexString(i)));
+        }
+        final YangInstanceIdentifier ret = builder.build();
+        Assert.assertEquals(size, ret.getPathArguments().size());
+        return ret;
+    }
+}