Fixed readline mixing with previous readchar executions

This commit is contained in:
nathan 2014-10-19 01:10:11 +02:00
parent 227a0d9378
commit 3756df22ab

View File

@ -8,6 +8,7 @@ import java.io.PrintStream;
import java.net.URL; import java.net.URL;
import java.security.CodeSource; import java.security.CodeSource;
import java.security.ProtectionDomain; import java.security.ProtectionDomain;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import org.fusesource.jansi.Ansi; import org.fusesource.jansi.Ansi;
@ -104,13 +105,13 @@ public class InputConsole {
return consoleProxyReader.echo0(); return consoleProxyReader.echo0();
} }
private final Object inputLock = new Object();
private final Object inputLockSingle = new Object(); private final Object inputLockSingle = new Object();
private final Object inputLockLine = new Object(); private final Object inputLockLine = new Object();
private final ObjectPool<ByteBuffer2> pool = ObjectPoolFactory.create(new ByteBuffer2Poolable()); private final ObjectPool<ByteBuffer2> pool = ObjectPoolFactory.create(new ByteBuffer2Poolable());
private ThreadLocal<ObjectPoolHolder<ByteBuffer2>> threadBufferForRead = new ThreadLocal<ObjectPoolHolder<ByteBuffer2>>(); private ThreadLocal<ObjectPoolHolder<ByteBuffer2>> threadBuffer = new ThreadLocal<ObjectPoolHolder<ByteBuffer2>>();
private CopyOnWriteArrayList<ObjectPoolHolder<ByteBuffer2>> threadBuffersForRead = new CopyOnWriteArrayList<ObjectPoolHolder<ByteBuffer2>>(); private List<ObjectPoolHolder<ByteBuffer2>> threadBuffersForRead = new CopyOnWriteArrayList<ObjectPoolHolder<ByteBuffer2>>();
private volatile int readChar = -1; private volatile int readChar = -1;
private final Terminal terminal; private final Terminal terminal;
@ -120,7 +121,7 @@ public class InputConsole {
String type = System.getProperty(TerminalType.TYPE, TerminalType.AUTO).toLowerCase(); String type = System.getProperty(TerminalType.TYPE, TerminalType.AUTO).toLowerCase();
if ("dumb".equals(System.getenv("TERM"))) { if ("dumb".equals(System.getenv("TERM"))) {
type = "none"; type = TerminalType.NONE;
logger.debug("$TERM=dumb; setting type={}", type); logger.debug("$TERM=dumb; setting type={}", type);
} }
@ -205,13 +206,44 @@ public class InputConsole {
return this.terminal.isEchoEnabled(); return this.terminal.isEchoEnabled();
} }
/** return -1 if no data or bunged-up */
private final int read0() {
synchronized (this.inputLockSingle) {
try {
this.inputLockSingle.wait();
} catch (InterruptedException e) {
return -1;
}
}
/** return null if no data */ return this.readChar;
}
/** return empty char[] if no data */
private final char[] readLinePassword0() {
// don't bother in an IDE. it won't work.
boolean echoEnabled = this.terminal.isEchoEnabled();
this.terminal.setEchoEnabled(false);
char[] readLine0 = readLine0();
this.terminal.setEchoEnabled(echoEnabled);
return readLine0;
}
/** return empty char[] if no data */
private final char[] readLine0() { private final char[] readLine0() {
if (this.threadBufferForRead.get() == null) { synchronized (this.inputLock) {
ObjectPoolHolder<ByteBuffer2> holder = this.pool.take(); // empty here, because we don't want to register a readLine WHILE we are still processing
this.threadBufferForRead.set(holder); // the current line info.
this.threadBuffersForRead.add(holder);
// the threadBufferForRead getting added is the part that is important
if (this.threadBuffer.get() == null) {
ObjectPoolHolder<ByteBuffer2> holder = this.pool.take();
this.threadBuffer.set(holder);
this.threadBuffersForRead.add(holder);
} else {
this.threadBuffer.get().getValue().clear();
}
} }
synchronized (this.inputLockLine) { synchronized (this.inputLockLine) {
@ -222,7 +254,7 @@ public class InputConsole {
} }
} }
ObjectPoolHolder<ByteBuffer2> objectPoolHolder = this.threadBufferForRead.get(); ObjectPoolHolder<ByteBuffer2> objectPoolHolder = this.threadBuffer.get();
ByteBuffer2 buffer = objectPoolHolder.getValue(); ByteBuffer2 buffer = objectPoolHolder.getValue();
int len = buffer.position(); int len = buffer.position();
if (len == 0) { if (len == 0) {
@ -237,35 +269,11 @@ public class InputConsole {
this.threadBuffersForRead.remove(objectPoolHolder); this.threadBuffersForRead.remove(objectPoolHolder);
this.pool.release(objectPoolHolder); this.pool.release(objectPoolHolder);
this.threadBufferForRead.set(null); this.threadBuffer.set(null);
return readChars; return readChars;
} }
/** return null if no data */
private final char[] readLinePassword0() {
// don't bother in an IDE. it won't work.
boolean echoEnabled = this.terminal.isEchoEnabled();
this.terminal.setEchoEnabled(false);
char[] readLine0 = readLine0();
this.terminal.setEchoEnabled(echoEnabled);
return readLine0;
}
/** return -1 if no data */
private final int read0() {
synchronized (this.inputLockSingle) {
try {
this.inputLockSingle.wait();
} catch (InterruptedException e) {
return -1;
}
}
return this.readChar;
}
/** /**
* releases any thread still waiting. * releases any thread still waiting.
*/ */
@ -292,83 +300,87 @@ public class InputConsole {
// don't type ; in a bash shell, it quits everything // don't type ; in a bash shell, it quits everything
// \n is replaced by \r in unix terminal? // \n is replaced by \r in unix terminal?
while ((typedChar = this.terminal.read()) != -1) { while ((typedChar = this.terminal.read()) != -1) {
asChar = (char) typedChar; synchronized (this.inputLock) {
// don't let anyone add a new reader while we are still processing the current actions
if (logger2.isTraceEnabled()) { asChar = (char) typedChar;
logger2.trace("READ: {} ({})", asChar, typedChar);
}
// notify everyone waiting for a character. if (logger2.isTraceEnabled()) {
synchronized (this.inputLockSingle) { logger2.trace("READ: {} ({})", asChar, typedChar);
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();
} }
}
// if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed. // notify everyone waiting for a character.
if (typedChar == '\b') { synchronized (this.inputLockSingle) {
int position = 0; if (this.terminal.wasSequence() && typedChar == '\n') {
// don't want to forward \n if it was a part of a sequence in the unsupported terminal
// clear ourself + one extra. // the JIT will short-cut this out if we are not the unsupported terminal
if (ansiEnabled) { } else {
for (ObjectPoolHolder<ByteBuffer2> objectPoolHolder : this.threadBuffersForRead) { this.readChar = typedChar;
ByteBuffer2 buffer = objectPoolHolder.getValue(); this.inputLockSingle.notifyAll();
// size of the buffer BEFORE our backspace was typed
int length = buffer.position();
int amtToOverwrite = 2 * 2; // backspace is always 2 chars (^?) * 2 because it's bytes
if (length > 1) {
char charAt = buffer.readChar(length-2);
amtToOverwrite += getPrintableCharacters(charAt);
// delete last item in our buffer
length -= 2;
buffer.setPosition(length);
// now figure out where the cursor is really at.
// this is more memory friendly than buf.toString.length
for (int i=0;i<length;i+=2) {
charAt = buffer.readChar(i);
position += getPrintableCharacters(charAt);
}
position++;
}
char[] overwrite = new char[amtToOverwrite];
char c = ' ';
for (int i=0;i<amtToOverwrite;i++) {
overwrite[i] = c;
}
// move back however many, over write, then go back again
out.print(ansi.cursorToColumn(position));
out.print(overwrite);
out.print(ansi.cursorToColumn(position));
out.flush();
} }
} }
// read-line will ignore backspace // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed.
continue; if (typedChar == '\b') {
} int position = 0;
// ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows) // clear ourself + one extra.
if (asChar == '\n') { if (ansiEnabled) {
synchronized (this.inputLockLine) { for (ObjectPoolHolder<ByteBuffer2> objectPoolHolder : this.threadBuffersForRead) {
this.inputLockLine.notifyAll(); ByteBuffer2 buffer = objectPoolHolder.getValue();
// size of the buffer BEFORE our backspace was typed
int length = buffer.position();
int amtToOverwrite = 2 * 2; // backspace is always 2 chars (^?) * 2 because it's bytes
if (length > 1) {
char charAt = buffer.readChar(length-2);
amtToOverwrite += getPrintableCharacters(charAt);
// delete last item in our buffer
length -= 2;
buffer.setPosition(length);
// now figure out where the cursor is really at.
// this is more memory friendly than buf.toString.length
for (int i=0;i<length;i+=2) {
charAt = buffer.readChar(i);
position += getPrintableCharacters(charAt);
}
position++;
}
char[] overwrite = new char[amtToOverwrite];
char c = ' ';
for (int i=0;i<amtToOverwrite;i++) {
overwrite[i] = c;
}
// move back however many, over write, then go back again
out.print(ansi.cursorToColumn(position));
out.print(overwrite);
out.print(ansi.cursorToColumn(position));
out.flush();
}
}
// read-line will ignore backspace
continue;
} }
} else {
// our windows console PREVENTS us from returning '\r' (it truncates '\r\n', and returns just '\n')
// only append if we are not a new line. // ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows)
for (ObjectPoolHolder<ByteBuffer2> objectPoolHolder : this.threadBuffersForRead) { if (asChar == '\n') {
ByteBuffer2 buffer = objectPoolHolder.getValue(); synchronized (this.inputLockLine) {
buffer.writeChar(asChar); this.inputLockLine.notifyAll();
}
} 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) {
ByteBuffer2 buffer = objectPoolHolder.getValue();
buffer.writeChar(asChar);
}
} }
} }
} }