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.net.HttpURLConnection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <code>InputStream</code> implementation which is based upon a * specific range within a <code>HttpURLConnection</code>. * * @author Waldemar Hummer */ public class HttpChannelRangeInputStream extends InputStream { /** Logger. */ private final static Logger log = LoggerFactory.getLogger(HttpChannelRangeInputStream.class); /** Underlying file channel */ private final HttpURLConnection urlConnection; /** End of range */ private final long maxPosition; /** Current position */ private long currentPosition = 0; /** * Default constructor. * * @param urlConnection the URL connection. * @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 HttpChannelRangeInputStream(final HttpURLConnection urlConnection, final long totalLength, final long rangeStart, final long rangeEnd) throws IOException { checkNotNull(urlConnection, "Expected channel to be not null."); final long fileSize = totalLength; 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.urlConnection = urlConnection; this.maxPosition = rangeEnd; urlConnection.setRequestProperty("Range", "bytes=" + rangeStart + "-" + rangeEnd); currentPosition = 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 return urlConnection.getInputStream().read(); } @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); // Read from the URL connection final int read = urlConnection.getInputStream().read(b, off, bytesToRead); currentPosition += read; final byte[] debugBytesRead = new byte[read]; System.arraycopy(b, off, debugBytesRead, 0, read); log.debug("Read (len=" + bytesToRead + " from fileoff=" + currentPosition + "): " + new String(debugBytesRead, "UTF-8")); return read; } /** * Returns the number of remaining bytes until the end of the 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 - currentPosition); } }