/* $Id$ */
package ibis.io;
import ibis.util.ThreadPool;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
/**
* Contract: write to multiple outputstreams.
* when an exception occurs, store it and continue.
* when the data is written to all streams, throw one large exception
* that contains all previous exceptions.
* This way, even when one of the streams dies, the rest will receive the data.
**/
public final class OutputStreamSplitter extends OutputStream {
private static final int MAXTHREADS = 32;
private boolean removeOnException = false;
private boolean saveException = false;
private SplitterException savedException = null;
private long bytesWritten = 0;
ArrayList<OutputStream> out = new ArrayList<OutputStream>();
private int numSenders = 0;
private class Sender implements Runnable {
int offset;
int len;
byte[] buf;
int index;
Sender(byte[] buf, int index, int offset, int len) {
this.buf = buf;
this.offset = offset;
this.len = len;
this.index = index;
}
public void run() {
doWrite(buf, offset, len, index);
finish();
}
}
private class Flusher implements Runnable {
int index;
Flusher(int index) {
this.index = index;
}
public void run() {
doFlush(index);
finish();
}
}
private class Closer implements Runnable {
int index;
Closer(int index) {
this.index = index;
}
public void run() {
doClose(index);
finish();
}
}
void doWrite(byte[] buf, int offset, int len, int index) {
try {
OutputStream o = out.get(index);
o.write(buf, offset, len);
} catch(IOException e) {
addException(e, index);
}
}
void doFlush(int index) {
try {
OutputStream o = out.get(index);
o.flush();
} catch(IOException e) {
addException(e, index);
}
}
void doClose(int index) {
try {
OutputStream o = out.get(index);
o.close();
} catch(IOException e) {
addException(e, index);
}
}
private synchronized void finish() {
numSenders--;
notifyAll();
}
private synchronized void addException(IOException e, int index) {
if (savedException == null) {
savedException = new SplitterException();
}
savedException.add(out.get(index), e);
if (removeOnException) {
out.set(index, null);
}
}
public OutputStreamSplitter() {
// empty constructor
}
public OutputStreamSplitter(boolean removeOnException, boolean saveException) {
this();
this.removeOnException = removeOnException;
this.saveException = saveException;
}
public void add(OutputStream s) {
out.add(s);
}
public void remove(OutputStream s) throws IOException {
int i = out.indexOf(s);
if (i == -1) {
throw new IOException("Removing unknown stream from splitter.");
}
out.remove(i);
}
public void write(int b) throws IOException {
synchronized(this) {
while (numSenders != 0) {
try {
wait();
} catch(Exception e) {
// Ignored
}
}
numSenders++;
}
bytesWritten += out.size();
for (int i = 0; i < out.size(); i++) {
try {
out.get(i).write(b);
} catch (IOException e2) {
if (savedException == null) {
savedException = new SplitterException();
}
savedException.add(out.get(i), e2);
if (removeOnException) {
out.remove(i);
i--;
}
}
}
synchronized(this) {
numSenders--;
notifyAll();
}
if (savedException != null) {
if (! saveException) {
SplitterException e = savedException;
savedException = null;
throw e;
}
}
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
if (out.size() > 0) {
bytesWritten += len * out.size();
synchronized(this) {
while (numSenders != 0) {
try {
wait();
} catch(Exception e) {
// Ignored
}
}
numSenders++;
}
for (int i = 1; i < out.size(); i++) {
Sender s = new Sender(b, i, off, len);
runThread(s, "Splitter sender");
}
doWrite(b, off, len, 0);
done();
}
}
public void flush() throws IOException {
if (out.size() > 0) {
synchronized(this) {
while (numSenders != 0) {
try {
wait();
} catch(Exception e) {
// Ignored
}
}
numSenders++;
}
for (int i = 1; i < out.size(); i++) {
Flusher f = new Flusher(i);
runThread(f, "Splitter flusher");
}
doFlush(0);
done();
}
}
public void close() throws IOException {
if (out.size() > 0) {
synchronized(this) {
while (numSenders != 0) {
try {
wait();
} catch(Exception e) {
// Ignored
}
}
numSenders++;
}
for (int i = 1; i < out.size(); i++) {
Closer f = new Closer(i);
runThread(f, "Splitter closer");
}
doClose(0);
done();
}
}
public long bytesWritten() {
return bytesWritten;
}
public void resetBytesWritten() {
bytesWritten = 0;
}
public SplitterException getExceptions() {
SplitterException e = savedException;
savedException = null;
return e;
}
private void runThread(Runnable r, String name) {
synchronized(this) {
while (numSenders >= MAXTHREADS) {
try {
wait();
} catch(Exception e) {
// Ignored
}
}
numSenders++;
}
ThreadPool.createNew(r, name);
}
private void done() throws IOException {
synchronized(this) {
numSenders--;
while (numSenders != 0) {
try {
wait();
} catch(Exception e) {
// Ignored
}
}
notifyAll();
if (savedException != null) {
if (removeOnException) {
for (int i = 0; i < out.size(); i++) {
if (out.get(i) == null) {
out.remove(i);
i--;
}
}
}
if (! saveException) {
SplitterException e = savedException;
savedException = null;
throw e;
}
}
}
}
}