Added workarounds for reading System.in (in an unsupported terminal) in
a way that permits unblocking from the (blocking) read() operation, via multiple threads.
This commit is contained in:
parent
c68e0b4675
commit
e9417429cb
@ -15,8 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.console.input;
|
package dorkbox.console.input;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
import dorkbox.util.FastThreadLocal;
|
import dorkbox.util.FastThreadLocal;
|
||||||
import dorkbox.util.bytes.ByteBuffer2;
|
import dorkbox.util.bytes.ByteBuffer2;
|
||||||
@ -25,10 +26,49 @@ import dorkbox.util.bytes.ByteBuffer2;
|
|||||||
public
|
public
|
||||||
class UnsupportedTerminal extends Terminal {
|
class UnsupportedTerminal extends Terminal {
|
||||||
|
|
||||||
private static final char[] newLine;
|
|
||||||
|
private static final char[] NEW_LINE;
|
||||||
|
private static final Thread backgroundReaderThread;
|
||||||
|
|
||||||
|
private static final Object lock = new Object[0];
|
||||||
|
private static String currentConsoleInput = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
newLine = new char[1];
|
NEW_LINE = new char[1];
|
||||||
newLine[0] = '\n';
|
NEW_LINE[0] = '\n';
|
||||||
|
|
||||||
|
// this adopts a different thread + locking to enable reader threads to "unblock" after the blocking read.
|
||||||
|
// http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4514257
|
||||||
|
// https://community.oracle.com/message/5318833#5318833
|
||||||
|
backgroundReaderThread = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!Thread.interrupted()) {
|
||||||
|
currentConsoleInput = null;
|
||||||
|
|
||||||
|
String line = reader.readLine();
|
||||||
|
if (line == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (lock) {
|
||||||
|
currentConsoleInput = line;
|
||||||
|
lock.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
backgroundReaderThread.setDaemon(true);
|
||||||
|
backgroundReaderThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final FastThreadLocal<ByteBuffer2> buffer = new FastThreadLocal<ByteBuffer2>() {
|
private final FastThreadLocal<ByteBuffer2> buffer = new FastThreadLocal<ByteBuffer2>() {
|
||||||
@ -79,51 +119,46 @@ class UnsupportedTerminal extends Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads single character input from the console.
|
* Reads single character input from the console. This is "faked" by reading a line
|
||||||
*
|
*
|
||||||
* @return -1 if no data or problems
|
* @return -1 if no data or problems
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public
|
public
|
||||||
int read() {
|
int read() {
|
||||||
|
int position;
|
||||||
// so, 'readCount' is REALLY the index at which we return letters (until the whole string is returned)
|
// so, 'readCount' is REALLY the index at which we return letters (until the whole string is returned)
|
||||||
ByteBuffer2 buffer = this.buffer.get();
|
ByteBuffer2 buffer = this.buffer.get();
|
||||||
|
buffer.clearSecure();
|
||||||
|
|
||||||
|
// we have to wait for more data.
|
||||||
if (this.readCount.get() == 0) {
|
if (this.readCount.get() == 0) {
|
||||||
// we have to wait for more data.
|
synchronized (lock) {
|
||||||
try {
|
try {
|
||||||
InputStream sysIn = System.in;
|
lock.wait();
|
||||||
int read;
|
} catch (InterruptedException ignored) {
|
||||||
char asChar;
|
|
||||||
buffer.clearSecure();
|
|
||||||
|
|
||||||
while ((read = sysIn.read()) != -1) {
|
|
||||||
asChar = (char) read;
|
|
||||||
if (asChar == '\n') {
|
|
||||||
int position = buffer.position();
|
|
||||||
this.readCount.set(position);
|
|
||||||
if (position == 0) {
|
|
||||||
// only send a NEW LINE if it was the ONLY thing pressed (this is to MOST ACCURATELY simulate single char input
|
|
||||||
return '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.rewind();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
buffer.writeChar(asChar);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentConsoleInput == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char[] chars = currentConsoleInput.toCharArray();
|
||||||
|
buffer.writeChars(chars);
|
||||||
|
position = buffer.position();
|
||||||
|
buffer.rewind();
|
||||||
|
|
||||||
|
this.readCount.set(position);
|
||||||
|
if (position == 0) {
|
||||||
|
// only send a NEW LINE if it was the ONLY thing pressed (this is to MOST ACCURATELY simulate single char input
|
||||||
|
return '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.rewind();
|
||||||
}
|
}
|
||||||
|
|
||||||
// // EACH thread will have it's own count!
|
|
||||||
// if (this.readCount == buffer.position()) {
|
|
||||||
// this.readCount = -1;
|
|
||||||
// return '\n';
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// return buffer.readChar();
|
|
||||||
// }
|
|
||||||
readCount.set(this.readCount.get() - 2); // 2 bytes per char in the stream
|
readCount.set(this.readCount.get() - 2); // 2 bytes per char in the stream
|
||||||
return buffer.readChar();
|
return buffer.readChar();
|
||||||
}
|
}
|
||||||
@ -133,44 +168,38 @@ class UnsupportedTerminal extends Terminal {
|
|||||||
*
|
*
|
||||||
* @return empty char[] if no data
|
* @return empty char[] if no data
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public
|
public
|
||||||
char[] readLineChars() {
|
char[] readLineChars() {
|
||||||
ByteBuffer2 buffer = this.buffer.get();
|
|
||||||
buffer.clearSecure();
|
|
||||||
|
|
||||||
int position = 0;
|
|
||||||
// we have to wait for more data.
|
// we have to wait for more data.
|
||||||
try {
|
synchronized (lock) {
|
||||||
final InputStream sysIn = System.in;
|
try {
|
||||||
int read;
|
lock.wait();
|
||||||
char asChar;
|
} catch (InterruptedException ignored) {
|
||||||
|
|
||||||
while ((read = sysIn.read()) != -1) {
|
|
||||||
asChar = (char) read;
|
|
||||||
|
|
||||||
if (asChar == '\n') {
|
|
||||||
buffer.rewind();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
buffer.writeChar(asChar);
|
|
||||||
position = buffer.position();
|
|
||||||
}
|
}
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position == 0) {
|
if (currentConsoleInput == null) {
|
||||||
|
return EMPTY_LINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char[] chars = currentConsoleInput.toCharArray();
|
||||||
|
int length = chars.length;
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
// only send a NEW LINE if it was the ONLY thing pressed (this is to MOST ACCURATELY simulate single char input
|
// only send a NEW LINE if it was the ONLY thing pressed (this is to MOST ACCURATELY simulate single char input
|
||||||
return newLine;
|
return NEW_LINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
char[] chars = buffer.readChars(position/2); // 2 bytes per char
|
|
||||||
buffer.clearSecure();
|
|
||||||
|
|
||||||
return chars;
|
return chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void close() {
|
void close() {
|
||||||
|
synchronized (lock) {
|
||||||
|
lock.notifyAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user