// Copyright 2017 JanusGraph Authors
//
// 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 org.janusgraph.graphdb.database.idhandling;
import com.google.common.base.Preconditions;
import org.janusgraph.diskstorage.ReadBuffer;
import org.janusgraph.diskstorage.ScanBuffer;
import org.janusgraph.diskstorage.StaticBuffer;
import org.janusgraph.diskstorage.WriteBuffer;
import org.janusgraph.diskstorage.util.WriteByteBuffer;
/**
* @author Matthias Broecheler (me@matthiasb.com)
*/
public class VariableLong {
public static int unsignedByte(byte b) {
return b < 0 ? b + 256 : b;
}
public static byte unsignedByte(int value) {
Preconditions.checkArgument(value>=0 && value<256,"Value overflow: %s",value);
return (byte)(value & 0xFF);
}
//Move stop bit back to front => rewrite prefix variable encoding by custom writing first byte
private static final byte BIT_MASK = 127;
private static final byte STOP_MASK = -128;
private static long readUnsigned(ScanBuffer in) {
long value = 0;
byte b;
do {
b = in.getByte();
value = value << 7 | (b & BIT_MASK);
} while (b >= 0);
return value;
}
private static void writeUnsigned(WriteBuffer out, final long value) {
writeUnsigned(out, unsignedBlockBitLength(value), value);
}
private static void writeUnsigned(WriteBuffer out, int offset, final long value) {
assert offset % 7 == 0;
while (offset > 0) {
offset -= 7;
byte b = (byte) ((value >>> offset) & BIT_MASK);
if (offset == 0) {
b = (byte) (b | STOP_MASK);
}
out.putByte(b);
}
}
private static int unsignedBlockBitLength(final long value) {
return unsignedNumBlocks(value)*7;
}
private static int unsignedNumBlocks(final long value) {
return numVariableBlocks(unsignedBitLength(value));
}
private static int numVariableBlocks(final int numBits) {
assert numBits > 0;
return (numBits - 1) / 7 + 1;
}
public static int unsignedBitLength(final long value) {
return (value == 0) ? 1 : Long.SIZE - Long.numberOfLeadingZeros(value);
}
/* ##################################
Read and write positive longs
################################## */
public static long readPositive(ScanBuffer in) {
long value = readUnsigned(in);
assert value >= 0;
return value;
}
public static void writePositive(WriteBuffer out, final long value) {
assert value >= 0;
writeUnsigned(out, value);
}
public static StaticBuffer positiveBuffer(final long value) {
WriteBuffer buffer = new WriteByteBuffer(positiveLength(value));
writePositive(buffer, value);
return buffer.getStaticBuffer();
}
public static StaticBuffer positiveBuffer(long[] value) {
int len = 0;
for (long aValue : value)
len += positiveLength(aValue);
WriteBuffer buffer = new WriteByteBuffer(len);
for (long aValue : value)
writePositive(buffer, aValue);
return buffer.getStaticBuffer();
}
public static int positiveLength(long value) {
assert value >= 0;
return unsignedNumBlocks(value);
}
/* ##################################
Read and write arbitrary longs
################################## */
private static long convert2Unsigned(final long value) {
assert value >= 0 || value > Long.MIN_VALUE;
return Math.abs(value) << 1 | (value < 0 ? 1 : 0);
}
private static long convertFromUnsigned(final long value) {
return ((value & 1) == 1) ? -(value >>> 1) : value >>> 1;
}
public static int length(long value) {
return unsignedNumBlocks(convert2Unsigned(value));
}
public static void write(WriteBuffer out, final long value) {
writeUnsigned(out, convert2Unsigned(value));
}
public static long read(ScanBuffer in) {
return convertFromUnsigned(readUnsigned(in));
}
/* ##################################
Read and write positive longs with a specified binary prefix of fixed length
################################## */
public static void writePositiveWithPrefix(final WriteBuffer out, long value, long prefix, final int prefixBitLen) {
assert value >= 0;
assert prefixBitLen > 0 && prefixBitLen < 6 && (prefix < (1L << prefixBitLen));
//Write first byte
int deltaLen = 8 - prefixBitLen;
byte first = (byte)(prefix<<deltaLen);
int valueLen = unsignedBitLength(value);
int mod = valueLen%7;
if (mod<=(deltaLen-1)) {
int offset = (valueLen-mod);
first = (byte)(first | (value >>> offset));
value = value & ((1l<<offset)-1);
valueLen -= mod;
} else {
valueLen += (7-mod);
}
assert valueLen>=0;
if (valueLen>0) {
//Add continue mask to indicate reading further
first = (byte) ( first | (1<<(deltaLen-1)));
}
out.putByte(first);
if (valueLen>0) {
//Keep writing
writeUnsigned(out,valueLen,value);
}
}
public static int positiveWithPrefixLength(final long value, final int prefixBitLen) {
assert value >= 0;
assert prefixBitLen > 0 && prefixBitLen < 6;
return numVariableBlocks(unsignedBitLength(value)+prefixBitLen);
}
public static long[] readPositiveWithPrefix(final ReadBuffer in, final int prefixBitLen) {
assert prefixBitLen > 0 && prefixBitLen < 6;
int first = unsignedByte(in.getByte());
int deltaLen = 8 - prefixBitLen;
long prefix = first>>deltaLen;
long value = first & ((1<<(deltaLen-1))-1);
if ( ((first>>>(deltaLen-1)) & 1) == 1) { //Continue mask
int deltaPos = in.getPosition();
long remainder = readUnsigned(in);
deltaPos = in.getPosition()-deltaPos;
assert deltaPos > 0;
value = (value<<(deltaPos*7)) + remainder;
}
return new long[]{value, prefix};
}
/* ##################################
Write positive longs so that they can be read backwards
Use positiveLength() for length
################################## */
public static void writePositiveBackward(WriteBuffer out, long value) {
assert value >= 0;
writeUnsignedBackward(out,value);
}
public static int positiveBackwardLength(long value) {
assert value >= 0;
return unsignedBackwardLength(value);
}
public static long readPositiveBackward(ReadBuffer in) {
return readUnsignedBackward(in);
}
/* ##################################
Write arbitrary longs so that they can be read backwards
Use length() for length
################################## */
public static void writeBackward(WriteBuffer out, final long value) {
writeUnsignedBackward(out, convert2Unsigned(value));
}
public static int backwardLength(final long value) {
return unsignedBackwardLength(convert2Unsigned(value));
}
public static long readBackward(ReadBuffer in) {
return convertFromUnsigned(readUnsignedBackward(in));
}
/**
* The format used is this:
* - The first bit indicates whether this is the first block (reading backwards, this would be the stopping criterion)
* - In the first byte, the 3 bits after the first bit indicate the number of bytes written minus 3 (since 3 is
* the minimum number of bytes written. So, if the 3 bits are 010 = 2 => 5 bytes written. The value is aligned to
* the left to ensure that this encoding is byte order preserving.
*
* @param out
* @param value
*/
private static void writeUnsignedBackward(WriteBuffer out, final long value) {
int numBytes= unsignedBackwardLength(value);
int prefixLen = numBytes-3;
assert prefixLen>=0 && prefixLen<8; //Consumes 3 bits
//Prepare first byte
byte b = (byte)((prefixLen<<4) | 0x80); //stop marker (first bit) and length
for (int i=numBytes-1; i>=0; i--) {
b = (byte)(b | (0x7F & (value>>>(i*7))));
out.putByte(b);
b=0;
}
}
private static int unsignedBackwardLength(final long value) {
int bitlength = unsignedBitLength(value);
assert bitlength>0 && bitlength<=64;
int numBytes= Math.max(3,1+(bitlength<=4?0:(1+(bitlength-5)/7)));
return numBytes;
}
private static long readUnsignedBackward(ReadBuffer in) {
int position = in.getPosition();
int numBytes = 0;
long value = 0;
long b;
while (true) {
position--;
b = in.getByte(position);
if (b<0) { //First byte
value = value | ((b & 0x0F)<<(7*numBytes));
assert ((b>>>4)&7)+3 == numBytes+1 : b + " vs " + numBytes; //verify correct length
break;
}
value = value | (b<<(7*numBytes));
numBytes++;
}
in.movePositionTo(position);
return value;
}
}