package org.keycloak.testsuite.cli.exec; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.util.LinkedList; class InteractiveInputStream extends InputStream { private LinkedList<Byte> queue = new LinkedList<>(); private Thread consumer; private boolean closed; @Override public int read(byte b[]) throws IOException { return read(b, 0, b.length); } @Override public synchronized int read(byte[] b, int off, int len) throws IOException { Byte current = null; int rc = 0; try { consumer = Thread.currentThread(); do { current = queue.poll(); if (current == null) { if (rc > 0) { return rc; } else { do { if (closed) { return -1; } wait(); } while ((current = queue.poll()) == null); } } b[off + rc] = current; rc++; } while (rc < len); } catch (InterruptedException e) { throw new InterruptedIOException("Signalled to exit"); } finally { consumer = null; } return rc; } @Override public long skip(long n) throws IOException { return super.skip(n); } @Override public int available() throws IOException { return super.available(); } @Override public synchronized void mark(int readlimit) { super.mark(readlimit); } @Override public synchronized void reset() throws IOException { super.reset(); } @Override public boolean markSupported() { return super.markSupported(); } @Override public synchronized int read() throws IOException { // when input is available pass it on Byte current; try { consumer = Thread.currentThread(); while ((current = queue.poll()) == null) { // we don't check for closed before making sure // that there is nothing more to read if (closed) { return -1; } wait(); } } catch (InterruptedException e) { throw new InterruptedIOException("Signalled to exit"); } finally { consumer = null; } return current; } @Override public synchronized void close() { closed = true; if (consumer != null) { consumer.interrupt(); } } public synchronized void pushBytes(byte [] buff) { for (byte b : buff) { queue.add(b); } notify(); } }