package at.ac.tuwien.dsg.scaledom.io.impl;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <code>InputStream</code> implementation which is based upon a specific range within a <code>FileChannel</code>.
*
* @author Dominik Rauch
*/
public class FileChannelRangeInputStream extends InputStream {
/** Logger. */
private final static Logger log = LoggerFactory.getLogger(FileChannelRangeInputStream.class);
/** Underlying file channel */
private final FileChannel channel;
/** End of range */
private final long maxPosition;
/**
* Default constructor.
*
* @param channel the file channel.
* @param rangeStart the offset of the first byte of the first character.
* @param rangeEnd the offset of the last byte of the last character.
* @throws IOException If some I/O error occurs.
*/
public FileChannelRangeInputStream(final FileChannel channel, final long rangeStart, final long rangeEnd)
throws IOException {
checkNotNull(channel, "Expected channel to be not null.");
final long fileSize = channel.size();
checkArgument(rangeStart >= 0 && rangeStart <= fileSize,
"Expected rangeStart to be a valid index, but %s is not. File size is %s.", rangeStart, fileSize);
checkArgument(rangeEnd > 0 && rangeEnd <= fileSize,
"Expected rangeEnd to be a valid index, but %s is not. File size is %s.", rangeEnd, fileSize);
checkArgument(rangeStart < rangeEnd, "Expected rangeStart to be smaller than rangeEnd, but %s >= %s is not.",
rangeStart, rangeEnd);
this.channel = channel;
this.maxPosition = rangeEnd;
channel.position(rangeStart);
}
@Override
public int read() throws IOException {
if (getRemainingBytes() == 0) {
return -1;
}
log.warn("The read() method should never be used due to performance reasons.");
// Read a single byte from the channel
final ByteBuffer bb = ByteBuffer.allocate(1);
channel.read(bb);
return bb.get();
}
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
final int remainingBytes = getRemainingBytes();
if (remainingBytes == 0) {
return -1;
}
// Load either len bytes or until the end of the range
final int bytesToRead = Math.min(len, remainingBytes);
// Wrap b and read bytesToRead bytes from the file
final long oldChanPos = channel.position();
final ByteBuffer bb = ByteBuffer.wrap(b);
bb.position(off);
bb.limit(off + bytesToRead);
final int read = channel.read(bb);
final byte[] debugBytesRead = new byte[read];
System.arraycopy(b, off, debugBytesRead, 0, read);
log.debug("Read (len=" + bytesToRead + " from fileoff=" + oldChanPos + "): "
+ new String(debugBytesRead, "UTF-8"));
return read;
}
/**
* Returns the number of remaining bytes until the end of the channel part has been reached.
*
* @return number of remaining bytes.
* @throws IOException If some I/O error occurs.
*/
private int getRemainingBytes() throws IOException {
return (int) (maxPosition - channel.position());
}
}