diff --git a/Dorkbox-Util/src/dorkbox/util/input/InputConsole.java b/Dorkbox-Util/src/dorkbox/util/input/InputConsole.java index a133dcd..548dfe9 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/InputConsole.java +++ b/Dorkbox-Util/src/dorkbox/util/input/InputConsole.java @@ -109,52 +109,70 @@ public class InputConsole { private final Object inputLockSingle = new Object(); private final Object inputLockLine = new Object(); - private final ObjectPool pool = ObjectPoolFactory.create(new ByteBuffer2Poolable()); - private ThreadLocal> threadBuffer = new ThreadLocal>(); - private List> threadBuffersForRead = new CopyOnWriteArrayList>(); + private final ObjectPool pool; + + private ThreadLocal> readBuff = new ThreadLocal>(); + private List> readBuffers = new CopyOnWriteArrayList>(); + private ThreadLocal threadBufferCounter = new ThreadLocal(); + + private ThreadLocal> readLineBuff = new ThreadLocal>(); + private List> readLineBuffers = new CopyOnWriteArrayList>(); - 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 objectPoolHolder = this.readBuff.get(); + ByteBuffer2 buffer = null; + + if (objectPoolHolder == null) { + bufferCounter = 0; + this.threadBufferCounter.set(bufferCounter); + + ObjectPoolHolder 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 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 objectPoolHolder = this.threadBuffer.get(); + ObjectPoolHolder 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 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 objectPoolHolder : this.threadBuffersForRead) { + for (ObjectPoolHolder 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 objectPoolHolder : this.threadBuffersForRead) { + for (ObjectPoolHolder objectPoolHolder : this.readLineBuffers) { ByteBuffer2 buffer = objectPoolHolder.getValue(); buffer.writeChar(asChar); } diff --git a/Dorkbox-Util/src/dorkbox/util/input/Terminal.java b/Dorkbox-Util/src/dorkbox/util/input/Terminal.java index 7c73d97..aa06398 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/Terminal.java +++ b/Dorkbox-Util/src/dorkbox/util/input/Terminal.java @@ -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; - } } diff --git a/Dorkbox-Util/src/dorkbox/util/input/TerminalType.java b/Dorkbox-Util/src/dorkbox/util/input/TerminalType.java index 0be770c..b4d3222 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/TerminalType.java +++ b/Dorkbox-Util/src/dorkbox/util/input/TerminalType.java @@ -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"; diff --git a/Dorkbox-Util/src/dorkbox/util/input/posix/UnixTerminal.java b/Dorkbox-Util/src/dorkbox/util/input/posix/UnixTerminal.java index 8ab0706..2e8c535 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/posix/UnixTerminal.java +++ b/Dorkbox-Util/src/dorkbox/util/input/posix/UnixTerminal.java @@ -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); diff --git a/Dorkbox-Util/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java b/Dorkbox-Util/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java index 41adfc3..9ea0d6f 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java +++ b/Dorkbox-Util/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java @@ -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 indexOfStringForReadChar = new ThreadLocal() { - @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; } } \ No newline at end of file diff --git a/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPoolFactory.java b/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPoolFactory.java index 47a598a..4f25a2d 100644 --- a/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPoolFactory.java +++ b/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPoolFactory.java @@ -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 ObjectPool create(PoolableObject poolableObject) { - return create(poolableObject, Runtime.getRuntime().availableProcessors() * 2); - } - - /** * Creates a pool of the specified size */ diff --git a/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java b/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java index 85b63e7..8d4dd60 100644 --- a/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java +++ b/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java @@ -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) { } }