package org.mapdb.volume;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mapdb.CC;
import org.mapdb.DBException;
import org.mapdb.DataInput2;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileLock;
/**
* Created by jan on 2/29/16.
*/
public final class RandomAccessFileVol extends Volume {
public static final VolumeFactory FACTORY = new VolumeFactory() {
@Override
public Volume makeVolume(String file, boolean readOnly, long fileLockWait, int sliceShift, long initSize, boolean fixedSize) {
//TODO allocate initSize
return new org.mapdb.volume.RandomAccessFileVol(new File(file), readOnly, fileLockWait, initSize);
}
@NotNull
@Override
public boolean exists(@Nullable String file) {
return new File(file).exists();
}
@Override
public boolean handlesReadonly() {
return true;
}
};
protected final File file;
protected final RandomAccessFile raf;
protected final FileLock fileLock;
protected final boolean readOnly;
public RandomAccessFileVol(File file, boolean readOnly, long fileLockWait, long initSize) {
this.file = file;
this.readOnly = readOnly;
try {
this.raf = new RandomAccessFile(file, readOnly ? "r" : "rw"); //TODO rwd, rws? etc
this.fileLock = Volume.lockFile(file, raf.getChannel(), readOnly, fileLockWait);
//grow file if needed
if (initSize != 0 && !readOnly) {
long oldLen = raf.length();
if (initSize > raf.length()) {
raf.setLength(initSize);
clear(oldLen, initSize);
}
}
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void ensureAvailable(long offset) {
try {
if (raf.length() < offset)
raf.setLength(offset);
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void truncate(long size) {
try {
raf.setLength(size);
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void putLong(long offset, 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();
}
try {
raf.seek(offset);
raf.writeLong(value);
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void putInt(long offset, 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();
}
try {
raf.seek(offset);
raf.writeInt(value);
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void putByte(long offset, byte value) {
if (CC.VOLUME_PRINT_STACK_AT_OFFSET != 0 && CC.VOLUME_PRINT_STACK_AT_OFFSET == offset) {
new IOException("VOL STACK:").printStackTrace();
}
try {
raf.seek(offset);
raf.writeByte(value);
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void putData(long offset, 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();
}
try {
raf.seek(offset);
raf.write(src, srcPos, srcSize);
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void putData(long offset, ByteBuffer buf) {
byte[] bb = buf.array();
int pos = buf.position();
int size = buf.limit() - pos;
if (CC.VOLUME_PRINT_STACK_AT_OFFSET != 0 && CC.VOLUME_PRINT_STACK_AT_OFFSET >= offset && CC.VOLUME_PRINT_STACK_AT_OFFSET <= offset + size) {
new IOException("VOL STACK:").printStackTrace();
}
if (bb == null) {
bb = new byte[size];
buf.get(bb);
pos = 0;
}
putData(offset, bb, pos, size);
}
@Override
public synchronized long getLong(long offset) {
try {
raf.seek(offset);
return raf.readLong();
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized int getInt(long offset) {
try {
raf.seek(offset);
return raf.readInt();
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized byte getByte(long offset) {
try {
raf.seek(offset);
return raf.readByte();
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized DataInput2 getDataInput(long offset, int size) {
try {
raf.seek(offset);
byte[] b = new byte[size];
raf.readFully(b);
return new DataInput2.ByteArray(b);
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void getData(long offset, byte[] bytes, int bytesPos, int size) {
try {
raf.seek(offset);
raf.readFully(bytes, bytesPos, size);
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void close() {
if (!closed.compareAndSet(false,true))
return;
try {
if (fileLock != null && fileLock.isValid()) {
fileLock.release();
}
raf.close();
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void sync() {
try {
raf.getFD().sync();
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public int sliceSize() {
return 0;
}
@Override
public boolean isSliced() {
return false;
}
@Override
public synchronized long length() {
try {
return raf.length();
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public File getFile() {
return file;
}
@Override
public synchronized boolean getFileLocked() {
return fileLock != null && fileLock.isValid();
}
@Override
public synchronized void clear(long startOffset, long endOffset) {
try {
clearRAF(raf, startOffset, endOffset);
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
protected static void clearRAF(RandomAccessFile raf, long startOffset, long endOffset) throws IOException {
raf.seek(startOffset);
while (startOffset < endOffset) {
long remaining = Math.min(CLEAR.length, endOffset - startOffset);
raf.write(CLEAR, 0, (int) remaining);
startOffset += CLEAR.length;
}
}
@Override
public synchronized void putUnsignedShort(long offset, int value) {
try {
raf.seek(offset);
raf.write(value >> 8);
raf.write(value);
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized int getUnsignedShort(long offset) {
try {
raf.seek(offset);
return (raf.readUnsignedByte() << 8) |
raf.readUnsignedByte();
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized long getSixLong(long offset) {
try {
raf.seek(offset);
return
(((long) raf.readUnsignedByte()) << 40) |
(((long) raf.readUnsignedByte()) << 32) |
(((long) raf.readUnsignedByte()) << 24) |
(raf.readUnsignedByte() << 16) |
(raf.readUnsignedByte() << 8) |
raf.readUnsignedByte();
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized void putSixLong(long pos, long value) {
if (CC.ASSERT && (value >>> 48 != 0))
throw new DBException.DataCorruption("six long out of range");
try {
raf.seek(pos);
raf.write((int) (value >>> 40));
raf.write((int) (value >>> 32));
raf.write((int) (value >>> 24));
raf.write((int) (value >>> 16));
raf.write((int) (value >>> 8));
raf.write((int) (value));
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized int putPackedLong(long pos, long value) {
try {
raf.seek(pos);
//$DELAY$
int ret = 1;
int shift = 63 - Long.numberOfLeadingZeros(value);
shift -= shift % 7; // round down to nearest multiple of 7
while (shift != 0) {
ret++;
raf.write((int) (((value >>> shift) & 0x7F)));
//$DELAY$
shift -= 7;
}
raf.write((int) ((value & 0x7F)|0x80));
return ret;
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public synchronized long getPackedLong(long pos) {
try {
raf.seek(pos);
long ret = 0;
long pos2 = 0;
byte v;
do {
pos2++;
v = raf.readByte();
ret = (ret << 7) | (v & 0x7F);
} while ((v&0x80)==0);
return (pos2 << 60) | ret;
} catch (IOException e) {
throw new DBException.VolumeIOError(e);
}
}
@Override
public boolean isReadOnly() {
return readOnly;
}
}