diff --git a/src/dorkbox/console/Console.java b/src/dorkbox/console/Console.java
index 6c0d1c9..22f687d 100644
--- a/src/dorkbox/console/Console.java
+++ b/src/dorkbox/console/Console.java
@@ -15,32 +15,28 @@
*/
package dorkbox.console;
-import static dorkbox.console.Input.readLinePassword;
-
-import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.io.PrintStream;
+import dorkbox.console.input.Input;
+import dorkbox.console.input.Terminal;
+import dorkbox.console.output.Ansi;
import dorkbox.console.output.AnsiOutputStream;
-import dorkbox.console.output.WindowsAnsiOutputStream;
-import dorkbox.console.util.posix.CLibraryPosix;
-import dorkbox.console.util.windows.Kernel32;
import dorkbox.util.Property;
/**
- * Provides a fluent API for generating ANSI escape sequences and providing access to streams that support it.
- *
- * See: https://en.wikipedia.org/wiki/ANSI_escape_code
+ * Provides access to single character input streams and ANSI capable output streams.
*
* @author dorkbox, llc
*/
+@SuppressWarnings("unused")
public
class Console {
/**
- * If true, allows an ANSI output stream to be created, otherwise a NO-OP stream is created instead
+ * If true, allows an ANSI output stream to be created on System.out/err, If true, allows an ANSI output stream to be created on
+ * System.out/err, otherwise it will provide an ANSI aware PrintStream which strips out the ANSI escape sequences.
*/
@Property
public static boolean ENABLE_ANSI = true;
@@ -53,13 +49,14 @@ class Console {
public static boolean FORCE_ENABLE_ANSI = false;
/**
- * Enables or disables character echo to stdout in the console, should call {@link #setEchoEnabled(boolean)} after initialization
+ * Enables or disables character echo to stdout in the console, should call {@link Terminal#setEchoEnabled(boolean)} after
+ * initialization
*/
@Property
public static volatile boolean ENABLE_ECHO = true;
/**
- * Enables or disables CTRL-C behavior in the console, should call {@link #setInterruptEnabled(boolean)} after initialization
+ * Enables or disables CTRL-C behavior in the console, should call {@link Terminal#setInterruptEnabled(boolean)} after initialization
*/
@Property
public static volatile boolean ENABLE_INTERRUPT = false;
@@ -73,6 +70,7 @@ class Console {
/**
+ * Used to determine what console to use/hook when AUTO is not correctly working.
* Valid options are:
* AUTO - automatically determine which OS/console type to use
* UNIX - try to control a UNIX console
@@ -83,16 +81,6 @@ class Console {
public static final String INPUT_CONSOLE_TYPE = "AUTO";
-
- private static final PrintStream original_out = System.out;
- private static final PrintStream original_err = System.err;
-
- // protected by synchronize
- private static int installed = 0;
- private static PrintStream out;
- private static PrintStream err;
-
-
/**
* Gets the version number.
*/
@@ -101,108 +89,49 @@ class Console {
return "2.9";
}
+
/**
- * Reads single character input from the console.
+ * If the standard in supports single character input, then a terminal will be returned that supports it, otherwise a buffered (aka
+ * 'normal') input will be returned
*
- * @return -1 if no data or problems
+ * @return a terminal that supports single character input or the default buffered input
*/
public static
- int read() {
- return Input.read();
+ Terminal in() {
+ return Input.terminal;
}
/**
- * Reads a line of characters from the console, defined as everything before the 'ENTER' key is pressed
+ * If the standard in supports single character input, then an InputStream will be returned that supports it, otherwise a buffered (aka
+ * 'normal') InputStream will be returned
*
- * @return null if no data
+ * @return an InputStream that supports single character input or the default buffered input
*/
public static
- String readLine() {
- return Input.readLine();
+ InputStream inputStream() {
+ return Input.wrappedInputStream;
}
-
/**
- * Reads a line of characters from the console as a character array, defined as everything before the 'ENTER' key is pressed
+ * If the standard out natively supports ANSI escape codes, then this just returns System.out (wrapped to reset ANSI stream on close),
+ * otherwise it will provide an ANSI aware PrintStream which strips out the ANSI escape sequences.
*
- * @return empty char[] if no data
+ * @return a PrintStream which is ANSI aware.
*/
public static
- char[] readLineChars() {
- return Input.readLineChars();
+ PrintStream out() {
+ return Ansi.out;
}
/**
- * Reads a line of characters from the console as a character array, defined as everything before the 'ENTER' key is pressed
+ * If the standard out natively supports ANSI escape codes, then this just returns System.err (wrapped to reset ANSI stream on close),
+ * otherwise it will provide an ANSI aware PrintStream which strips out the ANSI escape sequences.
*
- * @return empty char[] if no data
+ * @return a PrintStream which is ANSI aware.
*/
public static
- char[] readPassword() {
- return readLinePassword();
- }
-
- /**
- * Reads an InputStream capable of reading a single character at a time
- */
- public static
- InputStream getInputStream() {
- return Input.getInputStream();
- }
-
- /**
- * Enables or disables CTRL-C behavior in the console
- */
- public static
- void setInterruptEnabled(final boolean enabled) {
- Console.ENABLE_INTERRUPT = enabled;
- Input.setInterruptEnabled(enabled);
- }
-
- /**
- * Enables or disables character echo to stdout
- */
- public static
- void setEchoEnabled(final boolean enabled) {
- Console.ENABLE_ECHO = enabled;
- Input.setEchoEnabled(enabled);
- }
-
- /**
- * Override System.err and System.out with an ANSI capable {@link java.io.PrintStream}.
- */
- public static synchronized
- void systemInstall() {
- installed++;
- if (installed == 1) {
- out = out();
- err = err();
-
- System.setOut(out);
- System.setErr(err);
- }
- }
-
- /**
- * un-does a previous {@link #systemInstall()}.
- *
- * If {@link #systemInstall()} was called multiple times, then {@link #systemUninstall()} must be called the same number of
- * times before it is uninstalled.
- */
- public static synchronized
- void systemUninstall() {
- installed--;
- if (installed == 0) {
- if (out != null && out != original_out) {
- out.close();
- System.setOut(original_out);
- }
-
- if (err != null && err != original_err) {
- err.close();
- System.setErr(original_err);
- }
- }
+ PrintStream err() {
+ return Ansi.err;
}
/**
@@ -211,115 +140,11 @@ class Console {
*/
public static synchronized
void reset() {
- if (installed >= 1) {
- try {
- System.out.write(AnsiOutputStream.RESET_CODE);
- } catch (IOException e) {
- e.printStackTrace();
- }
+ try {
+ Ansi.out.write(AnsiOutputStream.RESET_CODE);
+ } catch (IOException e) {
+ e.printStackTrace();
}
}
-
-
- /**
- * If the standard out natively supports ANSI escape codes, then this just returns System.out, otherwise it will provide an ANSI
- * aware PrintStream which strips out the ANSI escape sequences or which implement the escape sequences.
- *
- * @return a PrintStream which is ANSI aware.
- */
- public static
- PrintStream out() {
- if (out == null) {
- out = createPrintStream(original_out, 1); // STDOUT_FILENO
- }
- return out;
- }
-
- /**
- * If the standard out natively supports ANSI escape codes, then this just returns System.err, otherwise it will provide an ANSI aware
- * PrintStream which strips out the ANSI escape sequences or which implement the escape sequences.
- *
- * @return a PrintStream which is ANSI aware.
- */
- public static
- PrintStream err() {
- if (err == null) {
- err = createPrintStream(original_err, 2); // STDERR_FILENO
- }
- return err;
- }
-
-
-
-
-
-
-
-
-
- private static boolean isXterm() {
- String term = System.getenv("TERM");
- return "xterm".equalsIgnoreCase(term);
- }
-
- private static
- PrintStream createPrintStream(final OutputStream stream, int fileno) {
- if (!ENABLE_ANSI) {
- // Use the ANSIOutputStream to strip out the ANSI escape sequences.
- return new PrintStream(new AnsiOutputStream(stream));
- }
-
- if (!isXterm()) {
- String os = System.getProperty("os.name");
- if (os.startsWith("Windows")) {
- // check if windows10+ (which natively supports ANSI)
- if (Kernel32.isWindows10OrGreater()) {
- // Just wrap it up so that when we get closed, we reset the attributes.
- return deafultPrintStream(stream);
- }
-
- // On windows we know the console does not interpret ANSI codes..
- try {
- return new PrintStream(new WindowsAnsiOutputStream(stream, fileno));
- } catch (Throwable ignore) {
- // this happens when JNA is not in the path.. or
- // this happens when the stdout is being redirected to a file.
- // this happens when the stdout is being redirected to different console.
- }
-
- // Use the ANSIOutputStream to strip out the ANSI escape sequences.
- if (!FORCE_ENABLE_ANSI) {
- return new PrintStream(new AnsiOutputStream(stream));
- }
- } else {
- // We must be on some unix variant..
- try {
- // If we can detect that stdout is not a tty.. then setup to strip the ANSI sequences..
- if (!FORCE_ENABLE_ANSI && CLibraryPosix.isatty(fileno) == 0) {
- return new PrintStream(new AnsiOutputStream(stream));
- }
- } catch (Throwable ignore) {
- // These errors happen if the JNI lib is not available for your platform.
- }
- }
- }
-
- // By default we assume the terminal can handle ANSI codes.
- // Just wrap it up so that when we get closed, we reset the attributes.
- return deafultPrintStream(stream);
- }
-
- private static
- PrintStream deafultPrintStream(final OutputStream stream) {
- return new PrintStream(new FilterOutputStream(stream) {
- @Override
- public
- void close() throws IOException {
- write(AnsiOutputStream.RESET_CODE);
- flush();
- super.close();
- }
- });
- }
}
diff --git a/src/dorkbox/console/Input.java b/src/dorkbox/console/Input.java
deleted file mode 100644
index 558f2bd..0000000
--- a/src/dorkbox/console/Input.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright 2010 dorkbox, llc
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package dorkbox.console;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-import org.slf4j.Logger;
-
-import dorkbox.console.input.PosixTerminal;
-import dorkbox.console.input.Terminal;
-import dorkbox.console.input.UnsupportedTerminal;
-import dorkbox.console.input.WindowsTerminal;
-import dorkbox.console.output.Ansi;
-import dorkbox.console.util.CharHolder;
-import dorkbox.objectPool.ObjectPool;
-import dorkbox.objectPool.PoolableObject;
-import dorkbox.util.OS;
-import dorkbox.util.bytes.ByteBuffer2;
-import dorkbox.util.bytes.ByteBuffer2Poolable;
-
-class Input {
- private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Input.class);
- private static final char[] emptyLine = new char[0];
-
-
- private static final List charInputBuffers = new ArrayList();
- private final static ObjectPool charInputPool = ObjectPool.NonBlocking(new PoolableObject() {
- @Override
- public
- CharHolder create() {
- return new CharHolder();
- }
-
- @Override
- public
- void onReturn(final CharHolder object) {
- // dump the chars in the buffer (safer for passwords, etc)
- object.character = (char) 0;
- charInputBuffers.remove(object);
- }
-
- @Override
- public
- void onTake(final CharHolder object) {
- charInputBuffers.add(object);
- }
- });
-
-
- private static final List lineInputBuffers = new ArrayList();
- private final static ObjectPool lineInputPool = ObjectPool.NonBlocking(new ByteBuffer2Poolable() {
- @Override
- public
- void onReturn(final ByteBuffer2 object) {
- // dump the chars in the buffer (safer for passwords, etc)
- object.clearSecure();
- lineInputBuffers.remove(object);
- }
-
- @Override
- public
- void onTake(final ByteBuffer2 object) {
- lineInputBuffers.add(object);
- }
- });
-
-
-
- private final static Terminal terminal;
-
- private static final Object inputLock = new Object();
- private static final Object inputLockSingle = new Object();
- private static final Object inputLockLine = new Object();
-
- static {
- String type = Console.INPUT_CONSOLE_TYPE.toUpperCase(Locale.ENGLISH);
-
- Throwable didFallbackE = null;
- Terminal term;
- try {
- if (type.equals("UNIX")) {
- term = new PosixTerminal();
- }
- else if (type.equals("WINDOWS")) {
- term = new WindowsTerminal();
- }
- else if (type.equals("NONE")) {
- term = new UnsupportedTerminal();
- }
- else {
- // if these cannot be created, because we are in an IDE, an error will be thrown
- if (OS.isWindows()) {
- term = new WindowsTerminal();
- }
- else {
- term = new PosixTerminal();
- }
- }
- } catch (Exception e) {
- didFallbackE = e;
- term = new UnsupportedTerminal();
- }
-
- terminal = term;
-
-
- if (logger.isDebugEnabled()) {
- logger.debug("Created Terminal: {} ({}w x {}h)", Input.terminal.getClass().getSimpleName(),
- Input.terminal.getWidth(), Input.terminal.getHeight());
- }
-
- if (didFallbackE != null && !didFallbackE.getMessage().equals(Terminal.CONSOLE_ERROR_INIT)) {
- logger.error("Failed to construct terminal, falling back to unsupported.", didFallbackE);
- } else if (term instanceof UnsupportedTerminal) {
- logger.debug("Terminal is in UNSUPPORTED (best guess). Unable to support single key input. Only line input available.");
- }
-
- // echo and backspace
- term.setEchoEnabled(Console.ENABLE_ECHO);
- term.setInterruptEnabled(Console.ENABLE_INTERRUPT);
-
- Thread consoleThread = new Thread(new Runnable() {
- @Override
- public
- void run() {
- Input.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.
- Thread shutdownThread = new Thread() {
- @Override
- public
- void run() {
- // called when the JVM is shutting down.
- release0();
-
- try {
- terminal.restore();
- // 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();
- }
- }
- };
- shutdownThread.setName("Console Input Shutdown");
- Runtime.getRuntime().addShutdownHook(shutdownThread);
- }
-
- static
- void setInterruptEnabled(final boolean enabled) {
- terminal.setInterruptEnabled(enabled);
- }
-
- static
- void setEchoEnabled(final boolean enabled) {
- terminal.setEchoEnabled(enabled);
- }
-
- private static InputStream wrappedInputStream = new InputStream() {
- @Override
- public
- int read() throws IOException {
- return Input.read();
- }
-
- @Override
- public
- void close() throws IOException {
- Input.release0();
- }
- };
-
- static
- InputStream getInputStream() {
- return wrappedInputStream;
- }
-
- private
- Input() {
- }
-
- /**
- * Reads single character input from the console.
- *
- * @return -1 if no data or problems
- */
- static
- int read() {
- CharHolder holder;
-
- synchronized (inputLock) {
- // don't want to register a read() WHILE we are still processing the current input.
- // also adds it to the global list of char inputs
- holder = charInputPool.take();
- }
-
- synchronized (inputLockSingle) {
- try {
- inputLockSingle.wait();
- } catch (InterruptedException e) {
- return -1;
- }
- }
-
- char c = holder.character;
-
- // also clears and removes from the global list of char inputs
- charInputPool.put(holder);
-
- return c;
- }
-
- /**
- * @return empty char[] if no data or problems
- */
- static
- char[] readLineChars() {
- ByteBuffer2 buffer;
-
- synchronized (inputLock) {
- // don't want to register a readLine() WHILE we are still processing the current line info.
- // also adds it to the global list of line inputs
- buffer = lineInputPool.take();
- }
-
- synchronized (inputLockLine) {
- try {
- inputLockLine.wait();
- } catch (InterruptedException e) {
- return emptyLine;
- }
- }
-
- int len = buffer.position();
- if (len == 0) {
- return emptyLine;
- }
-
- buffer.rewind();
- char[] readChars = buffer.readChars(len / 2); // java always stores chars in 2 bytes
-
- // also clears and removes from the global list of line inputs
- lineInputPool.put(buffer);
-
- return readChars;
- }
-
- /**
- * Reads line input from the console
- *
- * @return empty char[] if no data
- */
- static
- char[] readLinePassword() {
- // don't bother in an IDE. it won't work.
- boolean echoEnabled = Console.ENABLE_ECHO;
- Console.ENABLE_ECHO = false;
- char[] readLine0 = readLineChars();
- Console.ENABLE_ECHO = echoEnabled;
-
- return readLine0;
- }
-
- /**
- * Reads a single line of characters, defined as everything before the 'ENTER' key is pressed
- * @return null if no data
- */
- static
- String readLine() {
- char[] line = Input.readLineChars();
- if (line == null) {
- return null;
- }
- return new String(line);
- }
-
-
-
- /**
- * releases any thread still waiting.
- */
- private static
- void release0() {
- synchronized (inputLockSingle) {
- inputLockSingle.notifyAll();
- }
-
- synchronized (inputLockLine) {
- inputLockLine.notifyAll();
- }
- }
-
- private static
- void run() {
- final Logger logger2 = logger;
- final PrintStream out = System.out;
- final char overWriteChar = ' ';
-
- Ansi ansi = null;
- int typedChar;
- char asChar;
-
- while ((typedChar = terminal.read()) != -1) {
- synchronized (inputLock) {
- // don't let anyone add a new reader while we are still processing the current actions
- asChar = (char) typedChar;
-
- if (logger2.isTraceEnabled()) {
- logger2.trace("READ: {} ({})", asChar, typedChar);
- }
-
- // notify everyone waiting for a character.
- synchronized (inputLockSingle) {
- // have to do readChar first (readLine has to deal with \b and \n
- for (CharHolder holder : charInputBuffers) {
- holder.character = asChar; // copy by value
- }
-
- inputLockSingle.notifyAll();
- }
-
- // now to handle readLine stuff
-
- // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed anyways.
- if (Console.ENABLE_BACKSPACE && asChar == '\b') {
- int position = 0;
- char[] overwrite = null;
-
- // clear ourself + one extra.
- for (ByteBuffer2 buffer : lineInputBuffers) {
- // size of the buffer BEFORE our backspace was typed
- int length = buffer.position();
- int amtToOverwrite = 4; // 2*2 backspace is always 2 chars (^?) * 2 because it's bytes
-
- if (length > 1) {
- char charAt = buffer.readChar(length - 2);
- amtToOverwrite += getPrintableCharacters(charAt);
-
- // delete last item in our buffer
- length -= 2;
- buffer.setPosition(length);
-
- // now figure out where the cursor is really at.
- // this is more memory friendly than buf.toString.length
- for (int i = 0; i < length; i += 2) {
- charAt = buffer.readChar(i);
- position += getPrintableCharacters(charAt);
- }
-
- position++;
- }
-
- overwrite = new char[amtToOverwrite];
- for (int i = 0; i < amtToOverwrite; i++) {
- overwrite[i] = overWriteChar;
- }
- }
-
- if (Console.ENABLE_ANSI && overwrite != null) {
- if (ansi == null) {
- ansi = Ansi.ansi();
- }
-
- // 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();
- }
- }
- else if (asChar == '\n') {
- // ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows, which we changed)
- synchronized (inputLockLine) {
- inputLockLine.notifyAll();
- }
- }
- else {
- // only append if we are not a new line.
- // our windows console PREVENTS us from returning '\r' (it truncates '\r\n', and returns just '\n')
- for (ByteBuffer2 buffer : lineInputBuffers) {
- buffer.writeChar(asChar);
- }
- }
- }
- }
- }
-
- private static final int PLUS_TWO_MAYBE = 128 + 32;
- private static final int PLUS_ONE = 128 + 127;
-
- /**
- * 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.
- */
- private 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 >= PLUS_TWO_MAYBE) {
- if (ch < PLUS_ONE) {
- // 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;
- }
-}
-
diff --git a/src/dorkbox/console/input/Input.java b/src/dorkbox/console/input/Input.java
new file mode 100644
index 0000000..f8cfcb5
--- /dev/null
+++ b/src/dorkbox/console/input/Input.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2010 dorkbox, llc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dorkbox.console.input;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+import dorkbox.console.Console;
+import dorkbox.util.OS;
+
+public
+class Input {
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Console.class);
+
+ public final static Terminal terminal;
+ static {
+ String type = Console.INPUT_CONSOLE_TYPE.toUpperCase(Locale.ENGLISH);
+
+ Throwable didFallbackE = null;
+ Terminal term;
+ try {
+ if (type.equals("UNIX")) {
+ term = new PosixTerminal();
+ }
+ else if (type.equals("WINDOWS")) {
+ term = new WindowsTerminal();
+ }
+ else if (type.equals("NONE")) {
+ term = new UnsupportedTerminal();
+ }
+ else {
+ // if these cannot be created, because we are in an IDE, an error will be thrown
+ if (OS.isWindows()) {
+ term = new WindowsTerminal();
+ }
+ else {
+ term = new PosixTerminal();
+ }
+ }
+ } catch (Exception e) {
+ didFallbackE = e;
+ term = new UnsupportedTerminal();
+ }
+
+ terminal = term;
+
+ boolean debugEnabled = logger.isDebugEnabled();
+ if (didFallbackE != null && !didFallbackE.getMessage().equals(Terminal.CONSOLE_ERROR_INIT)) {
+ logger.error("Failed to construct terminal, falling back to unsupported.", didFallbackE);
+ } else if (debugEnabled && term instanceof UnsupportedTerminal) {
+ logger.debug("Terminal is UNSUPPORTED (best guess). Unable to support single key input. Only line input available.");
+ } else if (debugEnabled) {
+ logger.debug("Created Terminal: {} ({}w x {}h)", Input.terminal.getClass().getSimpleName(),
+ Input.terminal.getWidth(), Input.terminal.getHeight());
+ }
+
+
+ if (term instanceof SupportedTerminal) {
+ // echo and backspace
+ term.setEchoEnabled(Console.ENABLE_ECHO);
+ term.setInterruptEnabled(Console.ENABLE_INTERRUPT);
+
+ Thread consoleThread = new Thread((SupportedTerminal)term);
+ consoleThread.setDaemon(true);
+ consoleThread.setName("Console Input Reader");
+ consoleThread.start();
+
+
+ // has to be NOT DAEMON thread, since it must run before the app closes.
+ // alternatively, shut everything down when the JVM closes.
+ Thread shutdownThread = new Thread() {
+ @Override
+ public
+ void run() {
+ // called when the JVM is shutting down.
+ terminal.close();
+
+ try {
+ terminal.restore();
+ // 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();
+ }
+ }
+ };
+ shutdownThread.setName("Console Input Shutdown");
+ Runtime.getRuntime().addShutdownHook(shutdownThread);
+ }
+ }
+
+ public final static InputStream wrappedInputStream = new InputStream() {
+ @Override
+ public
+ int read() throws IOException {
+ return terminal.read();
+ }
+
+ @Override
+ public
+ void close() throws IOException {
+ terminal.close();
+ }
+ };
+
+ private
+ Input() {
+ }
+}
+
diff --git a/src/dorkbox/console/input/PosixTerminal.java b/src/dorkbox/console/input/PosixTerminal.java
index 20f734f..a09cee6 100644
--- a/src/dorkbox/console/input/PosixTerminal.java
+++ b/src/dorkbox/console/input/PosixTerminal.java
@@ -29,7 +29,7 @@ import dorkbox.console.util.posix.Termios;
* This implementation should work for an reasonable POSIX system.
*/
public
-class PosixTerminal extends Terminal {
+class PosixTerminal extends SupportedTerminal {
private final Termios original = new Termios();
private Termios termInfo = new Termios();
@@ -112,14 +112,8 @@ class PosixTerminal extends Terminal {
}
@Override
- public final
- int read() {
- CLibraryPosix.read(0, inputRef, 1);
- return inputRef.getValue();
- }
-
- public
- void setEchoEnabled(final boolean enabled) {
+ protected
+ void doSetEchoEnabled(final boolean enabled) {
// have to re-get them, since flags change everything
if (CLibraryPosix.tcgetattr(0, this.termInfo) != 0) {
this.logger.error("Failed to get terminal info");
@@ -137,8 +131,9 @@ class PosixTerminal extends Terminal {
}
}
- public
- void setInterruptEnabled(final boolean enabled) {
+ @Override
+ protected
+ void doSetInterruptEnabled(final boolean enabled) {
// have to re-get them, since flags change everything
if (CLibraryPosix.tcgetattr(0, this.termInfo) != 0) {
this.logger.error("Failed to get terminal info");
@@ -155,4 +150,11 @@ class PosixTerminal extends Terminal {
this.logger.error("Can not set terminal flags");
}
}
+
+ @Override
+ protected final
+ int doRead() {
+ CLibraryPosix.read(0, inputRef, 1);
+ return inputRef.getValue();
+ }
}
diff --git a/src/dorkbox/console/input/SupportedTerminal.java b/src/dorkbox/console/input/SupportedTerminal.java
new file mode 100644
index 0000000..4d1b8ca
--- /dev/null
+++ b/src/dorkbox/console/input/SupportedTerminal.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2010 dorkbox, llc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dorkbox.console.input;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+
+import dorkbox.console.Console;
+import dorkbox.console.output.Ansi;
+import dorkbox.console.util.CharHolder;
+import dorkbox.util.FastThreadLocal;
+import dorkbox.util.bytes.ByteBuffer2;
+
+public abstract
+class SupportedTerminal extends Terminal implements Runnable {
+ private final PrintStream out = System.out;
+
+ private static final char[] emptyLine = new char[0];
+
+ protected final Object inputLockLine = new Object();
+ protected final Object inputLockSingle = new Object();
+
+ protected final List charInputBuffers = new ArrayList();
+ protected final FastThreadLocal charInput = new FastThreadLocal() {
+ @Override
+ public
+ CharHolder initialValue() {
+ return new CharHolder();
+ }
+ };
+
+ private final List lineInputBuffers = new ArrayList();
+ private final FastThreadLocal lineInput = new FastThreadLocal() {
+ @Override
+ public
+ ByteBuffer2 initialValue() {
+ return new ByteBuffer2(8, -1);
+ }
+ };
+
+ public
+ SupportedTerminal() {
+ }
+
+ /**
+ * Reads single character input from the console.
+ *
+ * @return -1 if no data or problems
+ */
+ public final
+ int read() {
+ CharHolder holder = charInput.get();
+
+ synchronized (inputLockSingle) {
+ // don't want to register a read() WHILE we are still processing the current input.
+ // also adds it to the global list of char inputs
+ charInputBuffers.add(holder);
+
+ try {
+ inputLockSingle.wait();
+ } catch (InterruptedException e) {
+ return -1;
+ }
+
+ char c = holder.character;
+
+ // also clears and removes from the global list of char inputs
+ charInputBuffers.remove(holder);
+ return c;
+ }
+ }
+
+ /**
+ * Reads a line of characters from the console as a character array, defined as everything before the 'ENTER' key is pressed
+ *
+ * @return empty char[] if no data
+ */
+ public final
+ char[] readLineChars() {
+ ByteBuffer2 buffer = lineInput.get();
+
+ synchronized (inputLockLine) {
+ // don't want to register a readLine() WHILE we are still processing the current line info.
+ // also adds it to the global list of line inputs
+ lineInputBuffers.add(buffer);
+
+ try {
+ inputLockLine.wait();
+ } catch (InterruptedException e) {
+ return emptyLine;
+ }
+
+ int len = buffer.position();
+ if (len == 0) {
+ return emptyLine;
+ }
+
+ buffer.rewind();
+ char[] readChars = buffer.readChars(len / 2); // java always stores chars in 2 bytes
+
+ // dump the chars in the buffer (safer for passwords, etc)
+ buffer.clearSecure();
+
+ // also clears and removes from the global list of line inputs
+ lineInputBuffers.remove(buffer);
+
+ return readChars;
+ }
+ }
+
+ /**
+ * releases any thread still waiting.
+ */
+ public final
+ void close() {
+ synchronized (inputLockSingle) {
+ inputLockSingle.notifyAll();
+ }
+
+ synchronized (inputLockLine) {
+ inputLockLine.notifyAll();
+ }
+ }
+
+ /**
+ * Reads a single character from whatever underlying stream is available.
+ */
+ protected abstract int doRead();
+
+ public
+ void run() {
+ final Logger logger2 = logger;
+ final char overWriteChar = ' ';
+
+ Ansi ansi = null;
+ int typedChar;
+ char asChar;
+
+ while ((typedChar = doRead()) != -1) {
+ // don't let anyone add a new reader while we are still processing the current actions
+ asChar = (char) typedChar;
+
+ if (logger2.isTraceEnabled()) {
+ logger2.trace("READ: {} ({})", asChar, typedChar);
+ }
+
+ // notify everyone waiting for a character.
+ synchronized (inputLockSingle) {
+ // have to do readChar first (readLine has to deal with \b and \n
+ for (CharHolder holder : charInputBuffers) {
+ holder.character = asChar; // copy by value
+ }
+
+ inputLockSingle.notifyAll();
+ }
+
+ // now to handle readLine stuff
+
+ // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed anyways.
+ if (Console.ENABLE_BACKSPACE && asChar == '\b') {
+ int position = 0;
+ char[] overwrite = null;
+
+ // clear ourself + one extra.
+ for (ByteBuffer2 buffer : lineInputBuffers) {
+ // size of the buffer BEFORE our backspace was typed
+ int length = buffer.position();
+ int amtToOverwrite = 4; // 2*2 backspace is always 2 chars (^?) * 2 because it's bytes
+
+ if (length > 1) {
+ char charAt = buffer.readChar(length - 2);
+ amtToOverwrite += getPrintableCharacters(charAt);
+
+ // delete last item in our buffer
+ length -= 2;
+ buffer.setPosition(length);
+
+ // now figure out where the cursor is really at.
+ // this is more memory friendly than buf.toString.length
+ for (int i = 0; i < length; i += 2) {
+ charAt = buffer.readChar(i);
+ position += getPrintableCharacters(charAt);
+ }
+
+ position++;
+ }
+
+ overwrite = new char[amtToOverwrite];
+ for (int i = 0; i < amtToOverwrite; i++) {
+ overwrite[i] = overWriteChar;
+ }
+ }
+
+ if (Console.ENABLE_ANSI && overwrite != null) {
+ if (ansi == null) {
+ ansi = Ansi.ansi();
+ }
+
+ // 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();
+
+ }
+ }
+ else if (asChar == '\n') {
+ // ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows, which we changed)
+ synchronized (inputLockLine) {
+ inputLockLine.notifyAll();
+ }
+ }
+ else {
+ // only append if we are not a new line.
+ // our windows console PREVENTS us from returning '\r' (it truncates '\r\n', and returns just '\n')
+ for (ByteBuffer2 buffer : lineInputBuffers) {
+ buffer.writeChar(asChar);
+ }
+ }
+ }
+ }
+
+ private static final int PLUS_TWO_MAYBE = 128 + 32;
+ private static final int PLUS_ONE = 128 + 127;
+
+ /**
+ * 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.
+ */
+ private 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 >= PLUS_TWO_MAYBE) {
+ if (ch < PLUS_ONE) {
+ // 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;
+ }
+}
diff --git a/src/dorkbox/console/input/Terminal.java b/src/dorkbox/console/input/Terminal.java
index aad3e05..8baa0c6 100644
--- a/src/dorkbox/console/input/Terminal.java
+++ b/src/dorkbox/console/input/Terminal.java
@@ -17,18 +17,27 @@ package dorkbox.console.input;
import java.io.IOException;
+import dorkbox.console.Console;
+
+@SuppressWarnings("unused")
public abstract
class Terminal {
- public static final String CONSOLE_ERROR_INIT = "Unable to initialize the input console.";
- protected static final int DEFAULT_WIDTH = 80;
- protected static final int DEFAULT_HEIGHT = 24;
- protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
+ static final String CONSOLE_ERROR_INIT = "Unable to initialize the input console.";
+
+ static final int DEFAULT_WIDTH = 80;
+ static final int DEFAULT_HEIGHT = 24;
+ final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
- protected
Terminal() {
}
+ abstract
+ void doSetInterruptEnabled(final boolean enabled);
+
+ protected abstract
+ void doSetEchoEnabled(final boolean enabled);
+
public abstract
void restore() throws IOException;
@@ -38,16 +47,72 @@ class Terminal {
public abstract
int getHeight();
- // NOT THREAD SAFE
- public abstract
- void setEchoEnabled(final boolean enabled);
-
- public abstract
- void setInterruptEnabled(final boolean enabled);
+ /**
+ * Enables or disables CTRL-C behavior in the console
+ */
+ public final
+ void setInterruptEnabled(final boolean enabled) {
+ Console.ENABLE_INTERRUPT = enabled;
+ doSetInterruptEnabled(enabled);
+ }
/**
- * @return a character from whatever underlying input method the terminal has available.
+ * Enables or disables character echo to stdout
+ */
+ public final
+ void setEchoEnabled(final boolean enabled) {
+ Console.ENABLE_ECHO = enabled;
+ doSetEchoEnabled(enabled);
+ }
+
+ /**
+ * Reads single character input from the console.
+ *
+ * @return -1 if no data or problems
*/
public abstract
int read();
+
+ /**
+ * Reads a line of characters from the console as a character array, defined as everything before the 'ENTER' key is pressed
+ *
+ * @return empty char[] if no data
+ */
+ public abstract
+ char[] readLineChars();
+
+ /**
+ * Reads a single line of characters, defined as everything before the 'ENTER' key is pressed
+ * @return null if no data
+ */
+ public
+ String readLine() {
+ char[] line = readLineChars();
+ if (line == null) {
+ return null;
+ }
+ return new String(line);
+ }
+
+ /**
+ * Reads a line of characters from the console as a character array, defined as everything before the 'ENTER' key is pressed
+ *
+ * @return empty char[] if no data
+ */
+ public
+ char[] readLinePassword() {
+ // don't bother in an IDE. it won't work.
+ boolean echoEnabled = Console.ENABLE_ECHO;
+ Console.ENABLE_ECHO = false;
+ char[] readLine0 = readLineChars();
+ Console.ENABLE_ECHO = echoEnabled;
+
+ return readLine0;
+ }
+
+ /**
+ * releases any thread still waiting.
+ */
+ public abstract
+ void close();
}
diff --git a/src/dorkbox/console/input/UnsupportedTerminal.java b/src/dorkbox/console/input/UnsupportedTerminal.java
index fb45c83..29902ac 100644
--- a/src/dorkbox/console/input/UnsupportedTerminal.java
+++ b/src/dorkbox/console/input/UnsupportedTerminal.java
@@ -18,19 +18,49 @@ package dorkbox.console.input;
import java.io.IOException;
import java.io.InputStream;
+import dorkbox.util.FastThreadLocal;
import dorkbox.util.bytes.ByteBuffer2;
+@SuppressWarnings("Duplicates")
public
class UnsupportedTerminal extends Terminal {
- private final ByteBuffer2 buffer = new ByteBuffer2(8, -1);
- private final InputStream in = System.in;
- private int readerCount = -1;
+ private static final char[] newLine;
+ static {
+ newLine = new char[1];
+ newLine[0] = '\n';
+ }
+
+ private final FastThreadLocal buffer = new FastThreadLocal() {
+ @Override
+ public
+ ByteBuffer2 initialValue() {
+ return new ByteBuffer2(8, -1);
+ }
+ };
+
+ private final FastThreadLocal readCount = new FastThreadLocal() {
+ @Override
+ public
+ Integer initialValue() {
+ return 0;
+ }
+ };
public
UnsupportedTerminal() {
}
+ @Override
+ protected
+ void doSetInterruptEnabled(final boolean enabled) {
+ }
+
+ @Override
+ protected
+ void doSetEchoEnabled(final boolean enabled) {
+ }
+
@Override
public final
void restore() {
@@ -48,52 +78,99 @@ class UnsupportedTerminal extends Terminal {
return 0;
}
- @Override
+ /**
+ * Reads single character input from the console.
+ *
+ * @return -1 if no data or problems
+ */
public
- void setEchoEnabled(final boolean enabled) {
- }
-
- @Override
- public
- void setInterruptEnabled(final boolean enabled) {
- }
-
- @Override
- public final
int read() {
- // if we are reading data (because we are in IDE mode), we want to return ALL the chars of the line!
-
- // so, 'readerCount' is REALLY the index at which we return letters (until the whole string is returned)
- if (this.readerCount == -1) {
+ // so, 'readCount' is REALLY the index at which we return letters (until the whole string is returned)
+ ByteBuffer2 buffer = this.buffer.get();
+ if (this.readCount.get() == 0) {
// we have to wait for more data.
try {
- InputStream sysIn = this.in;
+ InputStream sysIn = System.in;
int read;
char asChar;
- this.buffer.clearSecure();
+ buffer.clearSecure();
while ((read = sysIn.read()) != -1) {
asChar = (char) read;
if (asChar == '\n') {
- this.readerCount = this.buffer.position();
- this.buffer.rewind();
+ int position = buffer.position();
+ this.readCount.set(position);
+ if (position == 0) {
+ // only send a NEW LINE if it was the ONLY thing pressed (this is to MOST ACCURATELY simulate single char input
+ return '\n';
+ }
+
+ buffer.rewind();
break;
}
else {
- this.buffer.writeChar(asChar);
+ buffer.writeChar(asChar);
}
}
} catch (IOException ignored) {
}
}
- // EACH thread will have it's own count!
- if (this.readerCount == this.buffer.position()) {
- this.readerCount = -1;
- return '\n';
+// // EACH thread will have it's own count!
+// if (this.readCount == buffer.position()) {
+// this.readCount = -1;
+// return '\n';
+// }
+// else {
+// return buffer.readChar();
+// }
+ readCount.set(this.readCount.get() - 2); // 2 bytes per char in the stream
+ return buffer.readChar();
+ }
+
+ /**
+ * Reads a line of characters from the console as a character array, defined as everything before the 'ENTER' key is pressed
+ *
+ * @return empty char[] if no data
+ */
+ public
+ char[] readLineChars() {
+ ByteBuffer2 buffer = this.buffer.get();
+ buffer.clearSecure();
+
+ int position = 0;
+ // we have to wait for more data.
+ try {
+ final InputStream sysIn = System.in;
+ int read;
+ char asChar;
+
+ while ((read = sysIn.read()) != -1) {
+ asChar = (char) read;
+
+ if (asChar == '\n') {
+ buffer.rewind();
+ break;
+ }
+ buffer.writeChar(asChar);
+ position = buffer.position();
+ }
+ } catch (IOException ignored) {
}
- else {
- return this.buffer.readChar();
+
+ if (position == 0) {
+ // only send a NEW LINE if it was the ONLY thing pressed (this is to MOST ACCURATELY simulate single char input
+ return newLine;
}
+
+ char[] chars = buffer.readChars(position/2); // 2 bytes per char
+ buffer.clearSecure();
+
+ return chars;
+ }
+
+ @Override
+ public
+ void close() {
}
}
diff --git a/src/dorkbox/console/input/WindowsTerminal.java b/src/dorkbox/console/input/WindowsTerminal.java
index e44868c..2a3b94b 100644
--- a/src/dorkbox/console/input/WindowsTerminal.java
+++ b/src/dorkbox/console/input/WindowsTerminal.java
@@ -35,7 +35,7 @@ import dorkbox.console.util.windows.Kernel32;
* Terminal implementation for Microsoft Windows.
*/
public
-class WindowsTerminal extends Terminal {
+class WindowsTerminal extends SupportedTerminal {
// Console mode constants copied wincon.h.
// There are OTHER options, however they DO NOT work with unbuffered input or we just don't care about them.
@@ -113,15 +113,15 @@ class WindowsTerminal extends Terminal {
}
@Override
- public
- void setEchoEnabled(final boolean enabled) {
+ protected
+ void doSetEchoEnabled(final boolean enabled) {
// only way to do this, console modes DO NOT work
echoEnabled = enabled;
}
@Override
- public
- void setInterruptEnabled(final boolean enabled) {
+ protected
+ void doSetInterruptEnabled(final boolean enabled) {
IntByReference mode = new IntByReference();
GetConsoleMode(console, mode);
@@ -138,8 +138,8 @@ class WindowsTerminal extends Terminal {
}
@Override
- public final
- int read() {
+ protected final
+ int doRead() {
int input = readInput();
if (echoEnabled) {
diff --git a/src/dorkbox/console/output/Ansi.java b/src/dorkbox/console/output/Ansi.java
index efcd667..3627940 100644
--- a/src/dorkbox/console/output/Ansi.java
+++ b/src/dorkbox/console/output/Ansi.java
@@ -32,9 +32,16 @@ package dorkbox.console.output;
import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_RESET;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
import java.util.ArrayList;
import dorkbox.console.Console;
+import dorkbox.console.util.posix.CLibraryPosix;
+import dorkbox.console.util.windows.Kernel32;
+import dorkbox.util.OS;
/**
* Provides a fluent API for generating ANSI escape sequences and providing access to streams that support it.
@@ -44,12 +51,36 @@ import dorkbox.console.Console;
* @author dorkbox, llc
* @author Hiram Chirino
*/
-@SuppressWarnings("unused")
+@SuppressWarnings({"unused", "WeakerAccess"})
public
class Ansi {
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Console.class);
+
+
+ private static final PrintStream original_out = System.out;
+ private static final PrintStream original_err = System.err;
+
+ public static final PrintStream out = createPrintStream(original_out, 1); // STDOUT_FILENO;
+ public static final PrintStream err = createPrintStream(original_err, 2); // STDERR_FILENO
+
static {
// make SURE that our console in/out/err are correctly setup BEFORE accessing methods in this class
Console.getVersion();
+
+ System.setOut(out);
+ System.setErr(err);
+
+ // don't forget we have to shut down the ansi console as well
+ Thread shutdownThread = new Thread() {
+ @Override
+ public
+ void run() {
+ // called when the JVM is shutting down.
+ restoreSystemStreams();
+ }
+ };
+ shutdownThread.setName("Console ANSI stream Shutdown");
+ Runtime.getRuntime().addShutdownHook(shutdownThread);
}
private static final String NEW_LINE = System.getProperty("line.separator");
@@ -57,6 +88,17 @@ class Ansi {
private final StringBuilder builder;
private final ArrayList attributeOptions = new ArrayList(8);
+
+ /**
+ * Restores System.err/out PrintStreams to their ORIGINAL configuration. Useful when using ANSI functionality but do not want to
+ * hook into the system.
+ */
+ public static
+ void restoreSystemStreams() {
+ System.setOut(original_out);
+ System.setErr(original_err);
+ }
+
/**
* Creates a new Ansi object
*/
@@ -872,4 +914,89 @@ class Ansi {
builder.append(command);
return this;
}
+
+ private static boolean isXterm() {
+ String term = System.getenv("TERM");
+ return "xterm".equalsIgnoreCase(term);
+ }
+
+ private static
+ PrintStream createPrintStream(final OutputStream stream, final int fileno) {
+ String type = fileno == 1 ? "OUT" : "ERR";
+
+ if (!Console.ENABLE_ANSI) {
+ // Use the ANSIOutputStream to strip out the ANSI escape sequences.
+ return getStripPrintStream(stream, type);
+ }
+
+ if (!isXterm()) {
+ if (OS.isWindows()) {
+ // check if windows10+ (which natively supports ANSI)
+ if (Kernel32.isWindows10OrGreater()) {
+ // Just wrap it up so that when we get closed, we reset the attributes.
+ return defaultPrintStream(stream, type);
+ }
+
+ // On windows we know the console does not interpret ANSI codes..
+ try {
+ PrintStream printStream = new PrintStream(new WindowsAnsiOutputStream(stream, fileno));
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Created a Windows ANSI PrintStream for {}", type);
+ }
+
+ return printStream;
+ } catch (Throwable ignore) {
+ // this happens when JNA is not in the path.. or
+ // this happens when the stdout is being redirected to a file.
+ // this happens when the stdout is being redirected to different console.
+ }
+
+ // Use the ANSIOutputStream to strip out the ANSI escape sequences.
+ if (!Console.FORCE_ENABLE_ANSI) {
+ return getStripPrintStream(stream, type);
+ }
+ } else {
+ // We must be on some unix variant..
+ try {
+ // If we can detect that stdout is not a tty.. then setup to strip the ANSI sequences..
+ if (!Console.FORCE_ENABLE_ANSI && CLibraryPosix.isatty(fileno) == 0) {
+ return getStripPrintStream(stream, type);
+ }
+ } catch (Throwable ignore) {
+ // These errors happen if the JNI lib is not available for your platform.
+ }
+ }
+ }
+
+ // By default we assume the terminal can handle ANSI codes.
+ // Just wrap it up so that when we get closed, we reset the attributes.
+ return defaultPrintStream(stream, type);
+ }
+
+ private static
+ PrintStream getStripPrintStream(final OutputStream stream, final String type) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Created a strip-ANSI PrintStream for {}", type);
+ }
+
+ return new PrintStream(new AnsiOutputStream(stream));
+ }
+
+ private static
+ PrintStream defaultPrintStream(final OutputStream stream, final String type) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Created ANSI PrintStream for {}", type);
+ }
+
+ return new PrintStream(new FilterOutputStream(stream) {
+ @Override
+ public
+ void close() throws IOException {
+ write(AnsiOutputStream.RESET_CODE);
+ flush();
+ super.close();
+ }
+ });
+ }
}