package com.jivesoftware.os.amza.service.filer;
import com.jivesoftware.os.amza.api.filer.IFiler;
import com.jivesoftware.os.amza.api.filer.UIO;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
*
*/
public class MultiAutoGrowingByteBufferBackedFiler implements IFiler {
public static final long MAX_BUFFER_SEGMENT_SIZE = UIO.chunkLength(30);
public static long MAX_POSITION = MAX_BUFFER_SEGMENT_SIZE * 100;
private final long initialBufferSegmentSize;
private final long maxBufferSegmentSize;
private final ByteBufferFactory byteBufferFactory;
private ByteBufferBackedFiler[] filers;
private int fpFilerIndex;
private long length = 0;
private final int fShift;
private final long fseekMask;
private final long maxPosition;
public MultiAutoGrowingByteBufferBackedFiler(long initialBufferSegmentSize,
long maxBufferSegmentSize,
ByteBufferFactory byteBufferFactory) throws IOException {
this.initialBufferSegmentSize = initialBufferSegmentSize > 0 ? UIO.chunkLength(UIO.chunkPower(initialBufferSegmentSize, 0)) : -1;
maxBufferSegmentSize = Math.min(UIO.chunkLength(UIO.chunkPower(maxBufferSegmentSize, 0)), MAX_BUFFER_SEGMENT_SIZE);
this.maxBufferSegmentSize = maxBufferSegmentSize;
this.byteBufferFactory = byteBufferFactory;
this.filers = new ByteBufferBackedFiler[0];
this.length = byteBufferFactory.length();
// test power of 2
if ((maxBufferSegmentSize & (maxBufferSegmentSize - 1)) == 0) {
this.fShift = Long.numberOfTrailingZeros(maxBufferSegmentSize);
this.fseekMask = maxBufferSegmentSize - 1;
} else {
throw new IllegalArgumentException("It's hard to ensure powers of 2");
}
this.maxPosition = MAX_POSITION;
}
private MultiAutoGrowingByteBufferBackedFiler(long maxBufferSegmentSize,
ByteBufferFactory byteBufferFactory,
ByteBufferBackedFiler[] filers,
long length,
int fShift,
long fseekMask) {
this.initialBufferSegmentSize = -1;
this.maxBufferSegmentSize = maxBufferSegmentSize;
this.byteBufferFactory = byteBufferFactory;
this.filers = filers;
this.fpFilerIndex = -1;
this.length = length;
this.fShift = fShift;
this.fseekMask = fseekMask;
this.maxPosition = length;
}
public MultiAutoGrowingByteBufferBackedFiler duplicateAll() {
ByteBufferBackedFiler[] duplicate = new ByteBufferBackedFiler[filers.length];
long copyLength = length; // grab length before duplicating buffers
for (int i = 0; i < duplicate.length; i++) {
duplicate[i] = new ByteBufferBackedFiler(filers[i].buffer.duplicate());
}
return new MultiAutoGrowingByteBufferBackedFiler(maxBufferSegmentSize, byteBufferFactory, duplicate, copyLength, fShift, fseekMask);
}
final long ensure(long bytesToWrite) throws IOException {
long fp = getFilePointer();
long newFp = fp + bytesToWrite;
if (newFp > length) {
position(newFp);
position(fp);
return (newFp - length);
}
return 0;
}
final void position(long position) throws IOException {
if (position > maxPosition) {
throw new IllegalStateException("Encountered a likely runaway file position! position=" + position);
}
int f = (int) (position >> fShift);
long fseek = position & fseekMask;
if (f >= filers.length) {
int lastFilerIndex = filers.length - 1;
if (lastFilerIndex > -1 && filers[lastFilerIndex].length() < maxBufferSegmentSize) {
ByteBuffer reallocate = reallocate(lastFilerIndex, filers[lastFilerIndex].buffer, maxBufferSegmentSize);
filers[lastFilerIndex] = new ByteBufferBackedFiler(reallocate);
}
int newLength = f + 1;
ByteBufferBackedFiler[] newFilers = new ByteBufferBackedFiler[newLength];
System.arraycopy(filers, 0, newFilers, 0, filers.length);
for (int n = filers.length; n < newLength; n++) {
if (n < newLength - 1) {
newFilers[n] = new ByteBufferBackedFiler(allocate(n, maxBufferSegmentSize));
} else {
newFilers[n] = new ByteBufferBackedFiler(allocate(n, Math.max(fseek, initialBufferSegmentSize)));
}
}
filers = newFilers;
} else if (f == filers.length - 1 && fseek > filers[f].length()) {
long newSize = byteBufferFactory.nextLength(f, filers[f].length(), fseek);
ByteBuffer reallocate = reallocate(f, filers[f].buffer, Math.min(maxBufferSegmentSize, newSize));
filers[f] = new ByteBufferBackedFiler(reallocate);
}
filers[f].seek(fseek);
fpFilerIndex = f;
length = Math.max(position, length);
}
private ByteBuffer allocate(int index, long maxBufferSegmentSize) {
return byteBufferFactory.allocate(index, maxBufferSegmentSize);
}
private ByteBuffer reallocate(int index, ByteBuffer oldBuffer, long newSize) {
return byteBufferFactory.reallocate(index, oldBuffer, newSize);
}
@Override
public final void seek(long position) throws IOException {
position(position);
}
@Override
public long skip(long skip) throws IOException {
long fp = getFilePointer();
position(fp + skip);
return skip;
}
@Override
public long length() throws IOException {
return length;
}
@Override
public void setLength(long len) throws IOException {
position(len);
length = len;
}
@Override
public long getFilePointer() throws IOException {
if (filers.length == 0) {
return 0;
}
long fp = (fpFilerIndex * maxBufferSegmentSize) + filers[fpFilerIndex].getFilePointer();
return fp;
}
@Override
public void eof() throws IOException {
position(length());
}
@Override
public int read() throws IOException {
int read = filers[fpFilerIndex].read();
while (read == -1 && fpFilerIndex < filers.length - 1) {
fpFilerIndex++;
filers[fpFilerIndex].seek(0);
read = filers[fpFilerIndex].read();
}
return read;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int offset, int len) throws IOException {
if (len == 0) {
return 0;
}
int remaining = len;
int read = filers[fpFilerIndex].read(b, offset, remaining);
if (read == -1) {
read = 0;
}
offset += read;
remaining -= read;
while (remaining > 0 && fpFilerIndex < filers.length - 1) {
fpFilerIndex++;
filers[fpFilerIndex].seek(0);
read = filers[fpFilerIndex].read(b, offset, remaining);
if (read == -1) {
read = 0;
}
offset += read;
remaining -= read;
}
if (len == remaining) {
return -1;
}
return offset;
}
@Override
public void write(byte b) throws IOException {
int len = 1;
ensure(len);
long canWrite = Math.min(len, filers[fpFilerIndex].length() - filers[fpFilerIndex].getFilePointer());
if (canWrite > 0) {
filers[fpFilerIndex].write(b);
} else {
fpFilerIndex++;
filers[fpFilerIndex].seek(0);
filers[fpFilerIndex].write(b);
}
length += 1;
}
@Override
public void write(byte[] b, int offset, int len) throws IOException {
long count = ensure(len);
long canWrite = Math.min(len, filers[fpFilerIndex].length() - filers[fpFilerIndex].getFilePointer());
filers[fpFilerIndex].write(b, offset, (int) canWrite);
long remaingToWrite = len - canWrite;
offset += canWrite;
while (remaingToWrite > 0) {
fpFilerIndex++;
filers[fpFilerIndex].seek(0);
canWrite = Math.min(remaingToWrite, filers[fpFilerIndex].length() - filers[fpFilerIndex].getFilePointer());
filers[fpFilerIndex].write(b, offset, (int) canWrite);
remaingToWrite -= canWrite;
offset += canWrite;
}
length += count;
}
@Override
public void close() throws IOException {
for (ByteBufferBackedFiler filer : filers) {
filer.close();
}
}
@Override
public void flush(boolean fsync) throws IOException {
for (ByteBufferBackedFiler filer : filers) {
filer.flush(fsync);
}
}
@Override
public Object lock() {
return this;
}
}