package net.vhati.ftldat;
import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
public class FileChannelRegionInputStream extends InputStream {
private FileChannel channel;
private long regionOffset;
private long regionLength;
// A buffer holds an even narrower region of the file.
// When possible read() calls will reuse this,
// rather than pester the channel.
private ByteBuffer buf = null;
private long bufOffset = 0; // Relative to regionOffset.
private int bufLength = 0;
private long intraPos = 0;
public FileChannelRegionInputStream(FileChannel channel, long offset, long length) {
this(channel, offset, length, 4096);
}
public FileChannelRegionInputStream(FileChannel channel, long offset, long length, int bufferSize) {
this.channel = channel;
this.regionOffset = offset;
this.regionLength = length;
buf = ByteBuffer.allocate(bufferSize);
}
@Override
public int available() throws IOException {
if (!channel.isOpen())
throw new ClosedChannelException();
return bufLength;
}
@Override
public int read() throws IOException {
if (!channel.isOpen())
throw new ClosedChannelException();
if (intraPos >= regionLength)
return -1;
if (intraPos < bufOffset || intraPos >= bufOffset + bufLength) {
// The requested byte isn't currently buffered.
bufOffset = intraPos;
int len = 0; // Get *something*.
buf.position(0);
while (len == 0) {
len = channel.read(buf, regionOffset + bufOffset);
}
if (len == -1) {
bufLength = 0;
return -1;
} else {
bufLength = len;
}
}
// Do an absolute get() from the buffer,
// and interpret the byte as if it were unsigned.
int result = (int) (buf.get((int) (intraPos - bufOffset)) & 0xff);
intraPos++;
return result;
}
@Override
public int read(byte[] b, int bOff, int bLen) throws IOException {
if (bLen == 0)
return 0;
if (bOff < 0)
throw new IndexOutOfBoundsException(String.format("Index: %d, Size: %d", bOff, bLen));
if (bOff + bLen > b.length)
throw new IndexOutOfBoundsException(String.format("Index: %d, Size: %d", (bOff + bLen), bLen));
if (!channel.isOpen())
throw new ClosedChannelException();
if (intraPos >= regionLength)
return -1;
int bytesTotal = Math.min(bLen, (int) (regionLength - intraPos));
int bytesRemaining = bytesTotal;
int bytesRead = 0;
if (intraPos >= bufOffset && intraPos < bufOffset + bufLength) {
// Read part of the current buffer, possibly until the end.
buf.position((int) (intraPos - bufOffset));
int bufTodo = Math.min(bytesRemaining, bufLength - (int) (intraPos - bufOffset));
buf.get(b, bOff, bufTodo);
bytesRemaining -= bufTodo;
bytesRead += bufTodo;
intraPos += bufTodo;
}
if (bytesRemaining > 0) {
// Refill the buffer at the current intraPos.
bufOffset = intraPos;
int len = 0;
buf.position(0);
len = channel.read(buf, regionOffset + bufOffset);
if (len == -1) {
bufLength = 0;
throw new BufferUnderflowException();
} else {
bufLength = len;
}
buf.position(0);
int bufTodo = Math.min(bytesRemaining, bufLength);
buf.get(b, bOff + bytesRead, bufTodo);
bytesRemaining -= bufTodo;
bytesRead += bufTodo;
intraPos += bufTodo;
}
return bytesRead;
}
@Override
public long skip(long n) throws IOException {
if (!channel.isOpen())
throw new ClosedChannelException();
if (intraPos >= regionLength)
return -1;
if (intraPos + n <= regionLength) {
n = regionLength - intraPos;
}
intraPos += n;
return n;
}
}