Cleaned up input console to use my own growing-byte buffers. Also made one that is fast (but unsynchronized)

This commit is contained in:
nathan 2014-10-17 00:10:49 +02:00
parent 24efc75f7b
commit 890d73e8aa
4 changed files with 549 additions and 172 deletions

View File

@ -34,23 +34,23 @@ public class ByteBuffer2 {
public ByteBuffer2(byte[] bytes) { public ByteBuffer2(byte[] bytes) {
this.bytes = bytes; this.bytes = bytes;
clear(); clear();
position = bytes.length; this.position = bytes.length;
} }
public byte getByte() { public byte getByte() {
if (position > limit) { if (this.position > this.limit) {
throw new BufferUnderflowException(); throw new BufferUnderflowException();
} }
return bytes[position++]; return this.bytes[this.position++];
} }
public byte getByte(int i) { public byte getByte(int i) {
if (i > limit) { if (i > this.limit) {
throw new BufferUnderflowException(); throw new BufferUnderflowException();
} }
return bytes[i]; return this.bytes[i];
} }
public void getBytes(byte[] buffer) { public void getBytes(byte[] buffer) {
@ -62,12 +62,12 @@ public class ByteBuffer2 {
} }
public void getBytes(byte[] buffer, int offset, int length) { public void getBytes(byte[] buffer, int offset, int length) {
if (position + length - offset > limit) { if (this.position + length - offset > this.limit) {
throw new BufferUnderflowException(); throw new BufferUnderflowException();
} }
System.arraycopy(bytes, position, buffer, 0, length-offset); System.arraycopy(this.bytes, this.position, buffer, 0, length-offset);
position += length-offset; this.position += length-offset;
} }
/** /**
@ -76,18 +76,18 @@ public class ByteBuffer2 {
* NOT PROTECTED * NOT PROTECTED
*/ */
private final void _put(byte b) { private final void _put(byte b) {
bytes[position++] = b; this.bytes[this.position++] = b;
} }
/** NOT PROTECTED! */ /** NOT PROTECTED! */
private final void checkBuffer(int threshold) { private final void checkBuffer(int threshold) {
if (bytes.length < threshold) { if (this.bytes.length < threshold) {
byte[] t = new byte[threshold]; byte[] t = new byte[threshold];
// grow at back of array // grow at back of array
System.arraycopy(bytes, 0, t, 0, bytes.length); System.arraycopy(this.bytes, 0, t, 0, this.bytes.length);
limit = t.length; this.limit = t.length;
bytes = t; this.bytes = t;
} }
} }
@ -101,16 +101,16 @@ public class ByteBuffer2 {
} }
public final synchronized ByteBuffer2 putBytes(byte[] src, int offset, int length) { public final synchronized ByteBuffer2 putBytes(byte[] src, int offset, int length) {
checkBuffer(position + length - offset); checkBuffer(this.position + length - offset);
System.arraycopy(src, offset, bytes, position, length); System.arraycopy(src, offset, this.bytes, this.position, length);
position += length; this.position += length;
return this; return this;
} }
public final synchronized ByteBuffer2 putByte(byte b) { public final synchronized ByteBuffer2 putByte(byte b) {
checkBuffer(position + 1); checkBuffer(this.position + 1);
_put(b); _put(b);
return this; return this;
@ -122,7 +122,7 @@ public class ByteBuffer2 {
} }
public final synchronized void putChar(char c) { public final synchronized void putChar(char c) {
checkBuffer(position + 2); checkBuffer(this.position + 2);
putBytes(BigEndian.Char_.toBytes(c)); putBytes(BigEndian.Char_.toBytes(c));
} }
@ -131,8 +131,19 @@ public class ByteBuffer2 {
return BigEndian.Char_.fromBytes(getByte(), getByte()); return BigEndian.Char_.fromBytes(getByte(), getByte());
} }
public final char getChar(int i) {
return BigEndian.Char_.fromBytes(getByte(i++), getByte(i));
}
public void getChars(int srcStart, int srcLength, char[] dest, int destStart) {
for (int i=srcStart;i<srcLength;i+=2) {
char c = BigEndian.Char_.fromBytes(getByte(), getByte());
dest[destStart++] = c;
}
}
public final synchronized ByteBuffer2 putShort(short x) { public final synchronized ByteBuffer2 putShort(short x) {
checkBuffer(position + 2); checkBuffer(this.position + 2);
putBytes(BigEndian.Short_.toBytes(x)); putBytes(BigEndian.Short_.toBytes(x));
@ -143,21 +154,26 @@ public class ByteBuffer2 {
return BigEndian.Short_.fromBytes(getByte(), getByte()); return BigEndian.Short_.fromBytes(getByte(), getByte());
} }
public final short getShort(int i) {
return BigEndian.Short_.fromBytes(getByte(i++), getByte(i));
}
public final synchronized void putInt(int x) { public final synchronized void putInt(int x) {
checkBuffer(position + 4); checkBuffer(this.position + 4);
putBytes(BigEndian.Int_.toBytes(x)); putBytes(BigEndian.Int_.toBytes(x));
} }
public final synchronized int getInt() { public final synchronized int getInt() {
byte b3 = getByte(); return BigEndian.Int_.fromBytes(getByte(), getByte(), getByte(), getByte());
byte b2 = getByte(); }
byte b1 = getByte();
return BigEndian.Int_.fromBytes(getByte(), b1, b2, b3); public final int getInt(int i) {
return BigEndian.Int_.fromBytes(getByte(i++), getByte(i++), getByte(i++), getByte(i++));
} }
public final synchronized void putLong(long x) { public final synchronized void putLong(long x) {
checkBuffer(position + 8); checkBuffer(this.position + 8);
putBytes(BigEndian.Long_.toBytes(x)); putBytes(BigEndian.Long_.toBytes(x));
} }
@ -166,21 +182,25 @@ public class ByteBuffer2 {
return BigEndian.Long_.fromBytes(getByte(), getByte(), getByte(), getByte(), getByte(), getByte(), getByte(), getByte()); return BigEndian.Long_.fromBytes(getByte(), getByte(), getByte(), getByte(), getByte(), getByte(), getByte(), getByte());
} }
public final long getLong(int i) {
return BigEndian.Long_.fromBytes(getByte(i++), getByte(i++), getByte(i++), getByte(i++), getByte(i++), getByte(i++), getByte(i++), getByte(i++));
}
/** /**
* Returns the backing array of this buffer * Returns the backing array of this buffer
*/ */
public byte[] array() { public byte[] array() {
return bytes; return this.bytes;
} }
/** /**
* Returns a copy of the backing array of this buffer * Returns a copy of the backing array of this buffer
*/ */
public final synchronized byte[] arrayCopy() { public final synchronized byte[] arrayCopy() {
int length = bytes.length - position; int length = this.bytes.length - this.position;
byte[] b = new byte[length]; byte[] b = new byte[length];
System.arraycopy(bytes, position, b, 0, length); System.arraycopy(this.bytes, this.position, b, 0, length);
return b; return b;
} }
@ -188,20 +208,20 @@ public class ByteBuffer2 {
* Returns this buffer's position. * Returns this buffer's position.
*/ */
public int position() { public int position() {
return position; return this.position;
} }
/** /**
* Sets this buffer's position. * Sets this buffer's position.
*/ */
public final synchronized ByteBuffer2 position(int position) { public final synchronized ByteBuffer2 position(int position) {
if (position > bytes.length || position < 0) { if (position > this.bytes.length || position < 0) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
this.position = position; this.position = position;
if (mark > position) { if (this.mark > position) {
mark = -1; this.mark = -1;
} }
return this; return this;
@ -212,7 +232,7 @@ public class ByteBuffer2 {
* limit. * limit.
*/ */
public final synchronized int remaining() { public final synchronized int remaining() {
return limit - position; return this.limit - this.position;
} }
/** /**
@ -220,7 +240,7 @@ public class ByteBuffer2 {
* the limit. * the limit.
*/ */
public final synchronized boolean hasRemaining() { public final synchronized boolean hasRemaining() {
return position < limit; return this.position < this.limit;
} }
/** /**
@ -228,11 +248,11 @@ public class ByteBuffer2 {
*/ */
public final synchronized void limit(int limit) { public final synchronized void limit(int limit) {
this.limit = limit; this.limit = limit;
if (position > limit) { if (this.position > limit) {
position = limit; this.position = limit;
} }
if (mark > limit) { if (this.mark > limit) {
mark = -1; this.mark = -1;
} }
} }
@ -240,14 +260,14 @@ public class ByteBuffer2 {
* Returns this buffer's limit. * Returns this buffer's limit.
*/ */
public int limit() { public int limit() {
return limit; return this.limit;
} }
/** /**
* Returns this buffer's capacity. * Returns this buffer's capacity.
*/ */
public int capacity() { public int capacity() {
return bytes.length; return this.bytes.length;
} }
/** /**
@ -260,8 +280,8 @@ public class ByteBuffer2 {
* can be followed immediately by an invocation of another relative put method. * can be followed immediately by an invocation of another relative put method.
*/ */
public final synchronized void compact() { public final synchronized void compact() {
mark = -1; this.mark = -1;
System.arraycopy(bytes, position, bytes, 0, remaining()); System.arraycopy(this.bytes, this.position, this.bytes, 0, remaining());
position(remaining()); position(remaining());
limit(capacity()); limit(capacity());
@ -275,9 +295,9 @@ public class ByteBuffer2 {
* discarded. * discarded.
*/ */
public final synchronized void flip() { public final synchronized void flip() {
limit = position; this.limit = this.position;
position = 0; this.position = 0;
mark = -1; this.mark = -1;
} }
/** /**
@ -285,9 +305,9 @@ public class ByteBuffer2 {
* the capacity, and the mark is discarded. * the capacity, and the mark is discarded.
*/ */
public final synchronized void clear() { public final synchronized void clear() {
position = 0; this.position = 0;
limit = capacity(); this.limit = capacity();
mark = -1; this.mark = -1;
} }
/** /**
@ -295,15 +315,15 @@ public class ByteBuffer2 {
* discarded. * discarded.
*/ */
public final synchronized void rewind() { public final synchronized void rewind() {
position = 0; this.position = 0;
mark = -1; this.mark = -1;
} }
/** /**
* Sets this buffer's mark at its position. * Sets this buffer's mark at its position.
*/ */
public final synchronized void mark() { public final synchronized void mark() {
mark = position; this.mark = this.position;
} }
/** /**
@ -313,6 +333,6 @@ public class ByteBuffer2 {
* value. </p> * value. </p>
*/ */
public void reset() { public void reset() {
position = mark; this.position = this.mark;
} }
} }

View File

@ -0,0 +1,338 @@
package dorkbox.util.bytes;
import java.nio.BufferUnderflowException;
/**
* Cleanroom implementation of a self-growing bytebuffer. NOT SYNCHRONIZED!
*/
public class ByteBuffer2Fast {
private byte[] bytes;
private int position = 0;
private int mark = -1;
private int limit = 0;
public static ByteBuffer2Fast wrap(byte[] buffer) {
return new ByteBuffer2Fast(buffer);
}
public static ByteBuffer2Fast allocate(int capacity) {
ByteBuffer2Fast byteBuffer2 = new ByteBuffer2Fast(new byte[capacity]);
byteBuffer2.clear();
return byteBuffer2;
}
public ByteBuffer2Fast() {
this(0);
}
public ByteBuffer2Fast(int size) {
this(new byte[size]);
}
public ByteBuffer2Fast(byte[] bytes) {
this.bytes = bytes;
clear();
this.position = bytes.length;
}
public byte getByte() {
if (this.position > this.limit) {
throw new BufferUnderflowException();
}
return this.bytes[this.position++];
}
public byte getByte(int i) {
if (i > this.limit) {
throw new BufferUnderflowException();
}
return this.bytes[i];
}
public void getBytes(byte[] buffer) {
getBytes(buffer, 0, buffer.length);
}
public void getBytes(byte[] buffer, int length) {
getBytes(buffer, 0, length);
}
public void getBytes(byte[] buffer, int offset, int length) {
if (this.position + length - offset > this.limit) {
throw new BufferUnderflowException();
}
System.arraycopy(this.bytes, this.position, buffer, 0, length-offset);
this.position += length-offset;
}
/**
* MUST call checkBuffer before calling this!
*
* NOT PROTECTED
*/
private final void _put(byte b) {
this.bytes[this.position++] = b;
}
/** NOT PROTECTED! */
private final void checkBuffer(int threshold) {
if (this.bytes.length < threshold) {
byte[] t = new byte[threshold];
// grow at back of array
System.arraycopy(this.bytes, 0, t, 0, this.bytes.length);
this.limit = t.length;
this.bytes = t;
}
}
public final void put(ByteBuffer2Fast buffer) {
putBytes(buffer.array(), buffer.position, buffer.limit);
buffer.position = buffer.limit;
}
public final ByteBuffer2Fast putBytes(byte[] src) {
return putBytes(src, 0, src.length);
}
public final ByteBuffer2Fast putBytes(byte[] src, int offset, int length) {
checkBuffer(this.position + length - offset);
System.arraycopy(src, offset, this.bytes, this.position, length);
this.position += length;
return this;
}
public final ByteBuffer2Fast putByte(byte b) {
checkBuffer(this.position + 1);
_put(b);
return this;
}
public final void putByte(int position, byte b) {
this.position = position;
putByte(b);
}
public final void putChar(char c) {
checkBuffer(this.position + 2);
putBytes(BigEndian.Char_.toBytes(c));
}
public final char getChar() {
return BigEndian.Char_.fromBytes(getByte(), getByte());
}
public final char getChar(int i) {
return BigEndian.Char_.fromBytes(getByte(i++), getByte(i));
}
public void getChars(int srcStart, int srcLength, char[] dest, int destStart) {
for (int i=srcStart;i<srcLength;i+=2) {
char c = BigEndian.Char_.fromBytes(getByte(i), getByte(i+1));
dest[destStart++] = c;
}
}
public final ByteBuffer2Fast putShort(short x) {
checkBuffer(this.position + 2);
putBytes(BigEndian.Short_.toBytes(x));
return this;
}
public final short getShort() {
return BigEndian.Short_.fromBytes(getByte(), getByte());
}
public final short getShort(int i) {
return BigEndian.Short_.fromBytes(getByte(i++), getByte(i));
}
public final void putInt(int x) {
checkBuffer(this.position + 4);
putBytes(BigEndian.Int_.toBytes(x));
}
public final int getInt() {
return BigEndian.Int_.fromBytes(getByte(), getByte(), getByte(), getByte());
}
public final int getInt(int i) {
return BigEndian.Int_.fromBytes(getByte(i++), getByte(i++), getByte(i++), getByte(i++));
}
public final void putLong(long x) {
checkBuffer(this.position + 8);
putBytes(BigEndian.Long_.toBytes(x));
}
public final long getLong() {
return BigEndian.Long_.fromBytes(getByte(), getByte(), getByte(), getByte(), getByte(), getByte(), getByte(), getByte());
}
public final long getLong(int i) {
return BigEndian.Long_.fromBytes(getByte(i++), getByte(i++), getByte(i++), getByte(i++), getByte(i++), getByte(i++), getByte(i++), getByte(i++));
}
/**
* Returns the backing array of this buffer
*/
public byte[] array() {
return this.bytes;
}
/**
* Returns a copy of the backing array of this buffer
*/
public final byte[] arrayCopy() {
int length = this.bytes.length - this.position;
byte[] b = new byte[length];
System.arraycopy(this.bytes, this.position, b, 0, length);
return b;
}
/**
* Returns this buffer's position.
*/
public int position() {
return this.position;
}
/**
* Sets this buffer's position.
*/
public final ByteBuffer2Fast position(int position) {
if (position > this.bytes.length || position < 0) {
throw new IllegalArgumentException();
}
this.position = position;
if (this.mark > position) {
this.mark = -1;
}
return this;
}
/**
* Returns the number of elements between the current position and the
* limit.
*/
public final int remaining() {
return this.limit - this.position;
}
/**
* Tells whether there are any elements between the current position and
* the limit.
*/
public final boolean hasRemaining() {
return this.position < this.limit;
}
/**
* Sets this buffer's limit.
*/
public final void limit(int limit) {
this.limit = limit;
if (this.position > limit) {
this.position = limit;
}
if (this.mark > limit) {
this.mark = -1;
}
}
/**
* Returns this buffer's limit.
*/
public int limit() {
return this.limit;
}
/**
* Returns this buffer's capacity.
*/
public int capacity() {
return this.bytes.length;
}
/**
* The bytes between the buffer's current position and its limit, if any, are copied to the beginning of the buffer.
* That is, the byte at index p = position() is copied to index zero, the byte at index p + 1 is copied to index one,
* and so forth until the byte at index limit() - 1 is copied to index n = limit() - 1 - p. The buffer's position is
* then set to n+1 and its limit is set to its capacity. The mark, if defined, is discarded.
*
* The buffer's position is set to the number of bytes copied, rather than to zero, so that an invocation of this method
* can be followed immediately by an invocation of another relative put method.
*/
public final void compact() {
this.mark = -1;
System.arraycopy(this.bytes, this.position, this.bytes, 0, remaining());
position(remaining());
limit(capacity());
}
/**
* Readies the buffer for reading.
*
* Flips this buffer. The limit is set to the current position and then
* the position is set to zero. If the mark is defined then it is
* discarded.
*/
public final void flip() {
this.limit = this.position;
this.position = 0;
this.mark = -1;
}
/**
* Clears this buffer. The position is set to zero, the limit is set to
* the capacity, and the mark is discarded.
*/
public final void clear() {
this.position = 0;
this.limit = capacity();
this.mark = -1;
}
/**
* Rewinds this buffer. The position is set to zero and the mark is
* discarded.
*/
public final void rewind() {
this.position = 0;
this.mark = -1;
}
/**
* Sets this buffer's mark at its position.
*/
public final void mark() {
this.mark = this.position;
}
/**
* Resets this buffer's position to the previously-marked position.
*
* <p> Invoking this method neither changes nor discards the mark's
* value. </p>
*/
public void reset() {
this.position = this.mark;
}
}

View File

@ -5,18 +5,21 @@ import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintStream;
import java.io.Reader; import java.io.Reader;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.CodeSource; import java.security.CodeSource;
import java.security.ProtectionDomain; import java.security.ProtectionDomain;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.CopyOnWriteArrayList;
import org.fusesource.jansi.Ansi; import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiConsole;
import org.slf4j.Logger; import org.slf4j.Logger;
import dorkbox.util.OS; import dorkbox.util.OS;
import dorkbox.util.Sys;
import dorkbox.util.bytes.ByteBuffer2Fast;
import dorkbox.util.input.posix.UnixTerminal; import dorkbox.util.input.posix.UnixTerminal;
import dorkbox.util.input.unsupported.UnsupportedTerminal; import dorkbox.util.input.unsupported.UnsupportedTerminal;
import dorkbox.util.input.windows.WindowsTerminal; import dorkbox.util.input.windows.WindowsTerminal;
@ -36,20 +39,31 @@ public class InputConsole {
{ {
AnsiConsole.systemInstall(); AnsiConsole.systemInstall();
Thread consoleThread = new Thread(new Runnable() {
@Override
public void run() {
consoleProxyReader.run();
}
});
consoleThread.setDaemon(true);
consoleThread.setName("Console Input Reader");
consoleThread.start();
// has to be NOT DAEMON thread, since it must run before the app closes.
// don't forget we have to shut down the ansi console as well // don't forget we have to shut down the ansi console as well
// alternatively, shut everything down when the JVM closes. // alternatively, shut everything down when the JVM closes.
Runtime.getRuntime().addShutdownHook(new Thread() { Thread shutdownThread = new Thread() {
@Override @Override
public void run() { public void run() {
AnsiConsole.systemUninstall(); AnsiConsole.systemUninstall();
InputConsole.destroy();
}
});
}
// called by our shutdown thread consoleProxyReader.shutdown0();
private static final void destroy() { }
consoleProxyReader.destroy0(); };
shutdownThread.setName("Console Input Shutdown");
Runtime.getRuntime().addShutdownHook(shutdownThread);
} }
/** return null if no data */ /** return null if no data */
@ -94,9 +108,17 @@ public class InputConsole {
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 AtomicBoolean isRunning = new AtomicBoolean(false); private ThreadLocal<ByteBuffer2Fast> threadBufferForRead = new ThreadLocal<ByteBuffer2Fast>();
private AtomicBoolean isInShutdown = new AtomicBoolean(false); private CopyOnWriteArrayList<ByteBuffer2Fast> threadBuffersForRead = new CopyOnWriteArrayList<ByteBuffer2Fast>();
private volatile char[] readLine = null;
ThreadLocal<Integer> indexOfStringForReadChar = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return -1;
}
};
private volatile int readChar = -1; private volatile int readChar = -1;
private final boolean unsupported; private final boolean unsupported;
@ -119,7 +141,7 @@ public class InputConsole {
logger.debug("Creating terminal; type={}", type); logger.debug("Creating terminal; type={}", type);
Terminal t; Terminal t;
try { try {
if (type.equals(TerminalType.UNIX)) { if (type.equals(TerminalType.UNIX)) {
t = new UnixTerminal(); t = new UnixTerminal();
@ -182,36 +204,8 @@ public class InputConsole {
logger.debug("Created Terminal: {}", this.terminal); logger.debug("Created Terminal: {}", this.terminal);
} }
/** // called when the JVM is shutting down.
* make sure the input console reader thread is started. private void shutdown0() {
*/
private void startInputConsole() {
// protected by atomic!
if (!this.isRunning.compareAndSet(false, true) || this.isInShutdown.get()) {
return;
}
Thread consoleThread = new Thread(new Runnable() {
@Override
public void run() {
consoleProxyReader.run();
}
});
consoleThread.setDaemon(true);
consoleThread.setName("Console Input Reader");
consoleThread.start();
}
private void destroy0() {
// Don't change this, because we don't want to enable reading, etc from this once it's destroyed.
// so we pretend that it's still running
// isRunning.set(false);
if (this.isInShutdown.compareAndSet(true, true)) {
return;
}
synchronized (this.inputLockSingle) { synchronized (this.inputLockSingle) {
this.inputLockSingle.notifyAll(); this.inputLockSingle.notifyAll();
} }
@ -224,7 +218,8 @@ public class InputConsole {
InputConsole inputConsole = InputConsole.this; InputConsole inputConsole = InputConsole.this;
inputConsole.terminal.restore(); inputConsole.terminal.restore();
inputConsole.reader.close(); // this will 'hang' our shutdown, and honestly, who cares? We're shutting down anyways.
// inputConsole.reader.close(); // hangs on shutdown
} catch (IOException ignored) { } catch (IOException ignored) {
ignored.printStackTrace(); ignored.printStackTrace();
} }
@ -241,7 +236,11 @@ public class InputConsole {
/** return null if no data */ /** return null if no data */
private final char[] readLine0() { private final char[] readLine0() {
startInputConsole(); if (this.threadBufferForRead.get() == null) {
ByteBuffer2Fast buffer = ByteBuffer2Fast.allocate(0);
this.threadBufferForRead.set(buffer);
this.threadBuffersForRead.add(buffer);
}
synchronized (this.inputLockLine) { synchronized (this.inputLockLine) {
try { try {
@ -250,34 +249,53 @@ public class InputConsole {
return emptyLine; return emptyLine;
} }
} }
return this.readLine;
ByteBuffer2Fast stringBuffer = this.threadBufferForRead.get();
int len = stringBuffer.position();
if (len == 0) {
return emptyLine;
}
char[] chars = new char[len/2]; // because 2 chars is 1 bytes
stringBuffer.getChars(0, len, chars, 0);
// dump the chars in the buffer (safer for passwords, etc)
stringBuffer.clear();
stringBuffer.putBytes(new byte[0]);
this.threadBufferForRead.set(null);
this.threadBuffersForRead.remove(stringBuffer); // TODO: use object pool!
return chars;
} }
/** return null if no data */ /** return null if no data */
private final char[] readLinePassword0() { private final char[] readLinePassword0() {
// don't bother in an IDE. it won't work. if (this.threadBufferForRead.get() == null) {
return readLine0(); ByteBuffer2Fast buffer = ByteBuffer2Fast.allocate(0);
} this.threadBufferForRead.set(buffer);
this.threadBuffersForRead.add(buffer);
ThreadLocal<Integer> indexOfStringForReadChar = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return -1;
} }
};
// 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 */ /** return -1 if no data */
private final int read0() { private final int read0() {
startInputConsole();
// if we are reading data (because we are in IDE mode), we want to return ALL // if we are reading data (because we are in IDE mode), we want to return ALL
// the chars of the line! // the chars of the line!
// so, readChar is REALLY the index at which we return letters (until the whole string is returned // so, readChar is REALLY the index at which we return letters (until the whole string is returned
if (this.unsupported) { if (this.unsupported) {
int integer = this.indexOfStringForReadChar.get(); int readerCount = this.indexOfStringForReadChar.get();
if (integer == -1) { if (readerCount == -1) {
// we have to wait for more data. // we have to wait for more data.
synchronized (this.inputLockLine) { synchronized (this.inputLockLine) {
try { try {
@ -285,24 +303,23 @@ public class InputConsole {
} catch (InterruptedException e) { } catch (InterruptedException e) {
return -1; return -1;
} }
integer = 0; readerCount = 0;
this.indexOfStringForReadChar.set(0); this.indexOfStringForReadChar.set(0);
} }
} }
// EACH thread will have it's own count!
char[] readLine2 = this.readLine;
if (readLine2 == null) { // EACH thread will have it's own count!
return -1; ByteBuffer2Fast stringBuffer = this.threadBufferForRead.get();
} else if (integer == readLine2.length) {
if (readerCount == stringBuffer.position()) {
this.indexOfStringForReadChar.set(-1); this.indexOfStringForReadChar.set(-1);
return '\n'; return '\n';
} else { } else {
this.indexOfStringForReadChar.set(integer+1); this.indexOfStringForReadChar.set(readerCount+1);
} }
char c = readLine2[integer]; char c = stringBuffer.getChar(readerCount);
return c; return c;
} }
else { else {
@ -339,19 +356,30 @@ public class InputConsole {
// it just waits until \n until it triggers // it just waits until \n until it triggers
if (this.unsupported) { if (this.unsupported) {
BufferedReader reader = (BufferedReader) this.reader; BufferedReader reader = (BufferedReader) this.reader;
String line = null;
char[] readLine = null;
try { try {
while ((this.readLine = reader.readLine().toCharArray()) != null) { while ((line = reader.readLine()) != null) {
readLine = line.toCharArray();
// notify everyone waiting for a line of text. // notify everyone waiting for a line of text.
synchronized (this.inputLockSingle) { synchronized (this.inputLockSingle) {
if (this.readLine.length > 0) { if (readLine.length > 0) {
this.readChar = this.readLine[0]; this.readChar = readLine[0];
} else { } else {
this.readChar = -1; this.readChar = -1;
} }
this.inputLockSingle.notifyAll(); this.inputLockSingle.notifyAll();
} }
synchronized (this.inputLockLine) { synchronized (this.inputLockLine) {
byte[] charToBytes = Sys.charToBytes(readLine);
for (ByteBuffer2Fast buffer : this.threadBuffersForRead) {
buffer.clear();
buffer.putBytes(charToBytes);
}
this.inputLockLine.notifyAll(); this.inputLockLine.notifyAll();
} }
} }
@ -360,12 +388,13 @@ public class InputConsole {
} }
} }
else { else {
// from a 'regular' console
try { try {
final boolean ansiEnabled = Ansi.isEnabled(); final boolean ansiEnabled = Ansi.isEnabled();
Ansi ansi = Ansi.ansi(); Ansi ansi = Ansi.ansi();
PrintStream out = AnsiConsole.out;
int typedChar; int typedChar;
StringBuilder buf = new StringBuilder();
// 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?
@ -388,65 +417,57 @@ public class InputConsole {
// clear ourself + one extra. // clear ourself + one extra.
if (ansiEnabled) { if (ansiEnabled) {
// size of the buffer BEFORE our backspace was typed for (ByteBuffer2Fast buffer : this.threadBuffersForRead) {
int length = buf.length(); // size of the buffer BEFORE our backspace was typed
int amtToOverwrite = 2; // backspace is always 2 chars (^?) int length = buffer.position();
int amtToOverwrite = 2 * 2; // backspace is always 2 chars (^?) * 2 because it's bytes
if (length > 1) { if (length > 1) {
char charAt = buf.charAt(length-1); char charAt = buffer.getChar(length-2);
amtToOverwrite += getPrintableCharacters(charAt); amtToOverwrite += getPrintableCharacters(charAt);
// delete last item in our buffer // delete last item in our buffer
buf.setLength(--length); length -= 2;
buffer.position(length);
// now figure out where the cursor is really at. // now figure out where the cursor is really at.
// this is more memory friendly than buf.toString.length // this is more memory friendly than buf.toString.length
for (int i=0;i<length;i++) { for (int i=0;i<length;i+=2) {
charAt = buf.charAt(i); charAt = buffer.getChar(i);
position += getPrintableCharacters(charAt); position += getPrintableCharacters(charAt);
}
position++;
} }
position++;
}
char[] overwrite = new char[amtToOverwrite]; char[] overwrite = new char[amtToOverwrite];
char c = ' '; char c = ' ';
for (int i=0;i<amtToOverwrite;i++) { for (int i=0;i<amtToOverwrite;i++) {
overwrite[i] = c; overwrite[i] = c;
} }
// move back however many, over write, then go back again // move back however many, over write, then go back again
AnsiConsole.out.print(ansi.cursorToColumn(position)); out.print(ansi.cursorToColumn(position));
AnsiConsole.out.print(overwrite); out.print(overwrite);
AnsiConsole.out.print(ansi.cursorToColumn(position)); out.print(ansi.cursorToColumn(position));
AnsiConsole.out.flush(); out.flush();
}
} }
// readline will ignore backspace // read-line will ignore backspace
continue; continue;
} }
// ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows) // ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows)
if (asChar == '\n') { if (asChar == '\n') {
int length = buf.length();
synchronized (this.inputLockLine) { synchronized (this.inputLockLine) {
if (length > 0) {
this.readLine = new char[length];
buf.getChars(0, length, this.readLine, 0);
} else {
this.readLine = emptyLine;
}
this.inputLockLine.notifyAll(); this.inputLockLine.notifyAll();
} }
// dump the characters in the backing array (slightly safer for passwords when using this method)
if (length > 0) {
buf.delete(0, buf.length());
}
} else if (asChar != '\r') { } else if (asChar != '\r') {
// only append if we are not a new line. // only append if we are not a new line.
buf.append(asChar); for (ByteBuffer2Fast buffer : this.threadBuffersForRead) {
buffer.putChar(asChar);
}
} }
} }
} catch (IOException ignored) { } catch (IOException ignored) {
@ -455,11 +476,11 @@ public class InputConsole {
} }
/** /**
+ * Get the default encoding. Will first look at the LC_CTYPE environment variable, then the input.encoding * Get the default encoding. Will first look at the LC_CTYPE environment variable, then the input.encoding
+ * system property, then the default charset according to the JVM. * system property, then the default charset according to the JVM.
+ * *
+ * @return The default encoding to use when none is specified. * @return The default encoding to use when none is specified.
+ */ */
public static String getEncoding() { public static String getEncoding() {
// LC_CTYPE is usually in the form en_US.UTF-8 // LC_CTYPE is usually in the form en_US.UTF-8
String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE")); String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE"));
@ -481,9 +502,8 @@ public class InputConsole {
String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1); String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1);
if (encodingAndModifier.indexOf('@') > 0) { if (encodingAndModifier.indexOf('@') > 0) {
return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@')); return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@'));
} else {
return encodingAndModifier;
} }
return encodingAndModifier;
} }
return null; return null;
} }

View File

@ -54,7 +54,6 @@ public class WindowsTerminal extends Terminal
public void init() throws IOException { public void init() throws IOException {
this.originalMode = WindowsSupport.getConsoleMode(); this.originalMode = WindowsSupport.getConsoleMode();
WindowsSupport.setConsoleMode(this.originalMode & ~ConsoleMode.ENABLE_ECHO_INPUT.code); WindowsSupport.setConsoleMode(this.originalMode & ~ConsoleMode.ENABLE_ECHO_INPUT.code);
setEchoEnabled(false);
} }
/** /**