/* This file is part of VoltDB.
* Copyright (C) 2008-2010 VoltDB L.L.C.
*
* VoltDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VoltDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.utils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import org.voltdb.utils.DBBPool.BBContainer;
/**
* InputStream implementation that can present data stored in ByteBuffers
*/
public class BBInputStream extends InputStream {
/**
* ByteBuffer containers to be used as a source of data for this input stream.
*/
private final ArrayDeque<BBContainer> containers = new ArrayDeque<BBContainer>();
/**
* Container for the ByteBuffer that is being used as a source of bytes.
*/
private BBContainer cContainer;
/**
* Current ByteBuffer being used as a source of data.
*/
private ByteBuffer cBuffer;
/**
* Set to true when no more buffers are going to be offered.
*/
private boolean m_eof = false;
/**
* Number of bytes available for reading without blocking
*/
private int m_available = 0;
@Override
public int available() throws IOException {
return m_available;
}
@Override
public int read() throws IOException {
if (cBuffer == null) {
getNextBuffer(true);
if (cBuffer == null) {
assert m_eof;
return -1;
}
}
int retval = cBuffer.get();
if (!cBuffer.hasRemaining()) {
cBuffer = null;
cContainer.discard();
cContainer = null;
}
m_available--;
return retval;
}
@Override
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
// total bytes written to b[], the return value.
int consumed = 0;
while (consumed < len) {
// no current buffer? get next or block on eof.
if (cBuffer == null) {
getNextBuffer(true);
if (cBuffer == null) {
assert m_eof;
assert(m_available == 0);
// partial reads return bytes read, otherwise eof.
return (consumed > 0) ? consumed : -1;
}
}
assert (cBuffer != null);
final int maxread = java.lang.Math.min(cBuffer.remaining(), (len-consumed));
cBuffer.get(b, (off + consumed), maxread);
consumed += maxread;
m_available -= maxread;
assert (m_available > -1);
// cleanup completed buffers
if (!cBuffer.hasRemaining()) {
cBuffer = null;
cContainer.discard();
cContainer = null;
}
}
return consumed;
}
/**
* Get the next buffer container from the containers Deque. If there are no containers in the Deque
* and block is true then the call will block until a buffer is available or EOF is received.
* @param block
*/
private void getNextBuffer(boolean block) {
synchronized(containers) {
if (containers.isEmpty()) {
if (block == false) {
return;
}
while(containers.isEmpty() && !m_eof) {
try {
containers.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
assert !containers.isEmpty() || m_eof;
if (containers.isEmpty()) {
return;
}
}
cContainer = containers.poll();
cBuffer = cContainer.b;
assert cBuffer.hasRemaining();
}
}
/**
* Offer a ByteBuffer as a source of data for this InputStream
* @param c ByteBuffer container to be used as a source of data
*/
public void offer(BBContainer c) {
assert c.b != null && c.b.hasRemaining();
synchronized (containers) {
containers.offer(c);
m_available += c.b.remaining();
containers.notifyAll();
}
}
/**
* Indicate that there are no more ByteBuffers coming and that the stream should return -1
* after all the data has been read.
*/
public void EOF() {
assert m_eof == false;
synchronized (containers) {
m_eof = true;
containers.notifyAll();
}
}
public void close() {
cBuffer = null;
cContainer = null;
for (BBContainer c : containers) {
c.discard();
}
containers.clear();
}
}