Cleaned up input console to use my own growing-byte buffers. Also made one that is fast (but unsynchronized)
This commit is contained in:
parent
24efc75f7b
commit
890d73e8aa
|
@ -34,23 +34,23 @@ public class ByteBuffer2 {
|
|||
public ByteBuffer2(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
clear();
|
||||
position = bytes.length;
|
||||
this.position = bytes.length;
|
||||
}
|
||||
|
||||
public byte getByte() {
|
||||
if (position > limit) {
|
||||
if (this.position > this.limit) {
|
||||
throw new BufferUnderflowException();
|
||||
}
|
||||
|
||||
return bytes[position++];
|
||||
return this.bytes[this.position++];
|
||||
}
|
||||
|
||||
public byte getByte(int i) {
|
||||
if (i > limit) {
|
||||
if (i > this.limit) {
|
||||
throw new BufferUnderflowException();
|
||||
}
|
||||
|
||||
return bytes[i];
|
||||
return this.bytes[i];
|
||||
}
|
||||
|
||||
public void getBytes(byte[] buffer) {
|
||||
|
@ -62,12 +62,12 @@ public class ByteBuffer2 {
|
|||
}
|
||||
|
||||
public void getBytes(byte[] buffer, int offset, int length) {
|
||||
if (position + length - offset > limit) {
|
||||
if (this.position + length - offset > this.limit) {
|
||||
throw new BufferUnderflowException();
|
||||
}
|
||||
|
||||
System.arraycopy(bytes, position, buffer, 0, length-offset);
|
||||
position += length-offset;
|
||||
System.arraycopy(this.bytes, this.position, buffer, 0, length-offset);
|
||||
this.position += length-offset;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,18 +76,18 @@ public class ByteBuffer2 {
|
|||
* NOT PROTECTED
|
||||
*/
|
||||
private final void _put(byte b) {
|
||||
bytes[position++] = b;
|
||||
this.bytes[this.position++] = b;
|
||||
}
|
||||
|
||||
/** NOT PROTECTED! */
|
||||
private final void checkBuffer(int threshold) {
|
||||
if (bytes.length < threshold) {
|
||||
if (this.bytes.length < threshold) {
|
||||
byte[] t = new byte[threshold];
|
||||
// grow at back of array
|
||||
System.arraycopy(bytes, 0, t, 0, bytes.length);
|
||||
limit = t.length;
|
||||
System.arraycopy(this.bytes, 0, t, 0, this.bytes.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) {
|
||||
checkBuffer(position + length - offset);
|
||||
checkBuffer(this.position + length - offset);
|
||||
|
||||
System.arraycopy(src, offset, bytes, position, length);
|
||||
position += length;
|
||||
System.arraycopy(src, offset, this.bytes, this.position, length);
|
||||
this.position += length;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public final synchronized ByteBuffer2 putByte(byte b) {
|
||||
checkBuffer(position + 1);
|
||||
checkBuffer(this.position + 1);
|
||||
|
||||
_put(b);
|
||||
return this;
|
||||
|
@ -122,7 +122,7 @@ public class ByteBuffer2 {
|
|||
}
|
||||
|
||||
public final synchronized void putChar(char c) {
|
||||
checkBuffer(position + 2);
|
||||
checkBuffer(this.position + 2);
|
||||
|
||||
putBytes(BigEndian.Char_.toBytes(c));
|
||||
}
|
||||
|
@ -131,8 +131,19 @@ public class ByteBuffer2 {
|
|||
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) {
|
||||
checkBuffer(position + 2);
|
||||
checkBuffer(this.position + 2);
|
||||
|
||||
putBytes(BigEndian.Short_.toBytes(x));
|
||||
|
||||
|
@ -143,21 +154,26 @@ public class ByteBuffer2 {
|
|||
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) {
|
||||
checkBuffer(position + 4);
|
||||
checkBuffer(this.position + 4);
|
||||
|
||||
putBytes(BigEndian.Int_.toBytes(x));
|
||||
}
|
||||
|
||||
public final synchronized int getInt() {
|
||||
byte b3 = getByte();
|
||||
byte b2 = getByte();
|
||||
byte b1 = getByte();
|
||||
return BigEndian.Int_.fromBytes(getByte(), b1, b2, b3);
|
||||
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 synchronized void putLong(long x) {
|
||||
checkBuffer(position + 8);
|
||||
checkBuffer(this.position + 8);
|
||||
|
||||
putBytes(BigEndian.Long_.toBytes(x));
|
||||
}
|
||||
|
@ -166,21 +182,25 @@ public class ByteBuffer2 {
|
|||
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 bytes;
|
||||
return this.bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the backing array of this buffer
|
||||
*/
|
||||
public final synchronized byte[] arrayCopy() {
|
||||
int length = bytes.length - position;
|
||||
int length = this.bytes.length - this.position;
|
||||
|
||||
byte[] b = new byte[length];
|
||||
System.arraycopy(bytes, position, b, 0, length);
|
||||
System.arraycopy(this.bytes, this.position, b, 0, length);
|
||||
return b;
|
||||
}
|
||||
|
||||
|
@ -188,20 +208,20 @@ public class ByteBuffer2 {
|
|||
* Returns this buffer's position.
|
||||
*/
|
||||
public int position() {
|
||||
return position;
|
||||
return this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this buffer's position.
|
||||
*/
|
||||
public final synchronized ByteBuffer2 position(int position) {
|
||||
if (position > bytes.length || position < 0) {
|
||||
if (position > this.bytes.length || position < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
this.position = position;
|
||||
if (mark > position) {
|
||||
mark = -1;
|
||||
if (this.mark > position) {
|
||||
this.mark = -1;
|
||||
}
|
||||
|
||||
return this;
|
||||
|
@ -212,7 +232,7 @@ public class ByteBuffer2 {
|
|||
* limit.
|
||||
*/
|
||||
public final synchronized int remaining() {
|
||||
return limit - position;
|
||||
return this.limit - this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -220,7 +240,7 @@ public class ByteBuffer2 {
|
|||
* the limit.
|
||||
*/
|
||||
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) {
|
||||
this.limit = limit;
|
||||
if (position > limit) {
|
||||
position = limit;
|
||||
if (this.position > limit) {
|
||||
this.position = limit;
|
||||
}
|
||||
if (mark > limit) {
|
||||
mark = -1;
|
||||
if (this.mark > limit) {
|
||||
this.mark = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,14 +260,14 @@ public class ByteBuffer2 {
|
|||
* Returns this buffer's limit.
|
||||
*/
|
||||
public int limit() {
|
||||
return limit;
|
||||
return this.limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this buffer's 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.
|
||||
*/
|
||||
public final synchronized void compact() {
|
||||
mark = -1;
|
||||
System.arraycopy(bytes, position, bytes, 0, remaining());
|
||||
this.mark = -1;
|
||||
System.arraycopy(this.bytes, this.position, this.bytes, 0, remaining());
|
||||
|
||||
position(remaining());
|
||||
limit(capacity());
|
||||
|
@ -275,9 +295,9 @@ public class ByteBuffer2 {
|
|||
* discarded.
|
||||
*/
|
||||
public final synchronized void flip() {
|
||||
limit = position;
|
||||
position = 0;
|
||||
mark = -1;
|
||||
this.limit = this.position;
|
||||
this.position = 0;
|
||||
this.mark = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -285,9 +305,9 @@ public class ByteBuffer2 {
|
|||
* the capacity, and the mark is discarded.
|
||||
*/
|
||||
public final synchronized void clear() {
|
||||
position = 0;
|
||||
limit = capacity();
|
||||
mark = -1;
|
||||
this.position = 0;
|
||||
this.limit = capacity();
|
||||
this.mark = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -295,15 +315,15 @@ public class ByteBuffer2 {
|
|||
* discarded.
|
||||
*/
|
||||
public final synchronized void rewind() {
|
||||
position = 0;
|
||||
mark = -1;
|
||||
this.position = 0;
|
||||
this.mark = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this buffer's mark at its position.
|
||||
*/
|
||||
public final synchronized void mark() {
|
||||
mark = position;
|
||||
this.mark = this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -313,6 +333,6 @@ public class ByteBuffer2 {
|
|||
* value. </p>
|
||||
*/
|
||||
public void reset() {
|
||||
position = mark;
|
||||
this.position = this.mark;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -5,18 +5,21 @@ import java.io.BufferedReader;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.fusesource.jansi.Ansi;
|
||||
import org.fusesource.jansi.AnsiConsole;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import dorkbox.util.OS;
|
||||
import dorkbox.util.Sys;
|
||||
import dorkbox.util.bytes.ByteBuffer2Fast;
|
||||
import dorkbox.util.input.posix.UnixTerminal;
|
||||
import dorkbox.util.input.unsupported.UnsupportedTerminal;
|
||||
import dorkbox.util.input.windows.WindowsTerminal;
|
||||
|
@ -36,20 +39,31 @@ public class InputConsole {
|
|||
{
|
||||
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
|
||||
// alternatively, shut everything down when the JVM closes.
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
Thread shutdownThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
AnsiConsole.systemUninstall();
|
||||
InputConsole.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// called by our shutdown thread
|
||||
private static final void destroy() {
|
||||
consoleProxyReader.destroy0();
|
||||
consoleProxyReader.shutdown0();
|
||||
}
|
||||
};
|
||||
shutdownThread.setName("Console Input Shutdown");
|
||||
Runtime.getRuntime().addShutdownHook(shutdownThread);
|
||||
}
|
||||
|
||||
/** return null if no data */
|
||||
|
@ -94,9 +108,17 @@ public class InputConsole {
|
|||
private final Object inputLockSingle = new Object();
|
||||
private final Object inputLockLine = new Object();
|
||||
|
||||
private AtomicBoolean isRunning = new AtomicBoolean(false);
|
||||
private AtomicBoolean isInShutdown = new AtomicBoolean(false);
|
||||
private volatile char[] readLine = null;
|
||||
private ThreadLocal<ByteBuffer2Fast> threadBufferForRead = new ThreadLocal<ByteBuffer2Fast>();
|
||||
private CopyOnWriteArrayList<ByteBuffer2Fast> threadBuffersForRead = new CopyOnWriteArrayList<ByteBuffer2Fast>();
|
||||
|
||||
ThreadLocal<Integer> indexOfStringForReadChar = new ThreadLocal<Integer>() {
|
||||
@Override
|
||||
protected Integer initialValue() {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private volatile int readChar = -1;
|
||||
|
||||
private final boolean unsupported;
|
||||
|
@ -119,7 +141,7 @@ public class InputConsole {
|
|||
|
||||
logger.debug("Creating terminal; type={}", type);
|
||||
|
||||
Terminal t;
|
||||
Terminal t;
|
||||
try {
|
||||
if (type.equals(TerminalType.UNIX)) {
|
||||
t = new UnixTerminal();
|
||||
|
@ -182,36 +204,8 @@ public class InputConsole {
|
|||
logger.debug("Created Terminal: {}", this.terminal);
|
||||
}
|
||||
|
||||
/**
|
||||
* make sure the input console reader thread is started.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
// called when the JVM is shutting down.
|
||||
private void shutdown0() {
|
||||
synchronized (this.inputLockSingle) {
|
||||
this.inputLockSingle.notifyAll();
|
||||
}
|
||||
|
@ -224,7 +218,8 @@ public class InputConsole {
|
|||
InputConsole inputConsole = InputConsole.this;
|
||||
|
||||
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) {
|
||||
ignored.printStackTrace();
|
||||
}
|
||||
|
@ -241,7 +236,11 @@ public class InputConsole {
|
|||
|
||||
/** return null if no data */
|
||||
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) {
|
||||
try {
|
||||
|
@ -250,34 +249,53 @@ public class InputConsole {
|
|||
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 */
|
||||
private final char[] readLinePassword0() {
|
||||
// don't bother in an IDE. it won't work.
|
||||
return readLine0();
|
||||
}
|
||||
|
||||
ThreadLocal<Integer> indexOfStringForReadChar = new ThreadLocal<Integer>() {
|
||||
@Override
|
||||
protected Integer initialValue() {
|
||||
return -1;
|
||||
if (this.threadBufferForRead.get() == null) {
|
||||
ByteBuffer2Fast buffer = ByteBuffer2Fast.allocate(0);
|
||||
this.threadBufferForRead.set(buffer);
|
||||
this.threadBuffersForRead.add(buffer);
|
||||
}
|
||||
};
|
||||
|
||||
// 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() {
|
||||
startInputConsole();
|
||||
|
||||
// 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
|
||||
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.
|
||||
synchronized (this.inputLockLine) {
|
||||
try {
|
||||
|
@ -285,24 +303,23 @@ public class InputConsole {
|
|||
} catch (InterruptedException e) {
|
||||
return -1;
|
||||
}
|
||||
integer = 0;
|
||||
readerCount = 0;
|
||||
this.indexOfStringForReadChar.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
// EACH thread will have it's own count!
|
||||
char[] readLine2 = this.readLine;
|
||||
|
||||
if (readLine2 == null) {
|
||||
return -1;
|
||||
} else if (integer == readLine2.length) {
|
||||
// EACH thread will have it's own count!
|
||||
ByteBuffer2Fast stringBuffer = this.threadBufferForRead.get();
|
||||
|
||||
if (readerCount == stringBuffer.position()) {
|
||||
this.indexOfStringForReadChar.set(-1);
|
||||
return '\n';
|
||||
} else {
|
||||
this.indexOfStringForReadChar.set(integer+1);
|
||||
this.indexOfStringForReadChar.set(readerCount+1);
|
||||
}
|
||||
|
||||
char c = readLine2[integer];
|
||||
char c = stringBuffer.getChar(readerCount);
|
||||
return c;
|
||||
}
|
||||
else {
|
||||
|
@ -339,19 +356,30 @@ public class InputConsole {
|
|||
// it just waits until \n until it triggers
|
||||
if (this.unsupported) {
|
||||
BufferedReader reader = (BufferedReader) this.reader;
|
||||
String line = null;
|
||||
char[] readLine = null;
|
||||
|
||||
try {
|
||||
while ((this.readLine = reader.readLine().toCharArray()) != null) {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
readLine = line.toCharArray();
|
||||
|
||||
// notify everyone waiting for a line of text.
|
||||
synchronized (this.inputLockSingle) {
|
||||
if (this.readLine.length > 0) {
|
||||
this.readChar = this.readLine[0];
|
||||
if (readLine.length > 0) {
|
||||
this.readChar = readLine[0];
|
||||
} else {
|
||||
this.readChar = -1;
|
||||
}
|
||||
this.inputLockSingle.notifyAll();
|
||||
}
|
||||
synchronized (this.inputLockLine) {
|
||||
byte[] charToBytes = Sys.charToBytes(readLine);
|
||||
|
||||
for (ByteBuffer2Fast buffer : this.threadBuffersForRead) {
|
||||
buffer.clear();
|
||||
buffer.putBytes(charToBytes);
|
||||
}
|
||||
|
||||
this.inputLockLine.notifyAll();
|
||||
}
|
||||
}
|
||||
|
@ -360,12 +388,13 @@ public class InputConsole {
|
|||
}
|
||||
}
|
||||
else {
|
||||
// from a 'regular' console
|
||||
try {
|
||||
final boolean ansiEnabled = Ansi.isEnabled();
|
||||
Ansi ansi = Ansi.ansi();
|
||||
PrintStream out = AnsiConsole.out;
|
||||
|
||||
int typedChar;
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
// don't type ; in a bash shell, it quits everything
|
||||
// \n is replaced by \r in unix terminal?
|
||||
|
@ -388,65 +417,57 @@ public class InputConsole {
|
|||
|
||||
// clear ourself + one extra.
|
||||
if (ansiEnabled) {
|
||||
// size of the buffer BEFORE our backspace was typed
|
||||
int length = buf.length();
|
||||
int amtToOverwrite = 2; // backspace is always 2 chars (^?)
|
||||
for (ByteBuffer2Fast buffer : this.threadBuffersForRead) {
|
||||
// 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 = buf.charAt(length-1);
|
||||
amtToOverwrite += getPrintableCharacters(charAt);
|
||||
if (length > 1) {
|
||||
char charAt = buffer.getChar(length-2);
|
||||
amtToOverwrite += getPrintableCharacters(charAt);
|
||||
|
||||
// delete last item in our buffer
|
||||
buf.setLength(--length);
|
||||
// delete last item in our buffer
|
||||
length -= 2;
|
||||
buffer.position(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++) {
|
||||
charAt = buf.charAt(i);
|
||||
position += getPrintableCharacters(charAt);
|
||||
// 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.getChar(i);
|
||||
position += getPrintableCharacters(charAt);
|
||||
}
|
||||
|
||||
position++;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
|
||||
char[] overwrite = new char[amtToOverwrite];
|
||||
char c = ' ';
|
||||
for (int i=0;i<amtToOverwrite;i++) {
|
||||
overwrite[i] = c;
|
||||
}
|
||||
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
|
||||
AnsiConsole.out.print(ansi.cursorToColumn(position));
|
||||
AnsiConsole.out.print(overwrite);
|
||||
AnsiConsole.out.print(ansi.cursorToColumn(position));
|
||||
AnsiConsole.out.flush();
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
// readline will ignore backspace
|
||||
// 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') {
|
||||
int length = buf.length();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// 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') {
|
||||
// only append if we are not a new line.
|
||||
buf.append(asChar);
|
||||
for (ByteBuffer2Fast buffer : this.threadBuffersForRead) {
|
||||
buffer.putChar(asChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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
|
||||
+ * system property, then the default charset according to the JVM.
|
||||
+ *
|
||||
+ * @return The default encoding to use when none is specified.
|
||||
+ */
|
||||
* 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.
|
||||
*
|
||||
* @return The default encoding to use when none is specified.
|
||||
*/
|
||||
public static String getEncoding() {
|
||||
// LC_CTYPE is usually in the form en_US.UTF-8
|
||||
String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE"));
|
||||
|
@ -481,9 +502,8 @@ public class InputConsole {
|
|||
String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1);
|
||||
if (encodingAndModifier.indexOf('@') > 0) {
|
||||
return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@'));
|
||||
} else {
|
||||
return encodingAndModifier;
|
||||
}
|
||||
return encodingAndModifier;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ public class WindowsTerminal extends Terminal
|
|||
public void init() throws IOException {
|
||||
this.originalMode = WindowsSupport.getConsoleMode();
|
||||
WindowsSupport.setConsoleMode(this.originalMode & ~ConsoleMode.ENABLE_ECHO_INPUT.code);
|
||||
setEchoEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue