/**
* Copyright 2008 - CommonCrawl Foundation
*
* CommonCrawl licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.commoncrawl.io;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class NIOBufferListInputStream extends InputStream {
protected NIOBufferList _bufferQueue = null;
protected ByteBuffer _activeBuf = null;
protected long _streamPos = 0;
public NIOBufferListInputStream(NIOBufferList source) {
_bufferQueue = source;
}
public synchronized void reset() {
_activeBuf = null;
}
public long getStreamPos() {
return _streamPos;
}
protected synchronized void getNext() throws IOException {
_activeBuf = null;
_activeBuf = _bufferQueue.read();
}
protected synchronized void ensureBuffer() throws IOException {
if (_activeBuf == null || _activeBuf.remaining() == 0) {
getNext();
}
}
@Override
public synchronized int read() throws IOException {
ensureBuffer();
if (_activeBuf != null) {
_streamPos++;
return (int) (_activeBuf.get() & 0xff);
}
return -1;
}
@Override
public synchronized int available() throws IOException {
int available = 0;
if (_activeBuf != null) {
available += _activeBuf.remaining();
}
available += _bufferQueue.available();
return available;
}
/**
* create and return a substream of a desired size - consume desired size
* bytes from source stream.
*
*
* @param desiredStreamSize - Desired Stream Size. If current stream
* contains less than disired size bytes an EOFException is thrown.
* @return new NIOBufferListInputStream contstrained to desired stream size
* @throws IOException
*/
public synchronized NIOBufferListInputStream subStream(int desiredStreamSize)throws IOException {
// throw EOF if we don't have enough bytes to service the request
if (available() < desiredStreamSize) {
throw new EOFException();
}
// otherwise ... allocate a new buffer list ...
NIOBufferList bufferList = new NIOBufferList();
int sizeOut = 0;
int len = desiredStreamSize;
// walk buffers from existing stream + source buffer list ...
while (len != 0) {
// grab new ByteBuffer from buffer list if necessary ...
ensureBuffer();
//if we could get another buffer from list. bail...
if (_activeBuf == null) {
break;
}
// calculate bytes available
final int sizeAvailable = _activeBuf.remaining();
// and bytes to read this iteration ...
final int sizeToRead = Math.min(sizeAvailable,len);
// if we can consume entire buffer ...
if (sizeAvailable <= sizeToRead) {
// slice the existing buffer ...
ByteBuffer buffer = _activeBuf.slice();
// position it to limit (bufferList.write flips it, so we must do this).
buffer.position(buffer.limit());
// add to buffer list via write
bufferList.write(buffer);
// null out this buffer as it has been fully consumed
_activeBuf = null;
}
else {
// slice the existing buffer
ByteBuffer buffer = _activeBuf.slice();
// reset limit on new buffer
buffer.limit(buffer.position() + sizeToRead);
// position the new buffer to limit (to facilitate flip in write call)
buffer.position(buffer.limit());
// add it to buffer list
bufferList.write(buffer);
// and increment position of source buffer
_activeBuf.position(_activeBuf.position() + sizeToRead);
}
len -= sizeToRead;
sizeOut += sizeToRead;
}
// flush any trailing write buffer in new list ...
bufferList.flush();
if (sizeOut != desiredStreamSize) {
throw new EOFException();
}
_streamPos += sizeOut;
return new NIOBufferListInputStream(bufferList);
}
@Override
public synchronized int read(byte[] b, int off, int len) throws IOException {
int sizeOut = 0;
while (len != 0) {
ensureBuffer();
if (_activeBuf == null) {
break;
}
final int sizeAvailable = _activeBuf.remaining();
final int sizeToRead = Math.min(sizeAvailable,len);
_activeBuf.get(b, off, sizeToRead);
len -= sizeToRead;
sizeOut += sizeToRead;
off += sizeToRead;
}
_streamPos += sizeOut;
return (sizeOut != 0) ? sizeOut : -1;
}
@Override
public synchronized int read(byte[] b) throws IOException {
return read(b,0,b.length);
}
@Override
public long skip(long skipAmount) throws IOException {
long skipAmountOut = 0;
while (skipAmount != 0) {
ensureBuffer();
if (_activeBuf == null) {
break;
}
final long sizeAvailable = _activeBuf.remaining();
final int sizeToSkip = (int) Math.min(sizeAvailable,skipAmount);
_activeBuf.position(_activeBuf.position() + sizeToSkip);
skipAmount -= sizeToSkip;
skipAmountOut += sizeToSkip;
}
_streamPos += skipAmountOut;
return skipAmountOut;
}
@Override
public synchronized void close() throws IOException {
if (_activeBuf != null && _activeBuf.remaining() != 0) {
if (_bufferQueue != null) {
_bufferQueue.putBack(_activeBuf);
}
}
super.close();
}
}