package org.xmlsh.core.io; import static org.xmlsh.util.Util.stringConcat; import java.io.BufferedReader; import java.io.Console; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.StringReader; import java.nio.CharBuffer; import java.util.Arrays; import jline.Terminal; import jline.TerminalFactory; import jline.console.ConsoleReader; import jline.Logger.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.xmlsh.core.InputPort; import org.xmlsh.util.FileUtils; import org.xmlsh.util.Util; /* * Singleton Console support for all shells * Console interface for interactive input , * provides a structured IO as well as stream IO */ public class ShellConsole { static Logger mLogger = LogManager.getLogger(); private static class JLineLogger implements jline.Logger { static org.apache.logging.log4j.Level[] levels = { org.apache.logging.log4j.Level.TRACE , org.apache.logging.log4j.Level.DEBUG , org.apache.logging.log4j.Level.INFO, org.apache.logging.log4j.Level.WARN, org.apache.logging.log4j.Level.ERROR }; //Sstatic Logger mLogger = LogManager.getLogger("jline.internal.Log"); @Override public void log( jline.Logger.Level level, Object... messages) { org.apache.logging.log4j.Level l4l = levels[level.ordinal()] ; if( !mLogger.isEnabled(l4l)) return ; if( messages.length > 0 && messages[messages.length-1] instanceof Throwable ){ Throwable t = (Throwable) messages[messages.length-1]; messages = Arrays.copyOf(messages, messages.length-1); if( messages.length > 0) mLogger.log( l4l, Util.stringJoin("{}"," ",messages.length) , messages , t ); else mLogger.log(l4l,t); } else mLogger.log(l4l, Util.stringJoin("{}"," ",messages.length) , messages); } } private static volatile ShellConsole _instance = null; private Console sJavaConsole; // JRE 6 Console if available private Terminal sJLineTerminal; // JLine 2 Console Reader if available private InputStream sSystemIn; // System.in private PrintStream sSystemOut; // System.out private PrintStream sSystemErr; // System.err private String sEncoding; private ConsoleReader jJLineConsole; /* * private Reader mReader ; * * * public ShellReader getReader() { * mLogger.entry(); * if( sJavaConsole != null ) * return new ShellConsoleReader(ps1,ps2) ; * else { * return new ShellSystemReader(ps1,ps2); * } * * } */ public static ShellConsole getConsole() { mLogger.entry(); if (_instance == null) { synchronized (ShellConsole.class) { if (_instance == null) _instance = new ShellConsole(); } } return _instance; } public static boolean hasConsole() { return mLogger.exit(getConsole().sJavaConsole != null); } private ShellConsole() { mLogger.entry(); sEncoding = FileUtils.getSystemTextEncoding(); sJavaConsole = System.console(); // If no console dont use jLine for now // cygwin note: // if running cygwin under cmd.exe then use do NOT use jline // Java does not detect a Console and TERM=cygwin // if running cygwin under mintty then do USE jline // Java does detect a console and TERM=xterm String sUseJLine = System.getProperty("xmlsh.jline"); boolean useJLine = (sUseJLine != null ? Util.parseBoolean(sUseJLine) : (sJavaConsole != null )); mLogger.debug("Has Console: {} xmlsh.jline: {} useJLine: {}", sJavaConsole != null , sUseJLine, useJLine); if ( useJLine ){ mLogger.debug("Setting jline logger"); jline.internal.Log.setLogger( new JLineLogger() ); sJLineTerminal = TerminalFactory.get(); jline.internal.Log.trace("Trace event from xmlsh"); } if (sJLineTerminal != null) { mLogger.trace("Using JLine Terminal"); try { jJLineConsole = new ConsoleReader("xmlsh", System.in, System.out, sJLineTerminal, sEncoding ); mLogger.trace("Using jline ConsoleReader"); } catch (IOException e) { mLogger.catching(e); mLogger.debug("Failed to load jline console"); jJLineConsole = null; sJLineTerminal = null; } } sSystemIn = System.in; sSystemOut = System.out; sSystemErr = System.err; if (sSystemOut == null) sSystemOut = sSystemErr; if (sSystemErr == null && sSystemOut != null) sSystemErr = sSystemOut; } public boolean hasInput() { if (sJavaConsole != null) return true; if (jJLineConsole != null) return true; return sSystemIn != null; } public boolean hasOutput() { if (sJavaConsole != null) return true; if (jJLineConsole != null) return true; return sSystemOut != null; } public boolean hasErr() { if (sJavaConsole != null) return true; if (jJLineConsole != null) return true; return sSystemErr != null; } private class ShellConsoleReader extends ShellReader { ShellConsoleReader(IShellPrompt prompt) { super(prompt); mLogger.entry(); } @Override protected String readLine(String prompt) { return sJavaConsole.readLine(prompt); } public InputPort getInputPort() { return new StreamInputPort(sSystemIn, null, true); } public OutputPort getOutputPort() { return new StreamOutputPort(sSystemOut, false, true); } public OutputPort getErrorPort() { return new StreamOutputPort(sSystemErr, false, true); } } private class ShellJLineReader extends ShellReader { ShellJLineReader(IShellPrompt prompt) { super(prompt); mLogger.entry(prompt); } @Override protected String readLine(String prompt) throws IOException { return jJLineConsole.readLine(prompt); } public InputPort getInputPort() { mLogger.entry(); // TODO: Make a ReaderInputPort InputStream in = new InputStream() { private byte[] line = null; private int pos = 0; // set to -1 for EOF @Override public int read() throws IOException { if( pos < 0 ) return -1; if( line == null || pos >= line.length ){ pos = 0; line = null; String sline = jJLineConsole.readLine(""); if( sline == null ) return (pos = -1) ; if( sline.isEmpty() ) line = Util.mNewLineBytes; else line = (sline+Util.getNewlineString()).getBytes(sEncoding); } assert( line != null ); return line[pos++]; } }; return new StreamInputPort(in, null, true); } @Override public OutputPort getOutputPort() { OutputStream out = new OutputStream() { private char[] onechar = new char[1]; @Override public void write(int b) throws IOException { if( b == '\r' ) return ; if( b == '\n' ) jJLineConsole.println(); onechar[0] = (char)b ; jJLineConsole.print( CharBuffer.wrap(onechar)); } @Override public void write(byte[] b, int off, int len) throws IOException { mLogger.entry( b , off , len ); int last = off; int i = off; while( i < len ){ if( b[i] == '\n'){ if( i == last ) jJLineConsole.println(); else jJLineConsole.println( new String(b,last,i-last,sEncoding)); last = ++i; } else i++; } if( last < i ) jJLineConsole.print( new String(b,last,i-last,sEncoding)); } @Override public void flush() throws IOException { jJLineConsole.flush(); } @Override public void close() throws IOException { flush(); super.close(); } }; return new StreamOutputPort(out, false, true); } @Override public OutputPort getErrorPort() { return getOutputPort(); } } public ShellReader newReader(boolean bUseConsole, IShellPrompt prompt) { mLogger.entry(); if (this.jJLineConsole != null){ mLogger.debug("Using ShellJLineReader"); return new ShellJLineReader(prompt); } if (sJavaConsole != null){ mLogger.debug("Using ShellConsoleReader"); return new ShellConsoleReader(prompt); } else { mLogger.debug("Using ShellSystemReader"); return new ShellReader.ShellSystemReader(sSystemIn, sSystemOut, prompt); } } }