Introduce WritableObject/WritableIdentifier 95/39695/6
authorRobert Varga <rovarga@cisco.com>
Wed, 1 Jun 2016 09:34:54 +0000 (11:34 +0200)
committerTony Tkacik <ttkacik@cisco.com>
Mon, 6 Jun 2016 10:00:54 +0000 (10:00 +0000)
When serializing nested Identifiers, it is sometimes useful
to be able to skip the object header in the stream. This patch
introduces WritableObject interface and WritableIdentifier
as the unification of WritableObject and WritableIdentifier.

It furthermore adds utilities to write long values if a format
which uses minimum number of bytes.

Change-Id: I8459e2883b4ab3dff113000cea43a452eacd3324
Signed-off-by: Robert Varga <rovarga@cisco.com>
common/concepts/pom.xml
common/concepts/src/main/java/org/opendaylight/yangtools/concepts/WritableIdentifier.java [new file with mode: 0644]
common/concepts/src/main/java/org/opendaylight/yangtools/concepts/WritableObject.java [new file with mode: 0644]
common/concepts/src/main/java/org/opendaylight/yangtools/concepts/WritableObjects.java [new file with mode: 0644]
common/concepts/src/test/java/org/opendaylight/yangtools/concepts/WritableObjectsTest.java [new file with mode: 0644]

index 457f8c94939b7b7d94b1cbb051996d83a6e70f29..48ab81ae45a56db77f606d72118d1f88f78d6e74 100644 (file)
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/WritableIdentifier.java b/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/WritableIdentifier.java
new file mode 100644 (file)
index 0000000..0176299
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.concepts;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * A combination of both {@link Identifier} and {@link WritableObject}.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public interface WritableIdentifier extends Identifier, WritableObject {
+
+}
diff --git a/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/WritableObject.java b/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/WritableObject.java
new file mode 100644 (file)
index 0000000..7d5a3d8
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.concepts;
+
+import com.google.common.annotations.Beta;
+import java.io.DataOutput;
+import java.io.IOException;
+import javax.annotation.Nonnull;
+
+/**
+ * Marker interface for an object which can be written out to an {@link DataOutput}. Classes implementing this
+ * interface should declare a corresponding
+ *
+ * <pre>
+ *      public static CLASS readFrom(DataInput in) throws IOException;
+ * </pre>
+ *
+ * The serialization format provided by this abstraction does not guarantee versioning. Callers are responsible for
+ * ensuring the source stream is correctly positioned.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public interface WritableObject {
+    /**
+     * Serialize this object into a {@link DataOutput} as a fixed-format stream.
+     *
+     * @param out Data output
+     * @throws IOException if an I/O error occurs
+     * @throws NullPointerException if out is null
+     */
+    void writeTo(@Nonnull DataOutput out) throws IOException;
+}
diff --git a/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/WritableObjects.java b/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/WritableObjects.java
new file mode 100644 (file)
index 0000000..c81939e
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.concepts;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import javax.annotation.Nonnull;
+
+/**
+ * Utility methods for working with {@link WritableObject}s.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public final class WritableObjects {
+    private WritableObjects() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Shorthand for {@link #writeLong(DataOutput, long, int)} with zero flags.
+     *
+     * @param out Data output
+     * @param value long value to write
+     * @throws IOException if an I/O error occurs
+     */
+    public static void writeLong(final DataOutput out, final long value) throws IOException {
+        writeLong(out, value, 0);
+    }
+
+    /**
+     * Write a long value into a {@link DataOutput}, compressing potential zero bytes. This method is useful for
+     * serializing counters and similar, which have a wide range, but typically do not use it. The value provided is
+     * treated as unsigned.
+     *
+     * This methods writes the number of trailing non-zero in the value. It then writes the minimum required bytes
+     * to reconstruct the value by left-padding zeroes. Inverse operation is performed by {@link #readLong(DataInput)}
+     * or a combination of {@link #readLongHeader(DataInput)} and {@link #readLongBody(DataInput, byte)}.
+     *
+     * Additionally the caller can use the top four bits (i.e. 0xF0) for caller-specific flags. These will be ignored
+     * by {@link #readLong(DataInput)}, but can be extracted via {@link #readLongHeader(DataInput)}.
+     *
+     * @param out Data output
+     * @param value long value to write
+     * @param flags flags to store
+     * @throws IOException if an I/O error occurs
+     */
+    public static void writeLong(final DataOutput out, final long value, final int flags) throws IOException {
+        Preconditions.checkArgument((flags & 0xFFFFFF0F) == 0, "Invalid flags {}", flags);
+        final int bytes = valueBytes(value);
+        out.writeByte(bytes | flags);
+        writeValue(out, value, bytes);
+    }
+
+    /**
+     * Read a long value from a {@link DataInput} which was previously written via {@link #writeLong(DataOutput, long)}.
+     *
+     * @param in Data input
+     * @return long value extracted from the data input
+     * @throws IOException if an I/O error occurs
+     */
+    public static long readLong(final @Nonnull DataInput in) throws IOException {
+        return readLongBody(in, readLongHeader(in));
+    }
+
+    /**
+     * Read the header of a compressed long value. The header may contain user-defined flags, which can be extracted
+     * via {@link #longHeaderFlags(byte)}.
+     *
+     * @param in Data input
+     * @return Header of next value
+     * @throws IOException if an I/O error occurs
+     */
+    public static byte readLongHeader(final @Nonnull DataInput in) throws IOException {
+        return in.readByte();
+    }
+
+    /**
+     * Extract user-defined flags from a compressed long header. This will return 0 if the long value originates from
+     * {@link #writeLong(DataOutput, long)}.
+     *
+     * @param header Value header, as returned by {@link #readLongHeader(DataInput)}
+     * @return User-defined flags
+     */
+    public static int longHeaderFlags(final byte header) {
+        return header & 0xF0;
+    }
+
+    /**
+     * Read a long value from a {@link DataInput} as hinted by the result of {@link #readLongHeader(DataInput)}.
+     *
+     * @param in Data input
+     * @param header Value header, as returned by {@link #readLongHeader(DataInput)}
+     * @throws IOException if an I/O error occurs
+     */
+    public static long readLongBody(final @Nonnull DataInput in, final byte header) throws IOException {
+        int bytes = header & 0xF;
+        if (bytes < 8) {
+            if (bytes > 0) {
+                long value = 0;
+                if (bytes >= 4) {
+                    bytes -= 4;
+                    value = (in.readInt() & 0xFFFFFFFFL) << (bytes * Byte.SIZE);
+                }
+                if (bytes >= 2) {
+                    bytes -= 2;
+                    value |= in.readUnsignedShort() << (bytes * Byte.SIZE);
+                }
+                if (bytes > 0) {
+                    value |= in.readUnsignedByte();
+                }
+                return value;
+            } else {
+                return 0;
+            }
+        } else {
+            return in.readLong();
+        }
+    }
+
+    private static void writeValue(final DataOutput out, final long value, final int bytes) throws IOException {
+        if (bytes < 8) {
+            int left = bytes;
+            if (left >= 4) {
+                left -= 4;
+                out.writeInt((int)(value >>> (left * Byte.SIZE)));
+            }
+            if (left >= 2) {
+                left -= 2;
+                out.writeShort((int)(value >>> (left * Byte.SIZE)));
+            }
+            if (left > 0) {
+                out.writeByte((int)(value & 0xFF));
+            }
+        } else {
+            out.writeLong(value);
+        }
+    }
+
+    private static int valueBytes(final long value) {
+        // This is a binary search for the first match. Note that we need to mask bits from the most significant one
+        if ((value & 0xFFFFFFFF00000000L) != 0) {
+            if ((value & 0xFFFF000000000000L) != 0) {
+                return (value & 0xFF00000000000000L) != 0 ? 8 : 7;
+            } else {
+                return (value & 0x0000FF0000000000L) != 0 ? 6 : 5;
+            }
+        } else if ((value & 0x00000000FFFFFFFFL) != 0) {
+            if ((value & 0x00000000FFFF0000L) != 0) {
+                return (value & 0x00000000FF000000L) != 0 ? 4 : 3;
+            } else {
+                return (value & 0x000000000000FF00L) != 0 ? 2 : 1;
+            }
+        } else {
+            return 0;
+        }
+    }
+}
diff --git a/common/concepts/src/test/java/org/opendaylight/yangtools/concepts/WritableObjectsTest.java b/common/concepts/src/test/java/org/opendaylight/yangtools/concepts/WritableObjectsTest.java
new file mode 100644 (file)
index 0000000..c4794ea
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.concepts;
+
+import static org.junit.Assert.assertEquals;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import org.junit.Test;
+
+public class WritableObjectsTest {
+
+    private static void assertRecovery(final long expected) throws IOException {
+        final ByteArrayDataOutput out = ByteStreams.newDataOutput();
+        WritableObjects.writeLong(out, expected);
+        final long actual = WritableObjects.readLong(ByteStreams.newDataInput(out.toByteArray()));
+        assertEquals(Long.toUnsignedString(expected, 16), Long.toUnsignedString(actual, 16));
+    }
+
+    @Test
+    public void testReadWriteLong() throws IOException {
+        assertRecovery(0L);
+        assertRecovery(1L);
+        assertRecovery(255L);
+        assertRecovery(256L);
+
+        assertRecovery(Long.MAX_VALUE);
+        assertRecovery(Long.MIN_VALUE);
+
+        assertRecovery(0xF000000000000000L);
+        assertRecovery(0x0F00000000000000L);
+        assertRecovery(0x00F0000000000000L);
+        assertRecovery(0x000F000000000000L);
+        assertRecovery(0x0000F00000000000L);
+        assertRecovery(0x00000F0000000000L);
+        assertRecovery(0x000000F000000000L);
+        assertRecovery(0x0000000F00000000L);
+        assertRecovery(0x00000000F0000000L);
+        assertRecovery(0x000000000F000000L);
+        assertRecovery(0x0000000000F00000L);
+        assertRecovery(0x00000000000F0000L);
+        assertRecovery(0x000000000000F000L);
+        assertRecovery(0x0000000000000F00L);
+        assertRecovery(0x00000000000000F0L);
+
+        assertRecovery(0xF0F0F0F0F0F0F0F0L);
+        assertRecovery(0x0FF0F0F0F0F0F0F0L);
+        assertRecovery(0x00F0F0F0F0F0F0F0L);
+        assertRecovery(0x000FF0F0F0F0F0F0L);
+        assertRecovery(0x0000F0F0F0F0F0F0L);
+        assertRecovery(0x00000F00F0F0F0F0L);
+        assertRecovery(0x000000F0F0F0F0F0L);
+        assertRecovery(0x0000000FF0F0F0F0L);
+        assertRecovery(0x00000000F0F0F0F0L);
+        assertRecovery(0x000000000FF0F0F0L);
+        assertRecovery(0x0000000000F0F0F0L);
+        assertRecovery(0x00000000000FF0F0L);
+        assertRecovery(0x000000000000F0F0L);
+        assertRecovery(0x0000000000000FF0L);
+        assertRecovery(0x00000000000000F0L);
+
+        assertRecovery(0x8000000000000000L);
+        assertRecovery(0x0800000000000000L);
+        assertRecovery(0x0080000000000000L);
+        assertRecovery(0x0008000000000000L);
+        assertRecovery(0x0000800000000000L);
+        assertRecovery(0x0000080000000000L);
+        assertRecovery(0x0000008000000000L);
+        assertRecovery(0x0000000800000000L);
+        assertRecovery(0x0000000080000000L);
+        assertRecovery(0x0000000008000000L);
+        assertRecovery(0x0000000000800000L);
+        assertRecovery(0x0000000000080000L);
+        assertRecovery(0x0000000000008000L);
+        assertRecovery(0x0000000000000800L);
+        assertRecovery(0x0000000000000080L);
+        assertRecovery(0x0000000000000008L);
+    }
+}