// 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.diskstorage.util;
import com.google.common.base.Preconditions;
import org.janusgraph.diskstorage.ReadBuffer;
import org.janusgraph.diskstorage.StaticBuffer;
import java.nio.ByteBuffer;
/**
* Implementation of {@link StaticBuffer} against byte array.
* <p/>
* The byte to primitive conversion code was copied from / is inspired by Kryo's Input class:
* {@linktourl https://code.google.com/p/kryo/source/browse/trunk/src/com/esotericsoftware/kryo/io/Input.java}
*
* @author Matthias Broecheler (me@matthiasb.com)
*/
public class StaticArrayBuffer implements StaticBuffer {
private final byte[] array;
private int offset;
private int limit;
public StaticArrayBuffer(byte[] array, int offset, int limit) {
assert array != null;
assert offset >= 0 && offset <= limit; // offset == limit implies a zero-length array
assert limit <= array.length;
this.array = array;
this.offset = offset;
this.limit = limit;
}
public StaticArrayBuffer(byte[] array) {
this(array, 0, array.length);
}
public StaticArrayBuffer(byte[] array, int limit) {
this(array, 0, limit);
}
public StaticArrayBuffer(StaticBuffer buffer) {
this((StaticArrayBuffer)buffer);
}
public StaticArrayBuffer(StaticArrayBuffer buffer) {
this(buffer.array, buffer.offset, buffer.limit);
}
public static StaticArrayBuffer of(byte[] array) {
return new StaticArrayBuffer(array);
}
public static StaticArrayBuffer of(ByteBuffer b) {
if (b.hasArray()) {
return new StaticArrayBuffer(b.array(),b.arrayOffset()+b.position(),b.arrayOffset()+b.limit());
} else {
byte[] array = new byte[b.remaining()];
b.mark();
b.get(array);
b.reset();
return StaticArrayBuffer.of(array);
}
}
//-------------------
void reset(int newOffset, int newLimit) {
assert newOffset >= 0 && newOffset <= newLimit;
assert newLimit <= array.length;
this.offset=newOffset;
this.limit=newLimit;
}
private int require(int position, int size) {
int base = position + offset;
if (position<0)
throw new ArrayIndexOutOfBoundsException("Position [" + position + "] must be nonnegative");
if (base+size>limit)
throw new ArrayIndexOutOfBoundsException("Required size [" + size + "] " +
"exceeds actual remaining size [" + (limit-base) + "]");
assert base + size <= limit;
return base;
}
@Override
public int length() {
return limit - offset;
}
/*
############## BULK READING ################
*/
void copyTo(byte[] dest, int destOffset) {
System.arraycopy(array,offset,dest,destOffset,length());
}
@Override
public StaticBuffer subrange(int position, int length) {
return subrange(position, length, false);
}
@Override
public StaticBuffer subrange(int position, int length, boolean invert) {
if (position<0 || length<0 || (offset + position + length)>limit)
throw new ArrayIndexOutOfBoundsException("Position ["+position+"] and or length ["+length+"] out of bounds");
if (!invert) {
return new StaticArrayBuffer(array, offset + position, offset + position + length);
} else {
byte[] inverted = new byte[length];
System.arraycopy(array,offset+position,inverted,0,length);
for (int i = 0; i < inverted.length; i++) {
inverted[i]=(byte)~inverted[i];
}
return new StaticArrayBuffer(inverted);
}
}
@Override
public ReadBuffer asReadBuffer() {
return new ReadArrayBuffer(this);
}
@Override
public ByteBuffer asByteBuffer() {
return as(StaticBuffer.BB_FACTORY);
}
@Override
public <T> T as(Factory<T> factory) {
return factory.get(array, offset, limit);
}
protected <T> T as(Factory<T> factory, int position, int length) {
if (position<0 || length<0 || (offset + position + length)>limit)
throw new ArrayIndexOutOfBoundsException("Position ["+position+"] and or length ["+length+"] out of bounds");
return factory.get(array,offset+position,offset+position+length);
}
/*
############## READING PRIMITIVES ################
*/
public static final int BYTE_LEN = 1;
public static final int SHORT_LEN = 2;
public static final int INT_LEN = 4;
public static final int LONG_LEN = 8;
public static final int CHAR_LEN = 2;
public static final int FLOAT_LEN = 4;
public static final int DOUBLE_LEN = 8;
@Override
public byte getByte(int position) {
return array[require(position, BYTE_LEN)];
}
@Override
public boolean getBoolean(int position) {
return getByte(position)>0?true:false;
}
@Override
public short getShort(int position) {
int base = require(position, SHORT_LEN);
return (short) (((array[base++] & 0xFF) << 8) | (array[base++] & 0xFF));
}
@Override
public int getInt(int position) {
int base = require(position, INT_LEN);
return getInt(array, base);
}
public static int getInt(byte[] array, int offset) {
return (array[offset++] & 0xFF) << 24
| (array[offset++] & 0xFF) << 16
| (array[offset++] & 0xFF) << 8
| array[offset] & 0xFF;
}
public static void putInt(byte[] array, int offset, final int value) {
array[offset++]= (byte)((value >> 24) & 0xFF);
array[offset++]= (byte)((value >> 16) & 0xFF);
array[offset++]= (byte)((value >> 8) & 0xFF);
array[offset]= (byte)(value & 0xFF);
}
@Override
public long getLong(int position) {
int offset = require(position, LONG_LEN);
return getLong(array,offset);
}
public static long getLong(byte[] array, int offset) {
return (long) array[offset++] << 56 //
| (long) (array[offset++] & 0xFF) << 48 //
| (long) (array[offset++] & 0xFF) << 40 //
| (long) (array[offset++] & 0xFF) << 32 //
| (long) (array[offset++] & 0xFF) << 24 //
| (array[offset++] & 0xFF) << 16 //
| (array[offset++] & 0xFF) << 8 //
| array[offset++] & 0xFF;
}
public static void putLong(byte[] array, int offset, final long value) {
array[offset++]= (byte)(value >> 56);
array[offset++]= (byte)((value >> 48) & 0xFF);
array[offset++]= (byte)((value >> 40) & 0xFF);
array[offset++]= (byte)((value >> 32) & 0xFF);
array[offset++]= (byte)((value >> 24) & 0xFF);
array[offset++]= (byte)((value >> 16) & 0xFF);
array[offset++]= (byte)((value >> 8) & 0xFF);
array[offset++]= (byte)(value & 0xFF);
}
@Override
public char getChar(int position) {
return (char) getShort(position);
}
@Override
public float getFloat(int position) {
return Float.intBitsToFloat(getInt(position));
}
@Override
public double getDouble(int position) {
return Double.longBitsToDouble(getLong(position));
}
//-------- ARRAY METHODS
@Override
public byte[] getBytes(int position, int length) {
byte[] result = new byte[length];
for (int i = 0; i < length; i++) {
result[i]=getByte(position);
position += BYTE_LEN;
}
return result;
}
public short[] getShorts(int position, int length) {
short[] result = new short[length];
for (int i = 0; i < length; i++) {
result[i]=getShort(position);
position += SHORT_LEN;
}
return result;
}
public int[] getInts(int position, int length) {
int[] result = new int[length];
for (int i = 0; i < length; i++) {
result[i]=getInt(position);
position += INT_LEN;
}
return result;
}
public long[] getLongs(int position, int length) {
long[] result = new long[length];
for (int i = 0; i < length; i++) {
result[i]=getLong(position);
position += LONG_LEN;
}
return result;
}
public char[] getChars(int position, int length) {
char[] result = new char[length];
for (int i = 0; i < length; i++) {
result[i]=getChar(position);
position += CHAR_LEN;
}
return result;
}
public float[] getFloats(int position, int length) {
float[] result = new float[length];
for (int i = 0; i < length; i++) {
result[i]=getFloat(position);
position += FLOAT_LEN;
}
return result;
}
public double[] getDoubles(int position, int length) {
double[] result = new double[length];
for (int i = 0; i < length; i++) {
result[i]=getDouble(position);
position += DOUBLE_LEN;
}
return result;
}
/*
############## EQUALS, HASHCODE & COMPARE ################
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!(o instanceof StaticBuffer)) return false;
StaticBuffer b = (StaticBuffer)o;
if (length()!=b.length()) return false;
return compareTo(b)==0;
}
/**
* Thread-safe hashcode method for StaticBuffer written according to
* Effective Java 2e by Josh Bloch.
*
* @return hashcode for given StaticBuffer
*/
@Override
public int hashCode() {
return hashCode(length());
}
protected int hashCode(int length) {
Preconditions.checkArgument(length<=length());
int result = 17;
for (int i = offset; i < offset+length; i++) {
result = 31 * result + (int)array[i];
}
return result;
}
@Override
public String toString() {
return toString("-");
}
public final String toString(String separator) {
StringBuilder s = new StringBuilder();
for (int i=offset;i<limit;i++) {
if (i>offset) s.append(separator);
s.append(toFixedWidthString(array[i]));
}
return s.toString();
}
private static String toString(byte b) {
return String.valueOf((b>=0)?b:256+b);
}
private static String toFixedWidthString(byte b) {
String s = toString(b);
assert s.length()<=3 && s.length()>0;
if (s.length()==1) s = " "+s;
else if (s.length()==2) s = " " + s;
return s;
}
@Override
public int compareTo(StaticBuffer other) {
assert other instanceof StaticArrayBuffer;
return compareTo((StaticArrayBuffer) other);
}
public int compareTo(StaticArrayBuffer other) {
return compareTo(array, offset, limit, other.array, other.offset, other.limit);
}
protected int compareTo(int length, StaticBuffer buffer, int bufferLen) {
assert buffer instanceof StaticArrayBuffer;
return compareTo(length, (StaticArrayBuffer)buffer, bufferLen);
}
protected int compareTo(int length, StaticArrayBuffer buffer, int bufferLen) {
assert buffer!=null;
Preconditions.checkArgument(length<=length() && bufferLen<=buffer.length());
return compareTo(array, offset, offset+length, buffer.array, buffer.offset, buffer.offset+bufferLen);
}
private static int compareTo(byte[] buffer1, int offset1, int end1,
byte[] buffer2, int offset2, int end2) {
// Short circuit equal case
int length1 = end1 - offset1;
int length2 = end2 - offset2;
if (buffer1 == buffer2 &&
offset1 == offset2 &&
length1 == length2) {
return 0;
}
for (int i = offset1, j = offset2; i < end1 && j < end2; i++, j++) {
int a = (buffer1[i] & 0xff);
int b = (buffer2[j] & 0xff);
if (a != b) {
return a - b;
}
}
return length1 - length2;
}
}