Introduce WritableObject/WritableIdentifier
[yangtools.git] / common / concepts / src / main / java / org / opendaylight / yangtools / concepts / WritableObjects.java
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;
+        }
+    }
+}