blob: 70f26daf101484ac4a21c949f47ce460c5cb5da4 [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Common methods to encode and decode varints and varlongs into ByteBuffers and
* arrays.
*/
public class VarInt {
/**
* Maximum encoded size of 32-bit positive integers (in bytes)
*/
public static final int MAX_VARINT_SIZE = 5;
/**
* maximum encoded size of 64-bit longs, and negative 32-bit ints (in bytes)
*/
public static final int MAX_VARLONG_SIZE = 10;
private VarInt() { }
/** Returns the encoding size in bytes of its input value.
* @param i the integer to be measured
* @return the encoding size in bytes of its input value
*/
public static int varIntSize(int i) {
int result = 0;
do {
result++;
i >>>= 7;
} while (i != 0);
return result;
}
/**
* Reads a varint from src, places its values into the first element of
* dst and returns the offset in to src of the first byte after the varint.
*
* @param src source buffer to retrieve from
* @param offset offset within src
* @param dst the resulting int value
* @return the updated offset after reading the varint
*/
public static int getVarInt(byte[] src, int offset, int[] dst) {
int result = 0;
int shift = 0;
int b;
do {
if (shift >= 32) {
// Out of range
throw new IndexOutOfBoundsException("varint too long");
}
// Get 7 bits from next byte
b = src[offset++];
result |= (b & 0x7F) << shift;
shift += 7;
} while ((b & 0x80) != 0);
dst[0] = result;
return offset;
}
/**
* Encodes an integer in a variable-length encoding, 7 bits per byte, into a
* destination byte[], following the protocol buffer convention.
*
* @param v the int value to write to sink
* @param sink the sink buffer to write to
* @param offset the offset within sink to begin writing
* @return the updated offset after writing the varint
*/
public static int putVarInt(int v, byte[] sink, int offset) {
do {
// Encode next 7 bits + terminator bit
int bits = v & 0x7F;
v >>>= 7;
byte b = (byte) (bits + ((v != 0) ? 0x80 : 0));
sink[offset++] = b;
} while (v != 0);
return offset;
}
/**
* Reads a varint from the current position of the given ByteBuffer and
* returns the decoded value as 32 bit integer.
*
* <p>The position of the buffer is advanced to the first byte after the
* decoded varint.
*
* @param src the ByteBuffer to get the var int from
* @return The integer value of the decoded varint
*/
public static int getVarInt(ByteBuffer src) {
int tmp;
if ((tmp = src.get()) >= 0) {
return tmp;
}
int result = tmp & 0x7f;
if ((tmp = src.get()) >= 0) {
result |= tmp << 7;
} else {
result |= (tmp & 0x7f) << 7;
if ((tmp = src.get()) >= 0) {
result |= tmp << 14;
} else {
result |= (tmp & 0x7f) << 14;
if ((tmp = src.get()) >= 0) {
result |= tmp << 21;
} else {
result |= (tmp & 0x7f) << 21;
result |= (tmp = src.get()) << 28;
while (tmp < 0) {
// We get into this loop only in the case of overflow.
// By doing this, we can call getVarInt() instead of
// getVarLong() when we only need an int.
tmp = src.get();
}
}
}
}
return result;
}
/**
* Encodes an integer in a variable-length encoding, 7 bits per byte, to a
* ByteBuffer sink.
* @param v the value to encode
* @param sink the ByteBuffer to add the encoded value
*/
public static void putVarInt(int v, ByteBuffer sink) {
while (true) {
int bits = v & 0x7f;
v >>>= 7;
if (v == 0) {
sink.put((byte) bits);
return;
}
sink.put((byte) (bits | 0x80));
}
}
/**
* Reads a varint from the given InputStream and returns the decoded value
* as an int.
*
* @param inputStream the InputStream to read from
*/
public static int getVarInt(InputStream inputStream) throws IOException {
int result = 0;
int shift = 0;
int b;
do {
if (shift >= 32) {
// Out of range
throw new IndexOutOfBoundsException("varint too long");
}
// Get 7 bits from next byte
b = inputStream.read();
result |= (b & 0x7F) << shift;
shift += 7;
} while ((b & 0x80) != 0);
return result;
}
/**
* Encodes an integer in a variable-length encoding, 7 bits per byte, and
* writes it to the given OutputStream.
*
* @param v the value to encode
* @param outputStream the OutputStream to write to
*/
public static void putVarInt(int v, OutputStream outputStream) throws IOException {
byte[] bytes = new byte[varIntSize(v)];
putVarInt(v, bytes, 0);
outputStream.write(bytes);
}
/**
* Returns the encoding size in bytes of its input value.
*
* @param v the long to be measured
* @return the encoding size in bytes of a given long value.
*/
public static int varLongSize(long v) {
int result = 0;
do {
result++;
v >>>= 7;
} while (v != 0);
return result;
}
/**
* Reads an up to 64 bit long varint from the current position of the
* given ByteBuffer and returns the decoded value as long.
*
* <p>The position of the buffer is advanced to the first byte after the
* decoded varint.
*
* @param src the ByteBuffer to get the var int from
* @return The integer value of the decoded long varint
*/
public static long getVarLong(ByteBuffer src) {
long tmp;
if ((tmp = src.get()) >= 0) {
return tmp;
}
long result = tmp & 0x7f;
if ((tmp = src.get()) >= 0) {
result |= tmp << 7;
} else {
result |= (tmp & 0x7f) << 7;
if ((tmp = src.get()) >= 0) {
result |= tmp << 14;
} else {
result |= (tmp & 0x7f) << 14;
if ((tmp = src.get()) >= 0) {
result |= tmp << 21;
} else {
result |= (tmp & 0x7f) << 21;
if ((tmp = src.get()) >= 0) {
result |= tmp << 28;
} else {
result |= (tmp & 0x7f) << 28;
if ((tmp = src.get()) >= 0) {
result |= tmp << 35;
} else {
result |= (tmp & 0x7f) << 35;
if ((tmp = src.get()) >= 0) {
result |= tmp << 42;
} else {
result |= (tmp & 0x7f) << 42;
if ((tmp = src.get()) >= 0) {
result |= tmp << 49;
} else {
result |= (tmp & 0x7f) << 49;
if ((tmp = src.get()) >= 0) {
result |= tmp << 56;
} else {
result |= (tmp & 0x7f) << 56;
result |= ((long) src.get()) << 63;
}
}
}
}
}
}
}
}
return result;
}
/**
* Encodes a long integer in a variable-length encoding, 7 bits per byte, to a
* ByteBuffer sink.
* @param v the value to encode
* @param sink the ByteBuffer to add the encoded value
*/
public static void putVarLong(long v, ByteBuffer sink) {
while (true) {
int bits = ((int) v) & 0x7f;
v >>>= 7;
if (v == 0) {
sink.put((byte) bits);
return;
}
sink.put((byte) (bits | 0x80));
}
}
}