<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
</dependencies>
<build>
--- /dev/null
+/*
+ * 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 {
+
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}