package org.mapdb.volume;
import org.mapdb.CC;
import org.mapdb.DBException;
import org.mapdb.DataInput2;
import sun.misc.Cleaner;
import sun.nio.ch.DirectBuffer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.util.concurrent.locks.ReentrantLock;
/**
* Abstract Volume over bunch of ByteBuffers
* It leaves ByteBufferVol details (allocation, disposal) on subclasses.
* Most methods are final for better performance (JIT compiler can inline those).
*/
abstract public class ByteBufferVol extends Volume {
protected final boolean cleanerHackEnabled;
protected final ReentrantLock growLock = new ReentrantLock();
protected final int sliceShift;
protected final int sliceSizeModMask;
protected final int sliceSize;
protected volatile ByteBuffer[] slices = new ByteBuffer[0];
protected final boolean readOnly;
protected ByteBufferVol(boolean readOnly, int sliceShift, boolean cleanerHackEnabled) {
this.readOnly = readOnly;
this.sliceShift = sliceShift;
this.cleanerHackEnabled = cleanerHackEnabled;
this.sliceSize = 1<< sliceShift;
this.sliceSizeModMask = sliceSize -1;
}
protected final ByteBuffer getSlice(long offset){
ByteBuffer[] slices = this.slices;
int pos = (int)(offset >>> sliceShift);
if(pos>=slices.length)
throw new DBException.VolumeEOF("Get/Set beyond file size. Requested offset: "+offset+", volume size: "+length());
return slices[pos];
}
@Override public final void putLong(final long offset, final long value) {
if(CC.VOLUME_PRINT_STACK_AT_OFFSET!=0 && CC.VOLUME_PRINT_STACK_AT_OFFSET>=offset && CC.VOLUME_PRINT_STACK_AT_OFFSET <= offset+8){
new IOException("VOL STACK:").printStackTrace();
}
getSlice(offset).putLong((int) (offset & sliceSizeModMask), value);
}
@Override public final void putInt(final long offset, final int value) {
if(CC.VOLUME_PRINT_STACK_AT_OFFSET!=0 && CC.VOLUME_PRINT_STACK_AT_OFFSET>=offset && CC.VOLUME_PRINT_STACK_AT_OFFSET <= offset+4){
new IOException("VOL STACK:").printStackTrace();
}
getSlice(offset).putInt((int) (offset & sliceSizeModMask), value);
}
@Override public final void putByte(final long offset, final byte value) {
if(CC.VOLUME_PRINT_STACK_AT_OFFSET!=0 && CC.VOLUME_PRINT_STACK_AT_OFFSET>=offset && CC.VOLUME_PRINT_STACK_AT_OFFSET <= offset+1){
new IOException("VOL STACK:").printStackTrace();
}
getSlice(offset).put((int) (offset & sliceSizeModMask), value);
}
@Override public void putData(final long offset, final byte[] src, int srcPos, int srcSize){
if(CC.VOLUME_PRINT_STACK_AT_OFFSET!=0 && CC.VOLUME_PRINT_STACK_AT_OFFSET>=offset && CC.VOLUME_PRINT_STACK_AT_OFFSET <= offset+srcSize){
new IOException("VOL STACK:").printStackTrace();
}
final ByteBuffer b1 = getSlice(offset).duplicate();
final int bufPos = (int) (offset& sliceSizeModMask);
b1.position(bufPos);
b1.put(src, srcPos, srcSize);
}
@Override public final void putData(final long offset, final ByteBuffer buf) {
if(CC.VOLUME_PRINT_STACK_AT_OFFSET!=0 && CC.VOLUME_PRINT_STACK_AT_OFFSET>=offset && CC.VOLUME_PRINT_STACK_AT_OFFSET <= offset+buf.remaining()){
new IOException("VOL STACK:").printStackTrace();
}
final ByteBuffer b1 = getSlice(offset).duplicate();
final int bufPos = (int) (offset& sliceSizeModMask);
//no overlap, so just write the value
b1.position(bufPos);
b1.put(buf);
}
@Override
public void copyTo(long inputOffset, Volume target, long targetOffset, long size) {
final ByteBuffer b1 =getSlice(inputOffset).duplicate();
final int bufPos = (int) (inputOffset& sliceSizeModMask);
b1.position(bufPos);
//TODO size>Integer.MAX_VALUE
b1.limit((int) (bufPos+size));
target.putData(targetOffset, b1);
}
@Override public void getData(final long offset, final byte[] src, int srcPos, int srcSize){
final ByteBuffer b1 = getSlice(offset).duplicate();
final int bufPos = (int) (offset& sliceSizeModMask);
b1.position(bufPos);
b1.get(src, srcPos, srcSize);
}
@Override final public long getLong(long offset) {
return getSlice(offset).getLong((int) (offset & sliceSizeModMask));
}
@Override final public int getInt(long offset) {
return getSlice(offset).getInt((int) (offset & sliceSizeModMask));
}
@Override public final byte getByte(long offset) {
return getSlice(offset).get((int) (offset & sliceSizeModMask));
}
@Override
public final DataInput2.ByteBuffer getDataInput(long offset, int size) {
return new DataInput2.ByteBuffer(getSlice(offset), (int) (offset& sliceSizeModMask));
}
@Override
public void putDataOverlap(long offset, byte[] data, int pos, int len) {
boolean overlap = (offset>>>sliceShift != (offset+len)>>>sliceShift);
if(overlap){
while(len>0){
ByteBuffer b = getSlice(offset).duplicate();
b.position((int) (offset&sliceSizeModMask));
int toPut = Math.min(len,sliceSize - b.position());
b.limit(b.position()+toPut);
b.put(data, pos, toPut);
pos+=toPut;
len-=toPut;
offset+=toPut;
}
}else{
putData(offset,data,pos,len);
}
}
@Override
public DataInput2 getDataInputOverlap(long offset, int size) {
boolean overlap = (offset>>>sliceShift != (offset+size)>>>sliceShift);
if(overlap){
byte[] bb = new byte[size];
final int origLen = size;
while(size>0){
ByteBuffer b = getSlice(offset).duplicate();
b.position((int) (offset&sliceSizeModMask));
int toPut = Math.min(size,sliceSize - b.position());
b.limit(b.position()+toPut);
b.get(bb,origLen-size,toPut);
size -=toPut;
offset+=toPut;
}
return new DataInput2.ByteArray(bb);
}else{
//return mapped buffer
return getDataInput(offset,size);
}
}
@Override
public void putUnsignedShort(long offset, int value) {
final ByteBuffer b = getSlice(offset);
int bpos = (int) (offset & sliceSizeModMask);
b.put(bpos++, (byte) (value >> 8));
b.put(bpos, (byte) (value));
}
@Override
public int getUnsignedShort(long offset) {
final ByteBuffer b = getSlice(offset);
int bpos = (int) (offset & sliceSizeModMask);
return (( (b.get(bpos++) & 0xff) << 8) |
( (b.get(bpos) & 0xff)));
}
@Override
public int getUnsignedByte(long offset) {
final ByteBuffer b = getSlice(offset);
int bpos = (int) (offset & sliceSizeModMask);
return b.get(bpos) & 0xff;
}
@Override
public void putUnsignedByte(long offset, int byt) {
final ByteBuffer b = getSlice(offset);
int bpos = (int) (offset & sliceSizeModMask);
b.put(bpos, toByte(byt));
}
protected static byte toByte(int byt) {
return (byte) (byt & 0xff);
}
protected static byte toByte(long l) {
return (byte) (l & 0xff);
}
@Override
public long getSixLong(long pos) {
final ByteBuffer bb = getSlice(pos);
int bpos = (int) (pos & sliceSizeModMask);
return
((long) (bb.get(bpos++) & 0xff) << 40) |
((long) (bb.get(bpos++) & 0xff) << 32) |
((long) (bb.get(bpos++) & 0xff) << 24) |
((long) (bb.get(bpos++) & 0xff) << 16) |
((long) (bb.get(bpos++) & 0xff) << 8) |
((long) (bb.get(bpos) & 0xff));
}
@Override
public void putSixLong(long pos, long value) {
final ByteBuffer b = getSlice(pos);
int bpos = (int) (pos & sliceSizeModMask);
if(CC.ASSERT && (value >>>48!=0))
throw new DBException.DataCorruption("six long out of range");
b.put(bpos++, (byte) (0xff & (value >> 40)));
b.put(bpos++, (byte) (0xff & (value >> 32)));
b.put(bpos++, (byte) (0xff & (value >> 24)));
b.put(bpos++, (byte) (0xff & (value >> 16)));
b.put(bpos++, (byte) (0xff & (value >> 8)));
b.put(bpos, (byte) (0xff & (value)));
}
@Override
public int putPackedLong(long pos, long value) {
final ByteBuffer b = getSlice(pos);
int bpos = (int) (pos & sliceSizeModMask);
//$DELAY$
int ret = 0;
int shift = 63-Long.numberOfLeadingZeros(value);
shift -= shift%7; // round down to nearest multiple of 7
while(shift!=0){
b.put(bpos + (ret++), (byte) (((value >>> shift) & 0x7F) ));
//$DELAY$
shift-=7;
}
b.put(bpos +(ret++),(byte) ((value & 0x7F) | 0x80));
return ret;
}
@Override
public long getPackedLong(long position) {
final ByteBuffer b = getSlice(position);
int bpos = (int) (position & sliceSizeModMask);
long ret = 0;
int pos2 = 0;
byte v;
do{
v = b.get(bpos +(pos2++));
ret = (ret<<7 ) | (v & 0x7F);
}while((v&0x80)==0);
return (((long)pos2)<<60) | ret;
}
@Override
public void clear(long startOffset, long endOffset) {
if(CC.ASSERT && (startOffset >>> sliceShift) != ((endOffset-1) >>> sliceShift))
throw new AssertionError();
ByteBuffer buf = getSlice(startOffset);
int start = (int) (startOffset&sliceSizeModMask);
int end = (int) (start+(endOffset-startOffset));
int pos = start;
while(pos<end){
buf = buf.duplicate();
buf.position(pos);
buf.put(CLEAR, 0, Math.min(CLEAR.length, end-pos));
pos+=CLEAR.length;
}
}
@Override
public boolean isSliced(){
return true;
}
@Override
public int sliceSize() {
return sliceSize;
}
/**
* Hack to unmap MappedByteBuffer.
* Unmap is necessary on Windows, otherwise file is locked until JVM exits or BB is GCed.
* There is no public JVM API to unmap buffer, so this tries to use SUN proprietary API for unmap.
* Any error is silently ignored (for example SUN API does not exist on Android).
*/
protected static boolean unmap(MappedByteBuffer b){
if(!unmapHackSupported) {
return false;
}
if(!(b instanceof DirectBuffer))
return false;
// need to dispose old direct buffer, see bug
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038
DirectBuffer bb = (DirectBuffer) b;
Cleaner c = bb.cleaner();
if(c!=null){
c.clean();
return true;
}
Object attachment = bb.attachment();
return attachment!=null &&
attachment instanceof DirectBuffer &&
attachment!=b &&
unmap((MappedByteBuffer) attachment);
}
private static boolean unmapHackSupported = true;
static{
try{
//TODO use better way to recognize class?
unmapHackSupported = Thread.currentThread().getContextClassLoader().loadClass("sun.nio.ch.DirectBuffer")!=null;
}catch(Exception e){
LOG.warning("mmap file unmap hack not supported, mmap files will not be closed, sun.nio.ch.DirectBuffer not found");
unmapHackSupported = false;
}
}
// Workaround for https://github.com/jankotek/MapDB/issues/326
// File locking after .close() on Windows.
static boolean windowsWorkaround = System.getProperty("os.name").toLowerCase().startsWith("win");
}