/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-07 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id: BlockingInputStream.java 223 2007-04-21 22:13:05Z dizzzz $
*/
package org.exist.storage.io;
import java.io.IOException;
import java.io.InputStream;
/**
* <code>BlockingInputStream</code> is a combination of an input stream and
* an output stream, connected through a (circular) buffer in memory.
* It is intended for coupling producer threads to consumer threads via a
* (byte) stream.
* When the buffer is full producer threads will be blocked until the buffer
* has some free space again. When the buffer is empty the consumer threads will
* be blocked until some bytes are available again.
* Closing of the output stream will block until the inputstream is closed.
* A special version of the close function enables the consumer threads to
* specify that an exception has occurred. This will cause producer calls to
* be unblocked and throw an IOException containing this exception as cause.
*
* @author Chris Offerman
*/
public class BlockingInputStream extends InputStream {
private final static int EOS = -1;
private static final int CAPACITY = 8192;
private static final int SIZE = CAPACITY + 1;
private byte[] buffer = new byte[SIZE]; // Circular queue.
private int head; // First full buffer position.
private int tail; // First empty buffer position.
private boolean inClosed; // Is the input stream closed?
private boolean outClosed; // Is the output stream closed?
private Exception inException; // Specified when closing input.
private Exception outException; // Specified when closing output.
private BlockingOutputStream bos = new BlockingOutputStream(this);
/**
* BlockingOutputStream adapter for this BlockingInputStream.
*/
public BlockingOutputStream getOutputStream() {
return bos;
}
/** Is a stream closed? */
private boolean closed() {
return inClosed || outClosed;
}
/* InputStream methods */
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @throws IOException if an I/O error occurs.
*/
public synchronized int read() throws IOException {
byte bb[] = new byte[1];
return (read(bb, 0, 1) == EOS) ? EOS : bb[0];
}
/**
* Reads up to <code>len</code> bytes of data from the input stream into
* an array of bytes. An attempt is made to read as many as
* <code>len</code> bytes, but a smaller number may be read.
* The number of bytes actually read is returned as an integer.
*
* <p> This method blocks until input data is available, end of file is
* detected, or an exception is thrown.
*
*
* @param b the buffer into which the data is read.
* @param off the start offset in array <code>b</code>
* at which the data is written.
* @param len the maximum number of bytes to read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @throws IOException if an I/O error occurs.
* @throws NullPointerException if <code>b</code> is <code>null</code>.
*/
public synchronized int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int count = EOS;
try {
while (empty() && !closed()) wait();
if (outException != null) throw new ExistIOException(
"BlockingOutputStream closed with an exception.", outException);
else if (!closed()) {
count = Math.min(len, available());
int count1 = Math.min(count, availablePart1());
System.arraycopy(buffer, head, b, off, count1);
int count2 = count - count1;
if (count2 > 0) {
System.arraycopy(buffer, 0, b, off + count1, count2);
}
head = next(head, count);
if (empty()) head = tail = 0; // Reset to optimal situation.
}
} catch (InterruptedException e) {
throw new ExistIOException("Read operation interrupted.", e);
} finally {
notifyAll();
}
return count;
}
/**
* Closes this input stream and releases the buffer associated
* with this stream.
*/
public synchronized void close() {
inClosed = true;
buffer = null;
notifyAll();
}
/**
* Closes this input stream, specifying that an exception has occurred.
* This will cause all producer calls to be unblocked and throw an
* ExistIOException with this exception as its cause.
* Releases the buffer associated with this stream.
* <code>BlockingInputStream</code> specific method.
*/
public synchronized void close(Exception ex) {
inException = ex;
close();
}
/**
* The number of bytes that can be read (or skipped over) from
* this input stream without blocking by the next caller of a method for
* this input stream.
*
*
* @return the number of bytes that can be read from this input stream
* without blocking.
* @throws ExistIOException if an I/O error occurs.
*/
public synchronized int available() {
return (tail - head + SIZE) % SIZE;
}
private int availablePart1() {
return (tail >= head) ? tail - head : SIZE - head;
}
// DWES Never called?
private int availablePart2() {
return (tail >= head) ? 0 : tail;
}
/* OutputStream methods */
/**
* Writes the specified byte to this output stream. The general
* contract for <code>write</code> is that one byte is written
* to the output stream. The byte to be written is the eight
* low-order bits of the argument <code>b</code>. The 24
* high-order bits of <code>b</code> are ignored.
*
*
* @param b the <code>byte</code>.
* @throws ExistIOException if an I/O error occurs. In particular,
* an <code>ExistIOException</code> may be thrown if the
* output stream has been closed.
*/
synchronized void writeOutputStream(int b) throws IOException {
byte bb[] = { (byte) b };
writeOutputStream(bb, 0, 1);
}
/**
* Writes <code>len</code> bytes from the specified byte array
* starting at offset <code>off</code> to this output stream.
* The general contract for <code>write(b, off, len)</code> is that
* some of the bytes in the array <code>b</code> are written to the
* output stream in order; element <code>b[off]</code> is the first
* byte written and <code>b[off+len-1]</code> is the last byte written
* by this operation.
*
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @throws IOException if an I/O error occurs. In particular,
* an <code>IOException</code> is thrown if the output
* stream is closed.
*/
synchronized void writeOutputStream(byte b[], int off, int len)
throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
}
while (len > 0) {
int count;
try {
while (full() && !closed()) wait();
if (inException != null) throw new ExistIOException(
"BlockingInputStream closed with exception.", inException);
else if (closed()) throw new ExistIOException(
"Writing to closed stream", inException);
count = Math.min(len, free());
int count1 = Math.min(count, freePart1());
System.arraycopy(b, off, buffer, tail, count1);
int count2 = count - count1;
if (count2 > 0) {
System.arraycopy(b, off + count1, buffer, 0, count2);
}
tail = next(tail, count);
} catch (InterruptedException e) {
throw new ExistIOException("Write operation interrupted.", e);
} finally {
notifyAll();
}
off += count;
len -= count;
}
}
/**
* Equivalent of the <code>close()</code> method of an output stream.
* Renamed to solve the name clash with the <code>close()</code> method
* of the input stream also implemented by this class.
* Closes this output stream.
* A closed stream cannot perform output operations and cannot be reopened.
* <p>
* This method blocks its caller until the corresponding input stream is
* closed or an exception occurs.
*
* @throws IOException if an I/O error occurs.
*/
synchronized void closeOutputStream() throws IOException {
if (outException == null) flushOutputStream();
outClosed = true;
notifyAll();
try {
while(!inClosed) wait();
if (inException != null) throw new ExistIOException(
"BlockingInputStream closed with an exception.", inException);
else if (!empty()) throw new ExistIOException(
"Closing non empty closed stream.", inException);
} catch (InterruptedException e) {
throw new ExistIOException(
"Close OutputStream operation interrupted.", e);
}
}
/**
* Closes this output stream, specifying that an exception has occurred.
* This will cause all consumer calls to be unblocked and throw an
* IOException with this exception as its cause.
* <code>BlockingInputStream</code> specific method.
* @throws IOException if an I/O error occurs.
*/
synchronized void closeOutputStream(Exception ex) throws IOException {
outException = ex;
closeOutputStream();
}
/**
* Flushes this output stream and forces any buffered output bytes
* to be written out.
* <p>
* This methods blocks its caller until all buffered bytes are actually
* read by the consuming threads.
*
*
* @throws IOException if an I/O error occurs.
*/
synchronized void flushOutputStream() throws IOException {
try {
while(!empty() && !closed()) wait();
if (inException != null) throw new ExistIOException(
"BlockingInputStream closed with an exception.", inException);
else if (!empty()) throw new ExistIOException(
"Flushing non empty closed stream.", inException);
} catch (InterruptedException e) {
throw new ExistIOException("Flush operation interrupted.", e);
} finally {
notifyAll();
}
}
/**
* The number of bytes that can be written to
* this output stream without blocking by the next caller of a method for
* this output stream.
*
*
* @return the number of bytes that can be written to this output stream
* without blocking.
* @throws IOException if an I/O error occurs.
*/
private synchronized int free() {
int prevhead = prev(head);
return (prevhead - tail + SIZE) % SIZE;
}
private int freePart1() {
int prevhead = prev(head);
return (prevhead >= tail) ? prevhead - tail : SIZE - tail;
}
// DWES Never called?
private int freePart2() {
int prevhead = prev(head);
return (prevhead >= tail) ? 0 : prevhead;
}
/* Buffer management methods */
private boolean empty() {
return head == tail;
}
private boolean full() {
return next(tail) == head;
}
private static int next(int pos) {
return next(pos, 1);
}
private static int next(int pos, int incr) {
return (pos + incr) % SIZE;
}
private static int prev(int pos) {
return prev(pos, 1);
}
private static int prev(int pos, int decr) {
return (pos - decr + SIZE) % SIZE;
}
}