WIP getting simple version of jline working/trimmed

This commit is contained in:
nathan 2014-10-15 02:19:42 +02:00
parent 3f3538ee4d
commit 24efc75f7b
14 changed files with 1238 additions and 96 deletions

View File

@ -12,8 +12,6 @@
<classpathentry kind="lib" path="/Dependencies/kryo/reflectasm.jar"/>
<classpathentry kind="lib" path="/Dependencies/javassist/javassist.jar"/>
<classpathentry kind="lib" path="/Dependencies/jlzma/lzma-java-1.1.jar" sourcepath="/Dependencies/jlzma/lzma-java-1.1-sources.zip"/>
<classpathentry kind="lib" path="/Dependencies/jline/jline2b.jar" sourcepath="/Dependencies/jline/jline2b-source.zip"/>
<classpathentry kind="lib" path="/Dependencies/jline/jansi-1.11b.jar" sourcepath="/Dependencies/jline/jansi-1.11b-source.zip"/>
<classpathentry kind="lib" path="/Dependencies/BouncyCastleCrypto/bcpkix-jdk15on-151.jar" sourcepath="/Dependencies/BouncyCastleCrypto/bcpkix-jdk15on-151-src.zip"/>
<classpathentry kind="lib" path="/Dependencies/BouncyCastleCrypto/bcprov-debug-jdk15on-151.jar" sourcepath="/Dependencies/BouncyCastleCrypto/bcprov-jdk15on-151-src.zip"/>
<classpathentry kind="lib" path="/Dependencies/netty/netty-all-4.1.0.jar" sourcepath="/Dependencies/netty/netty-all-4.1.0-sources.zip">
@ -25,5 +23,7 @@
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/JavaLauncher-Util"/>
<classpathentry kind="lib" path="/Dependencies/jodaTime/joda-time-2.4.jar" sourcepath="/Dependencies/jodaTime/joda-time-2.4-sources.zip"/>
<classpathentry kind="lib" path="/Dependencies/jna/jna-4.1.0.jar" sourcepath="/Dependencies/jna/jna-4.1.0-sources.zip"/>
<classpathentry kind="lib" path="/Dependencies/jansi/jansi-1.11b.jar" sourcepath="/Dependencies/jansi/jansi-1.11b-source.zip"/>
<classpathentry kind="output" path="classes"/>
</classpath>

View File

@ -4,6 +4,7 @@ import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -532,6 +533,28 @@ public class FileUtil {
unzipJar(zipFile, outputDir, true);
}
/**
* Unzips a ZIP file. Will close the input stream.
*
* @return The path to the output directory.
*/
public static void unzip(ZipInputStream inputStream, String outputDir) throws IOException {
if (outputDir == null) {
throw new IllegalArgumentException("outputDir cannot be null.");
}
unzip(inputStream, new File(outputDir));
}
/**
* Unzips a ZIP file. Will close the input stream.
*
* @return The path to the output directory.
*/
public static void unzip(ZipInputStream inputStream, File outputDir) throws IOException {
unzipJar(inputStream, outputDir, true);
}
/**
* Unzips a ZIP file
*
@ -564,7 +587,21 @@ public class FileUtil {
unjarzip0(zipFile, outputDir, extractManifest);
}
/**
* Unzips a ZIP file. Will close the input stream.
*
* @return The path to the output directory.
*/
public static void unzipJar(ZipInputStream inputStream, File outputDir, boolean extractManifest) throws IOException {
if (inputStream == null) {
throw new IllegalArgumentException("inputStream cannot be null.");
}
if (outputDir == null) {
throw new IllegalArgumentException("outputDir cannot be null.");
}
unjarzip1(inputStream, outputDir, extractManifest);
}
/**
* Unzips a ZIP or JAR file (and handles the manifest if requested)
@ -582,15 +619,20 @@ public class FileUtil {
throw new RuntimeException("Source filesize is too large!");
}
ZipInputStream inputStream = new ZipInputStream(new FileInputStream(zipFile));
ZipInputStream inputStrem = new ZipInputStream(new FileInputStream(zipFile));
unjarzip1(inputStream, outputDir, extractManifest);
}
/**
* Unzips a ZIP file
*
* @return The path to the output directory.
*/
private static void unjarzip1(ZipInputStream inputStream, File outputDir, boolean extractManifest) throws FileNotFoundException, IOException {
try {
while (true) {
ZipEntry entry = inputStrem.getNextEntry();
if (entry == null) {
break;
}
ZipEntry entry = null;
while ((entry = inputStream.getNextEntry()) != null) {
String name = entry.getName();
if (!extractManifest && name.startsWith("META-INF/")) {
@ -607,13 +649,13 @@ public class FileUtil {
FileOutputStream output = new FileOutputStream(file);
try {
Sys.copyStream(inputStrem, output);
Sys.copyStream(inputStream, output);
} finally {
output.close();
Sys.close(output);
}
}
} finally {
inputStrem.close();
Sys.close(inputStream);
}
}

View File

@ -89,6 +89,9 @@ import dorkbox.urlHandler.Box;
* 1) Necessary
* 2) Compatible with GWT
*
*
* To determine if we have hardware acclerated AES
* java -XX:+PrintFlagsFinal -version | grep UseAES
*/
public class Crypto {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Crypto.class);

View File

@ -1,37 +1,54 @@
package dorkbox.util;
package dorkbox.util.input;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
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 jline.IDE_Terminal;
import jline.Terminal;
import jline.console.ConsoleReader;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole;
import org.slf4j.Logger;
import dorkbox.util.OS;
import dorkbox.util.input.posix.UnixTerminal;
import dorkbox.util.input.unsupported.UnsupportedTerminal;
import dorkbox.util.input.windows.WindowsTerminal;
public class InputConsole {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InputConsole.class);
private static final InputConsole consoleProxyReader = new InputConsole();
private static final char[] emptyLine = new char[0];
static {
// setup (if necessary) the JLINE console logger.
// System.setProperty("jline.internal.Log.trace", "TRUE");
// System.setProperty("jline.internal.Log.debug", "TRUE");
}
/**
* empty method to allow code to initialize the input console.
*/
public static void init() {
}
public static final void destroy() {
// this is run by our init...
{
AnsiConsole.systemInstall();
// 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() {
@Override
public void run() {
AnsiConsole.systemUninstall();
InputConsole.destroy();
}
});
}
// called by our shutdown thread
private static final void destroy() {
consoleProxyReader.destroy0();
}
@ -74,8 +91,6 @@ public class InputConsole {
}
private final ConsoleReader jlineReader;
private final Object inputLockSingle = new Object();
private final Object inputLockLine = new Object();
@ -84,42 +99,87 @@ public class InputConsole {
private volatile char[] readLine = null;
private volatile int readChar = -1;
private final boolean isIDE;
private final boolean unsupported;
private final Terminal terminal;
private Reader reader;
private final String encoding;
// the streams are ALREADY buffered!
//
private InputConsole() {
boolean isIDECheck = false;
Terminal terminal = null;
ConsoleReader console = null;
try {
console = new ConsoleReader();
Logger logger = InputConsole.logger;
boolean unsupported = false;
terminal = console.getTerminal();
terminal.setEchoEnabled(true);
isIDECheck = terminal instanceof IDE_Terminal;
} catch (UnsupportedEncodingException ignored) {
} catch (IOException ignored) {
String type = System.getProperty(TerminalType.TYPE, TerminalType.AUTO).toLowerCase();
if ("dumb".equals(System.getenv("TERM"))) {
type = "none";
logger.debug("$TERM=dumb; setting type={}", type);
}
this.isIDE = isIDECheck;
this.jlineReader = console;
logger.debug("Creating terminal; type={}", type);
Logger logger2 = logger;
if (logger2.isDebugEnabled()) {
if (isIDECheck) {
logger2.debug("Terminal is in IDE (best guess). Unable to support single key input. Only line input available.");
Terminal t;
try {
if (type.equals(TerminalType.UNIX)) {
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();
unsupported = true;
} else {
String terminalType;
if (terminal != null) {
terminalType = terminal.getClass().getSimpleName();
if (isIDEAutoDetect()) {
logger.debug("Terminal is in UNSUPPORTED (best guess). Unable to support single key input. Only line input available.");
t = new UnsupportedTerminal();
unsupported = true;
} else {
terminalType = "NULL";
if (OS.isWindows()) {
t = new WindowsTerminal();
} else {
t = new UnixTerminal();
}
}
logger2.debug("Terminal Type: {}", terminalType);
}
}
catch (Exception e) {
logger.error("Failed to construct terminal, falling back to unsupported");
t = new UnsupportedTerminal();
unsupported = true;
}
InputStream in;
try {
t.init();
in = t.wrapInIfNeeded(System.in);
}
catch (Throwable e) {
logger.error("Terminal initialization failed, falling back to unsupported");
t = new UnsupportedTerminal();
unsupported = true;
in = System.in;
try {
t.init();
} catch (IOException e1) {
// UnsupportedTerminal can't do this
}
}
this.encoding = this.encoding != null ? this.encoding : getEncoding();
this.reader = new InputStreamReader(in, this.encoding);
if (unsupported) {
this.reader = new BufferedReader(this.reader);
}
this.unsupported = unsupported;
this.terminal = t;
logger.debug("Created Terminal: {}", this.terminal);
}
/**
@ -145,6 +205,7 @@ public class InputConsole {
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)) {
@ -159,34 +220,22 @@ public class InputConsole {
this.inputLockLine.notifyAll();
}
// we want to make sure this happens in a new thread, since this can BLOCK our main event dispatch thread
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
InputConsole.this.jlineReader.shutdown();
}});
thread.setDaemon(true);
thread.setName("Console Input Shutdown");
thread.start();
try {
InputConsole inputConsole = InputConsole.this;
inputConsole.terminal.restore();
inputConsole.reader.close();
} catch (IOException ignored) {
ignored.printStackTrace();
}
}
private void echo0(boolean enableEcho) {
if (this.jlineReader != null) {
Terminal terminal = this.jlineReader.getTerminal();
if (terminal != null) {
terminal.setEchoEnabled(enableEcho);
}
}
this.terminal.setEchoEnabled(enableEcho);
}
private boolean echo0() {
if (this.jlineReader != null) {
Terminal terminal = this.jlineReader.getTerminal();
if (terminal != null) {
return terminal.isEchoEnabled();
}
}
return false;
return this.terminal.isEchoEnabled();
}
@ -225,7 +274,7 @@ public class InputConsole {
// the chars of the line!
// so, readChar is REALLY the index at which we return letters (until the whole string is returned
if (this.isIDE) {
if (this.unsupported) {
int integer = this.indexOfStringForReadChar.get();
if (integer == -1) {
@ -284,15 +333,15 @@ public class InputConsole {
private final void run() {
Logger logger2 = logger;
if (this.jlineReader == null) {
logger2.error("Unable to start Console Reader");
return;
}
// if we are eclipse, we MUST do this per line! (per character DOESN'T work.)
if (this.isIDE) {
// if we are eclipse/etc, we MUST do this per line! (per character DOESN'T work.)
// char readers will get looped for the WHOLE string, so reading by char will work,
// it just waits until \n until it triggers
if (this.unsupported) {
BufferedReader reader = (BufferedReader) this.reader;
try {
while ((this.readLine = this.jlineReader.readLine()) != null) {
while ((this.readLine = reader.readLine().toCharArray()) != null) {
// notify everyone waiting for a line of text.
synchronized (this.inputLockSingle) {
if (this.readLine.length > 0) {
@ -309,8 +358,8 @@ public class InputConsole {
} catch (Exception ignored) {
ignored.printStackTrace();
}
} else {
}
else {
try {
final boolean ansiEnabled = Ansi.isEnabled();
Ansi ansi = Ansi.ansi();
@ -320,7 +369,7 @@ public class InputConsole {
// don't type ; in a bash shell, it quits everything
// \n is replaced by \r in unix terminal?
while ((typedChar = this.jlineReader.readCharacter()) != -1) {
while ((typedChar = this.reader.read()) != -1) {
char asChar = (char) typedChar;
if (logger2.isTraceEnabled()) {
@ -339,26 +388,30 @@ public class InputConsole {
// clear ourself + one extra.
if (ansiEnabled) {
int amtToBackspace = 2; // ConsoleReader.getPrintableCharacters(typedChar).length();
// size of the buffer BEFORE our backspace was typed
int length = buf.length();
int amtToOverwrite = 2; // backspace is always 2 chars (^?)
if (length > 1) {
char charAt = buf.charAt(length-1);
amtToBackspace += ConsoleReader.getPrintableCharacters(charAt).length();
buf.delete(length-1, length);
amtToOverwrite += getPrintableCharacters(charAt);
length--;
// delete last item in our buffer
buf.setLength(--length);
// now figure out where the cursor is at.
// 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 += ConsoleReader.getPrintableCharacters(charAt).length();
position += getPrintableCharacters(charAt);
}
position++;
}
char[] overwrite = new char[amtToBackspace];
for (int i=0;i<amtToBackspace;i++) {
overwrite[i] = ' ';
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
@ -372,7 +425,7 @@ public class InputConsole {
continue;
}
// ignoring \r, because \n is ALWAYS the last character in a new line sequence.
// ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows)
if (asChar == '\n') {
int length = buf.length();
@ -400,4 +453,112 @@ 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.
+ */
public static String getEncoding() {
// LC_CTYPE is usually in the form en_US.UTF-8
String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE"));
if (envEncoding != null) {
return envEncoding;
}
return System.getProperty("input.encoding", Charset.defaultCharset().name());
}
/**
* Parses the LC_CTYPE value to extract the encoding according to the POSIX standard, which says that the LC_CTYPE
* environment variable may be of the format <code>[language[_territory][.codeset][@modifier]]</code>
*
* @param ctype The ctype to parse, may be null
* @return The encoding, if one was present, otherwise null
*/
static String extractEncodingFromCtype(String ctype) {
if (ctype != null && ctype.indexOf('.') > 0) {
String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1);
if (encodingAndModifier.indexOf('@') > 0) {
return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@'));
} else {
return encodingAndModifier;
}
}
return null;
}
/**
* try to guess if we are running inside an IDE
*/
private boolean isIDEAutoDetect() {
try {
// Get the location of this class
ProtectionDomain pDomain = getClass().getProtectionDomain();
CodeSource cSource = pDomain.getCodeSource();
URL loc = cSource.getLocation(); // file:/X:/workspace/xxxx/classes/ when it's in eclipse
// if we are in eclipse, this won't be a jar -- it will be a class directory.
File locFile = new File(loc.getFile());
return locFile.isDirectory();
} catch (Exception e) {
}
// fall-back to unsupported
return true;
}
/**
* Return the number of characters that will be printed when the specified
* character is echoed to the screen
*
* Adapted from cat by Torbjorn Granlund, as repeated in stty by David MacKenzie.
*/
public static int getPrintableCharacters(final int ch) {
// StringBuilder sbuff = new StringBuilder();
if (ch >= 32) {
if (ch < 127) {
// sbuff.append((char) ch);
return 1;
}
else if (ch == 127) {
// sbuff.append('^');
// sbuff.append('?');
return 2;
}
else {
// sbuff.append('M');
// sbuff.append('-');
int count = 2;
if (ch >= 128 + 32) {
if (ch < 128 + 127) {
// sbuff.append((char) (ch - 128));
count++;
}
else {
// sbuff.append('^');
// sbuff.append('?');
count += 2;
}
}
else {
// sbuff.append('^');
// sbuff.append((char) (ch - 128 + 64));
count += 2;
}
return count;
}
}
else {
// sbuff.append('^');
// sbuff.append((char) (ch + 64));
return 2;
}
// return sbuff;
}
}

View File

@ -0,0 +1,342 @@
/*
* Copyright (c) 2002-2012, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package dorkbox.util.input;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;
/**
*
* NOTE for JLine: the default InputStreamReader that comes from the JRE
* usually read more bytes than needed from the input stream, which
* is not usable in a character per character model used in the console.
* We thus use the harmony code which only reads the minimal number of bytes,
* with a modification to ensure we can read larger characters (UTF-16 has
* up to 4 bytes, and UTF-32, rare as it is, may have up to 8).
*/
/**
* A class for turning a byte stream into a character stream. Data read from the
* source input stream is converted into characters by either a default or a
* provided character converter. The default encoding is taken from the
* "file.encoding" system property. {@code InputStreamReader} contains a buffer
* of bytes read from the source stream and converts these into characters as
* needed. The buffer size is 8K.
*
* @see OutputStreamWriter
*/
public class InputStreamReader extends Reader {
private InputStream in;
private static final int BUFFER_SIZE = 8192;
private boolean endOfInput = false;
String encoding;
CharsetDecoder decoder;
ByteBuffer bytes = ByteBuffer.allocate(BUFFER_SIZE);
/**
* Constructs a new {@code InputStreamReader} on the {@link InputStream}
* {@code in}. This constructor sets the character converter to the encoding
* specified in the "file.encoding" property and falls back to ISO 8859_1
* (ISO-Latin-1) if the property doesn't exist.
*
* @param in
* the input stream from which to read characters.
*/
public InputStreamReader(InputStream in) {
super(in);
this.in = in;
// FIXME: This should probably use Configuration.getFileEncoding()
this.encoding = System.getProperty("file.encoding", "ISO8859_1"); //$NON-NLS-1$//$NON-NLS-2$
this.decoder = Charset.forName(this.encoding).newDecoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
this.bytes.limit(0);
}
/**
* Constructs a new InputStreamReader on the InputStream {@code in}. The
* character converter that is used to decode bytes into characters is
* identified by name by {@code enc}. If the encoding cannot be found, an
* UnsupportedEncodingException error is thrown.
*
* @param in
* the InputStream from which to read characters.
* @param enc
* identifies the character converter to use.
* @throws NullPointerException
* if {@code enc} is {@code null}.
* @throws UnsupportedEncodingException
* if the encoding specified by {@code enc} cannot be found.
*/
public InputStreamReader(InputStream in, final String enc)
// throws UnsupportedEncodingException
{
super(in);
if (enc == null) {
throw new NullPointerException();
}
this.in = in;
try {
this.decoder = Charset.forName(enc).newDecoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
} catch (IllegalArgumentException e) {
e.printStackTrace();
// throw (UnsupportedEncodingException)
// new UnsupportedEncodingException(enc).initCause(e);
}
this.bytes.limit(0);
}
/**
* Constructs a new InputStreamReader on the InputStream {@code in} and
* CharsetDecoder {@code dec}.
*
* @param in
* the source InputStream from which to read characters.
* @param dec
* the CharsetDecoder used by the character conversion.
*/
public InputStreamReader(InputStream in, CharsetDecoder dec) {
super(in);
dec.averageCharsPerByte();
this.in = in;
this.decoder = dec;
this.bytes.limit(0);
}
/**
* Constructs a new InputStreamReader on the InputStream {@code in} and
* Charset {@code charset}.
*
* @param in
* the source InputStream from which to read characters.
* @param charset
* the Charset that defines the character converter
*/
public InputStreamReader(InputStream in, Charset charset) {
super(in);
this.in = in;
this.decoder = charset.newDecoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
this.bytes.limit(0);
}
/**
* Closes this reader. This implementation closes the source InputStream and
* releases all local storage.
*
* @throws IOException
* if an error occurs attempting to close this reader.
*/
@Override
public void close() throws IOException {
synchronized (this.lock) {
this.decoder = null;
if (this.in != null) {
this.in.close();
this.in = null;
}
}
}
/**
* Returns the name of the encoding used to convert bytes into characters.
* The value {@code null} is returned if this reader has been closed.
*
* @return the name of the character converter or {@code null} if this
* reader is closed.
*/
public String getEncoding() {
if (!isOpen()) {
return null;
}
return this.encoding;
}
/**
* Reads a single character from this reader and returns it as an integer
* with the two higher-order bytes set to 0. Returns -1 if the end of the
* reader has been reached. The byte value is either obtained from
* converting bytes in this reader's buffer or by first filling the buffer
* from the source InputStream and then reading from the buffer.
*
* @return the character read or -1 if the end of the reader has been
* reached.
* @throws IOException
* if this reader is closed or some other I/O error occurs.
*/
@Override
public int read() throws IOException {
synchronized (this.lock) {
if (!isOpen()) {
throw new IOException("InputStreamReader is closed.");
}
char buf[] = new char[4];
return read(buf, 0, 4) != -1 ? Character.codePointAt(buf, 0) : -1;
}
}
/**
* Reads at most {@code length} characters from this reader and stores them
* at position {@code offset} in the character array {@code buf}. Returns
* the number of characters actually read or -1 if the end of the reader has
* been reached. The bytes are either obtained from converting bytes in this
* reader's buffer or by first filling the buffer from the source
* InputStream and then reading from the buffer.
*
* @param buf
* the array to store the characters read.
* @param offset
* the initial position in {@code buf} to store the characters
* read from this reader.
* @param length
* the maximum number of characters to read.
* @return the number of characters read or -1 if the end of the reader has
* been reached.
* @throws IndexOutOfBoundsException
* if {@code offset < 0} or {@code length < 0}, or if
* {@code offset + length} is greater than the length of
* {@code buf}.
* @throws IOException
* if this reader is closed or some other I/O error occurs.
*/
@Override
public int read(char[] buf, int offset, int length) throws IOException {
synchronized (this.lock) {
if (!isOpen()) {
throw new IOException("InputStreamReader is closed.");
}
if (offset < 0 || offset > buf.length - length || length < 0) {
throw new IndexOutOfBoundsException();
}
if (length == 0) {
return 0;
}
CharBuffer out = CharBuffer.wrap(buf, offset, length);
CoderResult result = CoderResult.UNDERFLOW;
// bytes.remaining() indicates number of bytes in buffer
// when 1-st time entered, it'll be equal to zero
boolean needInput = !this.bytes.hasRemaining();
while (out.hasRemaining()) {
// fill the buffer if needed
if (needInput) {
try {
if (this.in.available() == 0
&& out.position() > offset) {
// we could return the result without blocking read
break;
}
} catch (IOException e) {
// available didn't work so just try the read
}
int to_read = this.bytes.capacity() - this.bytes.limit();
int off = this.bytes.arrayOffset() + this.bytes.limit();
int was_red = this.in.read(this.bytes.array(), off, to_read);
if (was_red == -1) {
this.endOfInput = true;
break;
} else if (was_red == 0) {
break;
}
this.bytes.limit(this.bytes.limit() + was_red);
needInput = false;
}
// decode bytes
result = this.decoder.decode(this.bytes, out, false);
if (result.isUnderflow()) {
// compact the buffer if no space left
if (this.bytes.limit() == this.bytes.capacity()) {
this.bytes.compact();
this.bytes.limit(this.bytes.position());
this.bytes.position(0);
}
needInput = true;
} else {
break;
}
}
if (result == CoderResult.UNDERFLOW && this.endOfInput) {
result = this.decoder.decode(this.bytes, out, true);
this.decoder.flush(out);
this.decoder.reset();
}
if (result.isMalformed()) {
throw new MalformedInputException(result.length());
} else if (result.isUnmappable()) {
throw new UnmappableCharacterException(result.length());
}
return out.position() - offset == 0 ? -1 : out.position() - offset;
}
}
/*
* Answer a boolean indicating whether or not this InputStreamReader is
* open.
*/
private boolean isOpen() {
return this.in != null;
}
/**
* Indicates whether this reader is ready to be read without blocking. If
* the result is {@code true}, the next {@code read()} will not block. If
* the result is {@code false} then this reader may or may not block when
* {@code read()} is called. This implementation returns {@code true} if
* there are bytes available in the buffer or the source stream has bytes
* available.
*
* @return {@code true} if the receiver will not block when {@code read()}
* is called, {@code false} if unknown or blocking will occur.
* @throws IOException
* if this reader is closed or some other I/O error occurs.
*/
@Override
public boolean ready() throws IOException {
synchronized (this.lock) {
if (this.in == null) {
throw new IOException("InputStreamReader is closed.");
}
try {
return this.bytes.hasRemaining() || this.in.available() > 0;
} catch (IOException e) {
return false;
}
}
}
}

View File

@ -0,0 +1,66 @@
package dorkbox.util.input;
import java.io.IOException;
import java.io.InputStream;
public abstract class Terminal {
protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
public static final int DEFAULT_WIDTH = 80;
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) {
this.echoEnabled = enabled;
}
public boolean isEchoEnabled() {
return this.echoEnabled;
}
public abstract int getWidth();
public abstract int getHeight();
public InputStream wrapInIfNeeded(InputStream in) throws IOException {
return in;
}
}

View File

@ -0,0 +1,14 @@
package dorkbox.util.input;
public class TerminalType {
public static final String TYPE = "input.terminal";
public static final String AUTO = "auto";
public static final String UNIX = "unix";
public static final String WIN = "win";
public static final String WINDOWS = "windows";
public static final String NONE = "none";
public static final String OFF = "off";
public static final String FALSE = "false";
}

View File

@ -0,0 +1,68 @@
package dorkbox.util.input.posix;
import java.nio.ByteBuffer;
import com.sun.jna.Library;
public interface PosixTerminalControl extends Library {
public static final int TCSANOW = 0;
public static final int TBUFLEN = 124;
// Definitions at: http://linux.die.net/man/3/termios
// also: http://code.metager.de/source/xref/DragonFly-BSD/sys/sys/termios.h
public static final int IGNBRK = 0x00000001; /* ignore BREAK condition */
public static final int BRKINT = 0x00000002; /* map BREAK to SIGINTR */
public static final int ISIG = 0000001;
public static final int ICANON = 0000002;
public static final int ECHO = 0000010;
public static final int IXON = 0002000;
public static final int VINTR = 0;
public static final int VQUIT = 1;
public static final int VERASE = 2;
public static final int VKILL = 3;
public static final int VEOF = 4;
public static final int VTIME = 5;
public static final int VMIN = 6;
public static final int VSWTC = 7;
public static final int VSTART = 8;
public static final int VSTOP = 9;
public static final int VSUSP = 10;
public static final int VEOL = 11;
public static final int VREPRINT = 12;
public static final int VDISCARD = 13;
public static final int VWERASE = 14;
public static final int VLNEXT = 15;
public static final int VEOL2 = 16;
// MAGIC!
public static final int TIOCGWINSZ = System.getProperty("os.name").equalsIgnoreCase("linux") ? 0x5413 : 1074295912;
public int open(String path, int flags);
public int close(int fd);
/**
* Original signature : <code>int ioctl(int, int, char*)</code><br>
*/
public int ioctl(int d, int request, ByteBuffer data);
/**
* Put the state of FD into *TERMIOS_P.<br>
*
* Original signature : <code>int tcgetattr(int, char*)</code><br>
*/
public int tcgetattr(int fd, TermiosStruct termios_p);
/**
* Set the state of FD to *TERMIOS_P.<br>
*
* Values for OPTIONAL_ACTIONS (TCSA*) are in <bits/termios.h>.<br>
*
* Original signature : <code>int tcsetattr(int, int, char*)</code><br>
*/
public int tcsetattr(int fd, int optional_actions, TermiosStruct termios_p);
}

View File

@ -0,0 +1,44 @@
package dorkbox.util.input.posix;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
public class TermiosStruct extends Structure {
/** input mode flags */
public int c_iflag;
/** output mode flags */
public int c_oflag;
/** control mode flags */
public int c_cflag;
/** local mode flags */
public int c_lflag;
/** line discipline */
public byte c_line;
/** control characters */
public byte[] c_cc = new byte[32];
/** input speed */
public int c_ispeed;
/** output speed */
public int c_ospeed;
public TermiosStruct() {
}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList(
"c_iflag",
"c_oflag",
"c_cflag",
"c_lflag",
"c_line",
"c_cc",
"c_ispeed",
"c_ospeed"
);
}
}

View File

@ -0,0 +1,167 @@
package dorkbox.util.input.posix;
import java.io.IOException;
import java.nio.ByteBuffer;
import com.sun.jna.Native;
import dorkbox.util.input.Terminal;
/**
* Terminal that is used for unix platforms. Terminal initialization
* is handled via JNA and ioctl/tcgetattr/tcsetattr/cfmakeraw.
*
* This implementation should work for an reasonable POSIX system.
*/
public class UnixTerminal extends Terminal {
private volatile TermiosStruct termInfoDefault = new TermiosStruct();
private volatile TermiosStruct termInfo = new TermiosStruct();
private PosixTerminalControl term;
private ByteBuffer windowSizeBuffer = ByteBuffer.allocate(8);
public UnixTerminal() throws Exception {
this.term = (PosixTerminalControl) Native.loadLibrary("c", PosixTerminalControl.class);
// save off the defaults
if (this.term.tcgetattr(0, this.termInfoDefault) !=0) {
throw new IOException("Failed to get terminal info");
}
}
@Override
public void init() throws IOException {
// COMPARABLE TO (from upstream)
//settings.set("-icanon min 1 -ixon");
//settings.set("dsusp undef");
/*
* NOT done in constructor, since our unit test DOES NOT use this!
*
* Set the console to be character-buffered instead of line-buffered.
* Allow ctrl-s and ctrl-q keypress to be used (as forward search)
*/
// raw mode
// t->c_iflag &= ~(IMAXBEL|IXOFF|INPCK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IGNPAR);
// t->c_iflag |= IGNBRK;
// t->c_oflag &= ~OPOST;
// t->c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON|ISIG|IEXTEN|NOFLSH|TOSTOP|PENDIN);
// t->c_cflag &= ~(CSIZE|PARENB);
// t->c_cflag |= CS8|CREAD;
// t->c_cc[VMIN] = 1;
// t->c_cc[VTIME] = 0;
if (this.term.tcgetattr(0, this.termInfo) !=0) {
throw new IOException("Failed to get terminal info");
}
this.termInfo.c_iflag &= ~PosixTerminalControl.IXON; // DISABLE - flow control mediated by ^S and ^Q
// struct.c_iflag |= PosixTerminalControl.IUTF8; // DISABLE - flow control mediated by ^S and ^Q
this.termInfo.c_lflag &= ~PosixTerminalControl.ICANON; // DISABLE - canonical mode (pass chars straight through to terminal)
// struct.c_lflag &= ~PosixTerminalControl.ISIG; // DISABLE - When any of the characters INTR, QUIT, SUSP, or DSUSP are received, generate the corresponding signal.
// If MIN > 0 and TIME = 0, MIN sets the number of characters to receive before the read is satisfied. As TIME is zero, the timer is not used.
this.termInfo.c_cc[PosixTerminalControl.VMIN] = 1; // Minimum number of characters for noncanonical read (MIN).
this.termInfo.c_cc[PosixTerminalControl.VTIME] = 0; // Timeout in deciseconds for noncanonical read (TIME).
this.termInfo.c_cc[PosixTerminalControl.VSUSP] = 0; // suspend disabled
this.termInfo.c_cc[PosixTerminalControl.VEOF] = 0; // eof disabled
this.termInfo.c_cc[PosixTerminalControl.VEOL] = 0; // eol disabled
if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) {
throw new IOException("Can not set terminal flags");
}
}
/**
* Restore the original terminal configuration, which can be used when
* shutting down the console reader. The ConsoleReader cannot be
* used after calling this method.
*/
@Override
public void restore() throws IOException {
if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfoDefault) != 0) {
throw new IOException("Can not reset terminal to defaults");
}
}
/**
* Returns number of columns in the terminal.
*/
@Override
public int getWidth() {
if (this.term.ioctl(0, PosixTerminalControl.TIOCGWINSZ, this.windowSizeBuffer) != 0) {
return DEFAULT_WIDTH;
}
short columns = (short)(0x000000FF &this.windowSizeBuffer.get(2) + (0x000000FF & this.windowSizeBuffer.get(3)) * 256);
return columns;
}
/**
* Returns number of rows in the terminal.
*/
@Override
public int getHeight() {
if (this.term.ioctl(0, PosixTerminalControl.TIOCGWINSZ, this.windowSizeBuffer) != 0) {
return DEFAULT_HEIGHT;
}
short rows = (short)(0x000000FF &this.windowSizeBuffer.get(0) + (0x000000FF & this.windowSizeBuffer.get(1)) * 256);
return rows;
}
@Override
public synchronized void setEchoEnabled(final boolean enabled) {
// have to reget them, since flags change everything
if (this.term.tcgetattr(0, this.termInfo) !=0) {
this.logger.error("Failed to get terminal info");
}
if (enabled) {
this.termInfo.c_lflag |= PosixTerminalControl.ECHO; // ENABLE Echo input characters.
}
else {
this.termInfo.c_lflag &= ~PosixTerminalControl.ECHO; // DISABLE Echo input characters.
}
if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) {
this.logger.error("Can not set terminal flags");
}
super.setEchoEnabled(enabled);
}
public void disableInterruptCharacter() {
// have to re-get them, since flags change everything
if (this.term.tcgetattr(0, this.termInfo) !=0) {
this.logger.error("Failed to get terminal info");
}
this.termInfo.c_cc[PosixTerminalControl.VINTR] = 0; // interrupt disabled
if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) {
this.logger.error("Can not set terminal flags");
}
}
public void enableInterruptCharacter() {
// have to re-get them, since flags change everything
if (this.term.tcgetattr(0, this.termInfo) !=0) {
this.logger.error("Failed to get terminal info");
}
this.termInfo.c_cc[PosixTerminalControl.VINTR] = 3; // interrupt is ctrl-c
if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) {
this.logger.error("Can not set terminal flags");
}
}
}

View File

@ -0,0 +1,30 @@
package dorkbox.util.input.unsupported;
import java.io.IOException;
import dorkbox.util.input.Terminal;
public class UnsupportedTerminal extends Terminal {
public UnsupportedTerminal() {
// setAnsiSupported(false);
setEchoEnabled(true);
}
@Override
public void init() throws IOException {
}
@Override
public void restore() {
}
@Override
public int getWidth() {
return 0;
}
@Override
public int getHeight() {
return 0;
}
}

View File

@ -0,0 +1,71 @@
package dorkbox.util.input.windows;
/**
* Console mode
* <p/>
* Constants copied <tt>wincon.h</tt>.
*/
public enum ConsoleMode {
/**
* The ReadFile or ReadConsole function returns only when a carriage return
* character is read. If this mode is disable, the functions return when one
* or more characters are available.
*/
ENABLE_LINE_INPUT(2),
/**
* Characters read by the ReadFile or ReadConsole function are written to
* the active screen buffer as they are read. This mode can be used only if
* the ENABLE_LINE_INPUT mode is also enabled.
*/
ENABLE_ECHO_INPUT(4),
/**
* CTRL+C is processed by the system and is not placed in the input buffer.
* If the input buffer is being read by ReadFile or ReadConsole, other
* control keys are processed by the system and are not returned in the
* ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also
* enabled, backspace, carriage return, and linefeed characters are handled
* by the system.
*/
ENABLE_PROCESSED_INPUT(1),
/**
* User interactions that change the size of the console screen buffer are
* reported in the console's input buffee. Information about these events
* can be read from the input buffer by applications using
* theReadConsoleInput function, but not by those using ReadFile
* orReadConsole.
*/
ENABLE_WINDOW_INPUT(8),
/**
* If the mouse pointer is within the borders of the console window and the
* window has the keyboard focus, mouse events generated by mouse movement
* and button presses are placed in the input buffer. These events are
* discarded by ReadFile or ReadConsole, even when this mode is enabled.
*/
ENABLE_MOUSE_INPUT(16),
/**
* When enabled, text entered in a console window will be inserted at the
* current cursor location and all text following that location will not be
* overwritten. When disabled, all following text will be overwritten. An OR
* operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS
* flag to enable this functionality.
*/
ENABLE_PROCESSED_OUTPUT(1),
/**
* This flag enables the user to use the mouse to select and edit text. To
* enable this option, use the OR to combine this flag with
* ENABLE_EXTENDED_FLAGS.
*/
ENABLE_WRAP_AT_EOL_OUTPUT(2),;
public final int code;
ConsoleMode(final int code) {
this.code = code;
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright (c) 2002-2012, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package dorkbox.util.input.windows;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.fusesource.jansi.internal.WindowsSupport;
import dorkbox.util.input.Terminal;
/**
* Terminal implementation for Microsoft Windows. Terminal initialization in
* {@link #init} is accomplished by calling the Win32 APIs <a
* href="http://msdn.microsoft.com/library/default.asp?
* url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and
* <a href="http://msdn.microsoft.com/library/default.asp?
* url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to
* disable character echoing.
* <p/>
* <p>
* By default, the {@link #wrapInIfNeeded(java.io.InputStream)} method will attempt
* to test to see if the specified {@link InputStream} is {@link System#in} or a wrapper
* around {@link FileDescriptor#in}, and if so, will bypass the character reading to
* directly invoke the readc() method in the JNI library. This is so the class
* can read special keys (like arrow keys) which are otherwise inaccessible via
* the {@link System#in} stream. Using JNI reading can be bypassed by setting
* the <code>jline.WindowsTerminal.directConsole</code> system property
* to <code>false</code>.
* </p>
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public class WindowsTerminal extends Terminal
{
public static final String DIRECT_CONSOLE = WindowsTerminal.class.getName() + ".directConsole";
private int originalMode;
public WindowsTerminal() {
}
@Override
public void init() throws IOException {
this.originalMode = WindowsSupport.getConsoleMode();
WindowsSupport.setConsoleMode(this.originalMode & ~ConsoleMode.ENABLE_ECHO_INPUT.code);
setEchoEnabled(false);
}
/**
* Restore the original terminal configuration, which can be used when
* shutting down the console reader. The ConsoleReader cannot be
* used after calling this method.
*/
@Override
public void restore() throws IOException {
// restore the old console mode
WindowsSupport.setConsoleMode(this.originalMode);
}
@Override
public int getWidth() {
int w = WindowsSupport.getWindowsTerminalWidth();
return w < 1 ? DEFAULT_WIDTH : w;
}
@Override
public int getHeight() {
int h = WindowsSupport.getWindowsTerminalHeight();
return h < 1 ? DEFAULT_HEIGHT : h;
}
@Override
public void setEchoEnabled(final boolean enabled) {
// Must set these four modes at the same time to make it work fine.
if (enabled) {
WindowsSupport.setConsoleMode(WindowsSupport.getConsoleMode() |
ConsoleMode.ENABLE_ECHO_INPUT.code |
ConsoleMode.ENABLE_LINE_INPUT.code |
ConsoleMode.ENABLE_PROCESSED_INPUT.code |
ConsoleMode.ENABLE_WINDOW_INPUT.code);
}
else {
WindowsSupport.setConsoleMode(WindowsSupport.getConsoleMode() &
~(ConsoleMode.ENABLE_LINE_INPUT.code |
ConsoleMode.ENABLE_ECHO_INPUT.code |
ConsoleMode.ENABLE_PROCESSED_INPUT.code |
ConsoleMode.ENABLE_WINDOW_INPUT.code));
}
super.setEchoEnabled(enabled);
}
@Override
public InputStream wrapInIfNeeded(InputStream in) throws IOException {
if (isSystemIn(in)) {
return new InputStream() {
@Override
public int read() throws IOException {
return WindowsSupport.readByte();
}
};
} else {
return in;
}
}
private boolean isSystemIn(final InputStream in) throws IOException {
if (in == null) {
return false;
}
else if (in == System.in) {
return true;
}
else if (in instanceof FileInputStream && ((FileInputStream) in).getFD() == FileDescriptor.in) {
return true;
}
return false;
}
}

View File

@ -115,7 +115,10 @@ public class ShellProcessBuilder {
} else {
// *nix
executableName = "/bin/bash";
// executableName = "/bin/sh";
File file = new File(executableName);
if (!file.canExecute()) {
executableName = "/bin/sh";
}
arguments.add(0, "-c");
}
} else if (workingDirectory != null) {