Fixed issues with buffering input. Now fixed for windows/posix/unsupported
This commit is contained in:
parent
3756df22ab
commit
25855ae04d
|
@ -109,52 +109,70 @@ public class InputConsole {
|
|||
private final Object inputLockSingle = new Object();
|
||||
private final Object inputLockLine = new Object();
|
||||
|
||||
private final ObjectPool<ByteBuffer2> pool = ObjectPoolFactory.create(new ByteBuffer2Poolable());
|
||||
private ThreadLocal<ObjectPoolHolder<ByteBuffer2>> threadBuffer = new ThreadLocal<ObjectPoolHolder<ByteBuffer2>>();
|
||||
private List<ObjectPoolHolder<ByteBuffer2>> threadBuffersForRead = new CopyOnWriteArrayList<ObjectPoolHolder<ByteBuffer2>>();
|
||||
private final ObjectPool<ByteBuffer2> pool;
|
||||
|
||||
private ThreadLocal<ObjectPoolHolder<ByteBuffer2>> readBuff = new ThreadLocal<ObjectPoolHolder<ByteBuffer2>>();
|
||||
private List<ObjectPoolHolder<ByteBuffer2>> readBuffers = new CopyOnWriteArrayList<ObjectPoolHolder<ByteBuffer2>>();
|
||||
private ThreadLocal<Integer> threadBufferCounter = new ThreadLocal<Integer>();
|
||||
|
||||
private ThreadLocal<ObjectPoolHolder<ByteBuffer2>> readLineBuff = new ThreadLocal<ObjectPoolHolder<ByteBuffer2>>();
|
||||
private List<ObjectPoolHolder<ByteBuffer2>> readLineBuffers = new CopyOnWriteArrayList<ObjectPoolHolder<ByteBuffer2>>();
|
||||
|
||||
private volatile int readChar = -1;
|
||||
private final Terminal terminal;
|
||||
|
||||
private InputConsole() {
|
||||
Logger logger = InputConsole.logger;
|
||||
|
||||
String readers = System.getProperty(TerminalType.READERS);
|
||||
int readers2 = 32;
|
||||
if (readers != null) {
|
||||
try {
|
||||
readers2 = Integer.parseInt(readers);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
this.pool = ObjectPoolFactory.create(new ByteBuffer2Poolable(), readers2);
|
||||
|
||||
|
||||
String type = System.getProperty(TerminalType.TYPE, TerminalType.AUTO).toLowerCase();
|
||||
if ("dumb".equals(System.getenv("TERM"))) {
|
||||
type = TerminalType.NONE;
|
||||
logger.debug("$TERM=dumb; setting type={}", type);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("System environment 'TERM'=dumb, creating type=" + type);
|
||||
}
|
||||
} else {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Creating terminal, type=" + type);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Creating terminal; type={}", type);
|
||||
|
||||
|
||||
String encoding = Encoding.get();
|
||||
Terminal t;
|
||||
try {
|
||||
if (type.equals(TerminalType.UNIX)) {
|
||||
t = new UnixTerminal(encoding);
|
||||
t = new UnixTerminal();
|
||||
}
|
||||
else if (type.equals(TerminalType.WIN) || type.equals(TerminalType.WINDOWS)) {
|
||||
t = new WindowsTerminal();
|
||||
}
|
||||
else if (type.equals(TerminalType.NONE) || type.equals(TerminalType.OFF) || type.equals(TerminalType.FALSE)) {
|
||||
t = new UnsupportedTerminal(encoding);
|
||||
t = new UnsupportedTerminal();
|
||||
} else {
|
||||
if (isIDEAutoDetect()) {
|
||||
logger.debug("Terminal is in UNSUPPORTED (best guess). Unable to support single key input. Only line input available.");
|
||||
t = new UnsupportedTerminal(encoding);
|
||||
t = new UnsupportedTerminal();
|
||||
} else {
|
||||
if (OS.isWindows()) {
|
||||
t = new WindowsTerminal();
|
||||
} else {
|
||||
t = new UnixTerminal(encoding);
|
||||
t = new UnixTerminal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Failed to construct terminal, falling back to unsupported");
|
||||
t = new UnsupportedTerminal(encoding);
|
||||
t = new UnsupportedTerminal();
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -162,7 +180,7 @@ public class InputConsole {
|
|||
}
|
||||
catch (Throwable e) {
|
||||
logger.error("Terminal initialization failed, falling back to unsupported");
|
||||
t = new UnsupportedTerminal(encoding);
|
||||
t = new UnsupportedTerminal();
|
||||
|
||||
try {
|
||||
t.init();
|
||||
|
@ -174,7 +192,7 @@ public class InputConsole {
|
|||
t.setEchoEnabled(true);
|
||||
|
||||
this.terminal = t;
|
||||
logger.debug("Created Terminal: {} ({}x{})", this.terminal.getClass().getSimpleName(), t.getWidth(), t.getHeight());
|
||||
logger.debug("Created Terminal: {} ({}x{})", t.getClass().getSimpleName(), t.getWidth(), t.getHeight());
|
||||
}
|
||||
|
||||
// called when the JVM is shutting down.
|
||||
|
@ -208,15 +226,41 @@ public class InputConsole {
|
|||
|
||||
/** return -1 if no data or bunged-up */
|
||||
private final int read0() {
|
||||
synchronized (this.inputLockSingle) {
|
||||
try {
|
||||
this.inputLockSingle.wait();
|
||||
} catch (InterruptedException e) {
|
||||
return -1;
|
||||
Integer bufferCounter = this.threadBufferCounter.get();
|
||||
ObjectPoolHolder<ByteBuffer2> objectPoolHolder = this.readBuff.get();
|
||||
ByteBuffer2 buffer = null;
|
||||
|
||||
if (objectPoolHolder == null) {
|
||||
bufferCounter = 0;
|
||||
this.threadBufferCounter.set(bufferCounter);
|
||||
|
||||
ObjectPoolHolder<ByteBuffer2> holder = this.pool.take();
|
||||
buffer = holder.getValue();
|
||||
this.readBuff.set(holder);
|
||||
this.readBuffers.add(holder);
|
||||
} else {
|
||||
buffer = objectPoolHolder.getValue();
|
||||
}
|
||||
|
||||
if (bufferCounter == buffer.position()) {
|
||||
synchronized (this.inputLockSingle) {
|
||||
buffer.setPosition(0);
|
||||
this.threadBufferCounter.set(0);
|
||||
|
||||
try {
|
||||
this.inputLockSingle.wait();
|
||||
} catch (InterruptedException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.readChar;
|
||||
bufferCounter = this.threadBufferCounter.get();
|
||||
char c = buffer.readChar(bufferCounter);
|
||||
bufferCounter += 2;
|
||||
|
||||
this.threadBufferCounter.set(bufferCounter);
|
||||
return c;
|
||||
}
|
||||
|
||||
/** return empty char[] if no data */
|
||||
|
@ -237,12 +281,12 @@ public class InputConsole {
|
|||
// the current line info.
|
||||
|
||||
// the threadBufferForRead getting added is the part that is important
|
||||
if (this.threadBuffer.get() == null) {
|
||||
if (this.readLineBuff.get() == null) {
|
||||
ObjectPoolHolder<ByteBuffer2> holder = this.pool.take();
|
||||
this.threadBuffer.set(holder);
|
||||
this.threadBuffersForRead.add(holder);
|
||||
this.readLineBuff.set(holder);
|
||||
this.readLineBuffers.add(holder);
|
||||
} else {
|
||||
this.threadBuffer.get().getValue().clear();
|
||||
this.readLineBuff.get().getValue().clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,7 +298,7 @@ public class InputConsole {
|
|||
}
|
||||
}
|
||||
|
||||
ObjectPoolHolder<ByteBuffer2> objectPoolHolder = this.threadBuffer.get();
|
||||
ObjectPoolHolder<ByteBuffer2> objectPoolHolder = this.readLineBuff.get();
|
||||
ByteBuffer2 buffer = objectPoolHolder.getValue();
|
||||
int len = buffer.position();
|
||||
if (len == 0) {
|
||||
|
@ -267,9 +311,9 @@ public class InputConsole {
|
|||
// dump the chars in the buffer (safer for passwords, etc)
|
||||
buffer.clearSecure();
|
||||
|
||||
this.threadBuffersForRead.remove(objectPoolHolder);
|
||||
this.readLineBuffers.remove(objectPoolHolder);
|
||||
this.pool.release(objectPoolHolder);
|
||||
this.threadBuffer.set(null);
|
||||
this.readLineBuff.set(null);
|
||||
|
||||
return readChars;
|
||||
}
|
||||
|
@ -302,7 +346,6 @@ public class InputConsole {
|
|||
while ((typedChar = this.terminal.read()) != -1) {
|
||||
synchronized (this.inputLock) {
|
||||
// don't let anyone add a new reader while we are still processing the current actions
|
||||
|
||||
asChar = (char) typedChar;
|
||||
|
||||
if (logger2.isTraceEnabled()) {
|
||||
|
@ -311,22 +354,25 @@ public class InputConsole {
|
|||
|
||||
// notify everyone waiting for a character.
|
||||
synchronized (this.inputLockSingle) {
|
||||
if (this.terminal.wasSequence() && typedChar == '\n') {
|
||||
// don't want to forward \n if it was a part of a sequence in the unsupported terminal
|
||||
// the JIT will short-cut this out if we are not the unsupported terminal
|
||||
} else {
|
||||
this.readChar = typedChar;
|
||||
this.inputLockSingle.notifyAll();
|
||||
// have to do readChar first (readLine has to deal with \b and \n
|
||||
for (ObjectPoolHolder<ByteBuffer2> objectPoolHolder : this.readBuffers) {
|
||||
ByteBuffer2 buffer = objectPoolHolder.getValue();
|
||||
buffer.writeChar(asChar);
|
||||
}
|
||||
|
||||
this.inputLockSingle.notifyAll();
|
||||
}
|
||||
|
||||
|
||||
// now to handle readLine stuff
|
||||
|
||||
// if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed.
|
||||
if (typedChar == '\b') {
|
||||
if (asChar == '\b') {
|
||||
int position = 0;
|
||||
|
||||
// clear ourself + one extra.
|
||||
if (ansiEnabled) {
|
||||
for (ObjectPoolHolder<ByteBuffer2> objectPoolHolder : this.threadBuffersForRead) {
|
||||
for (ObjectPoolHolder<ByteBuffer2> objectPoolHolder : this.readLineBuffers) {
|
||||
ByteBuffer2 buffer = objectPoolHolder.getValue();
|
||||
// size of the buffer BEFORE our backspace was typed
|
||||
int length = buffer.position();
|
||||
|
@ -363,21 +409,17 @@ public class InputConsole {
|
|||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
// read-line will ignore backspace
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows)
|
||||
if (asChar == '\n') {
|
||||
else if (asChar == '\n') {
|
||||
// ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows)
|
||||
synchronized (this.inputLockLine) {
|
||||
this.inputLockLine.notifyAll();
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// only append if we are not a new line.
|
||||
// our windows console PREVENTS us from returning '\r' (it truncates '\r\n', and returns just '\n')
|
||||
|
||||
for (ObjectPoolHolder<ByteBuffer2> objectPoolHolder : this.threadBuffersForRead) {
|
||||
for (ObjectPoolHolder<ByteBuffer2> objectPoolHolder : this.readLineBuffers) {
|
||||
ByteBuffer2 buffer = objectPoolHolder.getValue();
|
||||
buffer.writeChar(asChar);
|
||||
}
|
||||
|
|
|
@ -10,42 +10,12 @@ public abstract class Terminal {
|
|||
public static final int DEFAULT_HEIGHT = 24;
|
||||
|
||||
private volatile boolean echoEnabled;
|
||||
private volatile Thread shutdown;
|
||||
|
||||
public Terminal() {
|
||||
if (this.shutdown != null) {
|
||||
try {
|
||||
Runtime.getRuntime().removeShutdownHook(this.shutdown);
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// The VM is shutting down, ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Register a task to restore the terminal on shutdown
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
restore();
|
||||
} catch (IOException e) {
|
||||
Terminal.this.logger.error("Unable to restore the terminal", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.shutdown = new Thread(runnable, "Terminal");
|
||||
|
||||
try {
|
||||
Runtime.getRuntime().addShutdownHook(this.shutdown);
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// The VM is shutting down, ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public abstract void init() throws IOException;
|
||||
|
||||
public abstract void restore() throws IOException;
|
||||
|
||||
public void setEchoEnabled(boolean enabled) {
|
||||
|
@ -63,11 +33,4 @@ public abstract class Terminal {
|
|||
* @return a character from whatever underlying input method the terminal has available.
|
||||
*/
|
||||
public abstract int read();
|
||||
|
||||
/**
|
||||
* Only needed for unsupported character input.
|
||||
*/
|
||||
public boolean wasSequence() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package dorkbox.util.input;
|
|||
|
||||
public class TerminalType {
|
||||
public static final String TYPE = "input.terminal";
|
||||
public static final String READERS = "input.terminal.readers";
|
||||
|
||||
public static final String AUTO = "auto";
|
||||
public static final String UNIX = "unix";
|
||||
|
|
|
@ -6,6 +6,7 @@ import java.nio.ByteBuffer;
|
|||
|
||||
import com.sun.jna.Native;
|
||||
|
||||
import dorkbox.util.input.Encoding;
|
||||
import dorkbox.util.input.Terminal;
|
||||
|
||||
/**
|
||||
|
@ -25,7 +26,8 @@ public class UnixTerminal extends Terminal {
|
|||
private ByteBuffer windowSizeBuffer = ByteBuffer.allocate(8);
|
||||
|
||||
|
||||
public UnixTerminal(String encoding) throws Exception {
|
||||
public UnixTerminal() throws Exception {
|
||||
String encoding = Encoding.get();
|
||||
this.reader = new InputStreamReader(System.in, encoding);
|
||||
|
||||
this.term = (PosixTerminalControl) Native.loadLibrary("c", PosixTerminalControl.class);
|
||||
|
|
|
@ -1,29 +1,20 @@
|
|||
package dorkbox.util.input.unsupported;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import dorkbox.util.bytes.ByteBuffer2;
|
||||
import dorkbox.util.input.Terminal;
|
||||
import dorkbox.util.input.posix.InputStreamReader;
|
||||
|
||||
public class UnsupportedTerminal extends Terminal {
|
||||
|
||||
private final ByteBuffer2 buffer = new ByteBuffer2(8, -1);
|
||||
private ByteBuffer2 buffer = new ByteBuffer2(8, -1);
|
||||
|
||||
private BufferedReader reader;
|
||||
private String readLine = null;
|
||||
private char[] line;
|
||||
private int readerCount = -1;
|
||||
private InputStream in;
|
||||
|
||||
private ThreadLocal<Integer> indexOfStringForReadChar = new ThreadLocal<Integer>() {
|
||||
@Override
|
||||
protected Integer initialValue() {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
public UnsupportedTerminal(String encoding) {
|
||||
this.reader = new BufferedReader(new InputStreamReader(System.in, encoding));
|
||||
public UnsupportedTerminal() {
|
||||
this.in = System.in;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -46,46 +37,39 @@ public class UnsupportedTerminal extends Terminal {
|
|||
|
||||
@Override
|
||||
public final int read() {
|
||||
// if we are reading data (because we are in IDE mode), we want to return ALL
|
||||
// the chars of the line!
|
||||
|
||||
// so, readChar is REALLY the index at which we return letters (until the whole string is returned)
|
||||
int readerCount = this.indexOfStringForReadChar.get();
|
||||
|
||||
if (readerCount == -1) {
|
||||
// if we are reading data (because we are in IDE mode), we want to return ALL the chars of the line!
|
||||
|
||||
// so, 'readerCount' is REALLY the index at which we return letters (until the whole string is returned)
|
||||
if (this.readerCount == -1) {
|
||||
// we have to wait for more data.
|
||||
try {
|
||||
this.readLine = this.reader.readLine();
|
||||
} catch (IOException e) {
|
||||
return -1;
|
||||
}
|
||||
InputStream sysIn = this.in;
|
||||
int read;
|
||||
char asChar;
|
||||
this.buffer.clearSecure();
|
||||
|
||||
this.line = this.readLine.toCharArray();
|
||||
this.buffer.clear();
|
||||
for (char c : this.line) {
|
||||
this.buffer.writeChar(c);
|
||||
while ((read = sysIn.read()) != -1) {
|
||||
asChar = (char)read;
|
||||
if (asChar == '\n') {
|
||||
this.readerCount = this.buffer.position();
|
||||
this.buffer.rewind();
|
||||
break;
|
||||
} else {
|
||||
this.buffer.writeChar(asChar);
|
||||
}
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
|
||||
readerCount = 0;
|
||||
this.indexOfStringForReadChar.set(0);
|
||||
}
|
||||
|
||||
|
||||
// EACH thread will have it's own count!
|
||||
if (readerCount == this.buffer.position()) {
|
||||
this.indexOfStringForReadChar.set(-1);
|
||||
if (this.readerCount == this.buffer.position()) {
|
||||
this.readerCount = -1;
|
||||
return '\n';
|
||||
} else {
|
||||
this.indexOfStringForReadChar.set(readerCount+2); // because 2 bytes per char in java
|
||||
char c = this.buffer.readChar();
|
||||
return c;
|
||||
}
|
||||
|
||||
char c = this.buffer.readChar(readerCount);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean wasSequence() {
|
||||
return this.line.length > 0;
|
||||
}
|
||||
}
|
|
@ -7,14 +7,6 @@ public class ObjectPoolFactory {
|
|||
private ObjectPoolFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pool with the max number of available processors as the pool size (padded by 2x as many).
|
||||
*/
|
||||
public static <T> ObjectPool<T> create(PoolableObject<T> poolableObject) {
|
||||
return create(poolableObject, Runtime.getRuntime().availableProcessors() * 2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a pool of the specified size
|
||||
*/
|
||||
|
|
|
@ -11,8 +11,8 @@ public class ProcessProxy extends Thread {
|
|||
|
||||
// when reading from the stdin and outputting to the process
|
||||
public ProcessProxy(String processName, InputStream inputStreamFromConsole, OutputStream outputStreamToProcess) {
|
||||
is = inputStreamFromConsole;
|
||||
os = outputStreamToProcess;
|
||||
this.is = inputStreamFromConsole;
|
||||
this.os = outputStreamToProcess;
|
||||
|
||||
setName(processName);
|
||||
setDaemon(true);
|
||||
|
@ -20,7 +20,7 @@ public class ProcessProxy extends Thread {
|
|||
|
||||
public void close() {
|
||||
try {
|
||||
is.close();
|
||||
this.is.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
@ -32,28 +32,26 @@ public class ProcessProxy extends Thread {
|
|||
// the stream will be closed when the process closes it (usually on exit)
|
||||
int readInt;
|
||||
|
||||
if (os == null) {
|
||||
if (this.os == null) {
|
||||
// just read so it won't block.
|
||||
while ((readInt = is.read()) != -1) {
|
||||
while ((readInt = this.is.read()) != -1) {
|
||||
}
|
||||
} else {
|
||||
while ((readInt = is.read()) != -1) {
|
||||
os.write(readInt);
|
||||
|
||||
// flush the output on new line. Works for windows/linux, since \n is always the last char in the sequence.
|
||||
if (readInt == '\n') {
|
||||
os.flush();
|
||||
}
|
||||
while ((readInt = this.is.read()) != -1) {
|
||||
System.err.println("READ : " + (char)readInt);
|
||||
this.os.write(readInt);
|
||||
// always flush
|
||||
this.os.flush();
|
||||
}
|
||||
}
|
||||
} catch (IOException ignore) {
|
||||
} catch (IllegalArgumentException e) {
|
||||
} finally {
|
||||
try {
|
||||
if (os != null) {
|
||||
os.flush(); // this goes to the console, so we don't want to close it!
|
||||
if (this.os != null) {
|
||||
this.os.flush(); // this goes to the console, so we don't want to close it!
|
||||
}
|
||||
is.close();
|
||||
this.is.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue