package rescuecore2.connection; import static rescuecore2.misc.EncodingTools.writeInt32; import static rescuecore2.misc.EncodingTools.readInt32; import static rescuecore2.misc.EncodingTools.readBytes; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; import java.io.EOFException; import java.io.InterruptedIOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.List; import java.util.LinkedList; import rescuecore2.misc.WorkerThread; import rescuecore2.misc.Pair; import rescuecore2.registry.Registry; import rescuecore2.log.Logger; /** Connection implementation that uses InputStreams and OutputStreams. */ public class StreamConnection extends AbstractConnection { private static final int SEND_WAIT = 10000; private InputStream in; private OutputStream out; private ReadThread readThread; private WriteThread writeThread; private List<byte[]> toWrite; /** Create a StreamConnection. @param in The InputStream to read. @param out The OutputStream to write to. */ public StreamConnection(InputStream in, OutputStream out) { super(); this.in = in; this.out = out; toWrite = new LinkedList<byte[]>(); } @Override protected void startupImpl() { Logger.debug("Starting " + this + ". Registry: " + Registry.getCurrentRegistry()); readThread = new ReadThread(); writeThread = new WriteThread(); readThread.start(); writeThread.start(); } @Override public boolean isAlive() { return super.isAlive() && readThread.isRunning() && writeThread.isRunning(); } @Override protected void shutdownImpl() { Logger.info("Shutting down " + this); try { readThread.kill(); } catch (InterruptedException e) { Logger.error("StreamConnection interrupted while shutting down read thread", e); } try { writeThread.kill(); } catch (InterruptedException e) { Logger.error("StreamConnection interrupted while shutting down write thread", e); } try { out.flush(); } catch (IOException e) { Logger.error("StreamConnection error flushing output buffer", e); } try { out.close(); } catch (IOException e) { Logger.error("StreamConnection error closing output buffer", e); } try { in.close(); } catch (IOException e) { Logger.error("StreamConnection error closing input buffer", e); } } @Override protected void sendBytes(byte[] b) throws IOException { synchronized (toWrite) { toWrite.add(b); toWrite.notifyAll(); } } /** Worker thread that reads from the input stream. */ private class ReadThread extends WorkerThread { @Override protected boolean work() { try { int size = readInt32(in); if (size > 0) { byte[] buffer = readBytes(size, in); bytesReceived(buffer); } return true; } catch (InterruptedIOException e) { return true; } catch (EOFException e) { return false; } catch (IOException e) { Logger.error("Error reading from StreamConnection " + StreamConnection.this, e); return false; } } } /** Worker thread that writes to the output stream. */ private class WriteThread extends WorkerThread { @Override protected boolean work() throws InterruptedException { byte[] bytes = null; synchronized (toWrite) { if (toWrite.isEmpty()) { toWrite.wait(SEND_WAIT); return true; } else { bytes = toWrite.remove(0); } } if (bytes == null) { return true; } try { writeInt32(bytes.length, out); out.write(bytes); out.flush(); return true; } catch (IOException e) { Logger.error("Error writing to StreamConnection " + StreamConnection.this, e); return false; } } } /** Create and start a pair of connections that pipe input to each other. @return A pair of connections. */ public static Pair<Connection, Connection> createConnectionPair() { try { PipedInputStream in1 = new PipedInputStream(); PipedInputStream in2 = new PipedInputStream(); PipedOutputStream out1 = new PipedOutputStream(in2); PipedOutputStream out2 = new PipedOutputStream(in1); Connection c1 = new StreamConnection(in1, out1); Connection c2 = new StreamConnection(in2, out2); return new Pair<Connection, Connection>(c1, c2); } catch (IOException e) { throw new RuntimeException(e); } } }