/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.shell; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.TreeMap; import javax.naming.NameNotFoundException; import org.apache.log4j.Logger; import org.jnode.driver.console.CompletionInfo; import org.jnode.driver.console.ConsoleEvent; import org.jnode.driver.console.ConsoleListener; import org.jnode.driver.console.ConsoleManager; import org.jnode.driver.console.InputHistory; import org.jnode.driver.console.TextConsole; import org.jnode.driver.console.spi.ConsoleWriter; import org.jnode.driver.console.textscreen.KeyboardReader; import org.jnode.naming.InitialNaming; import org.jnode.shell.alias.AliasManager; import org.jnode.shell.alias.NoSuchAliasException; import org.jnode.shell.help.Help; import org.jnode.shell.help.HelpException; import org.jnode.shell.help.HelpFactory; import org.jnode.shell.io.CommandIO; import org.jnode.shell.io.CommandInput; import org.jnode.shell.io.CommandInputOutput; import org.jnode.shell.io.CommandOutput; import org.jnode.shell.io.FanoutWriter; import org.jnode.shell.io.NullInputStream; import org.jnode.shell.io.NullOutputStream; import org.jnode.shell.io.ShellConsoleReader; import org.jnode.shell.io.ShellConsoleWriter; import org.jnode.shell.isolate.IsolateCommandInvoker; import org.jnode.shell.proclet.ProcletCommandInvoker; import org.jnode.shell.syntax.ArgumentBundle; import org.jnode.shell.syntax.CommandSyntaxException; import org.jnode.shell.syntax.SyntaxBundle; import org.jnode.shell.syntax.SyntaxManager; import org.jnode.shell.syntax.CommandSyntaxException.Context; import org.jnode.util.ReaderInputStream; import org.jnode.util.SystemInputStream; import org.jnode.vm.VmExit; import org.jnode.vm.VmSystem; /** * This is the primary implementation of the {@link Shell} interface. In * addition to core Shell functionality, this implementation supports * command-line completion, command and application input history, * switch-able interpreters and invokers, and initialization scripting. * * @author epr * @author Fabien DUMINY * @author crawley@jnode.org * @author chris boertien */ public class CommandShell implements Runnable, Shell, ConsoleListener { public static final String PROMPT_PROPERTY_NAME = "jnode.prompt"; public static final String INTERPRETER_PROPERTY_NAME = "jnode.interpreter"; public static final String INVOKER_PROPERTY_NAME = "jnode.invoker"; public static final String CMDLINE_PROPERTY_NAME = "jnode.cmdline"; public static final String DEBUG_PROPERTY_NAME = "jnode.debug"; public static final String DEBUG_DEFAULT = "false"; public static final String HISTORY_PROPERTY_NAME = "jnode.history"; public static final String HISTORY_DEFAULT = "true"; public static final String USER_HOME_PROPERTY_NAME = "user.home"; public static final String JAVA_HOME_PROPERTY_NAME = "java.home"; public static final String DIRECTORY_PROPERTY_NAME = "user.dir"; public static final String INITIAL_INVOKER = "proclet"; // The Emu-mode invoker must be something that runs on the dev't platform; // e.g. not 'isolate' or 'proclet'. private static final String EMU_INVOKER = "thread"; public static final String INITIAL_INTERPRETER = "redirecting"; public static final String FALLBACK_INVOKER = "default"; public static final String FALLBACK_INTERPRETER = "default"; private static String DEFAULT_PROMPT = "JNode $P$G"; private static final String COMMAND_KEY = "cmd="; /** * My logger */ private static final Logger log = Logger.getLogger(CommandShell.class); private CommandInput cin; private CommandOutput cout; private CommandOutput cerr; private Reader in; private PrintWriter outPW; private PrintWriter errPW; private AliasManager aliasMgr; private SyntaxManager syntaxMgr; /** * Keeps a reference to the console this CommandShell is using. */ private TextConsole console; /** * Contains the archive of commands. */ private InputHistory commandHistory = new InputHistory(); /** * Contains the application input history for the current thread. */ private static InheritableThreadLocal<InputHistory> applicationHistory = new InheritableThreadLocal<InputHistory>(); /** * When {@code true}, command input characters are being requested / read * from the console. This controls whether command or application history * is used, and whether command completion may be active. */ private boolean readingCommand; /** * Contains the last command entered */ private String lastCommandLine = ""; /** * Contains the last application input line entered */ private String lastInputLine = ""; private SimpleCommandInvoker invoker; private CommandInterpreter interpreter; private HashMap<String, String> propertyMap; private CompletionInfo completion; private boolean historyEnabled; private boolean debugEnabled; private boolean exited = false; private Thread ownThread; private boolean bootShell; public TextConsole getConsole() { return console; } public static void main(String[] args) throws NameNotFoundException, ShellException { CommandShell shell = new CommandShell( (TextConsole) (InitialNaming.lookup(ConsoleManager.NAME)).getFocus()); for (String arg : args) { if ("boot".equals(arg)) { shell.bootShell = true; break; } } shell.run(); } public CommandShell(TextConsole cons) throws ShellException { this(cons, false); } public CommandShell(TextConsole cons, boolean emu) throws ShellException { debugEnabled = true; try { console = cons; KeyboardReader in = (KeyboardReader) console.getIn(); ConsoleWriter out = (ConsoleWriter) console.getOut(); ConsoleWriter err = (ConsoleWriter) console.getErr(); if (in == null) { throw new ShellException("console input stream is null"); } setupStreams(new ShellConsoleReader(in), new ShellConsoleWriter(out), new ShellConsoleWriter(err)); SystemInputStream.getInstance().initialize(new ReaderInputStream(in)); cons.setCompleter(this); console.addConsoleListener(this); aliasMgr = ShellUtils.getAliasManager().createAliasManager(); syntaxMgr = ShellUtils.getSyntaxManager().createSyntaxManager(); propertyMap = initShellProperties(emu); } catch (NameNotFoundException ex) { throw new ShellException("Cannot find required resource", ex); } catch (Exception ex) { ex.printStackTrace(); } } /** * Create a CommandShell that doesn't use a TextConsole or the ConsoleManager * for use in the TestHarness. * * @throws ShellException */ protected CommandShell() throws ShellException { debugEnabled = true; try { setupStreams(new InputStreamReader(System.in), new OutputStreamWriter(System.out), new OutputStreamWriter(System.err)); aliasMgr = ShellUtils.getAliasManager().createAliasManager(); syntaxMgr = ShellUtils.getSyntaxManager().createSyntaxManager(); propertyMap = initShellProperties(true); } catch (NameNotFoundException ex) { throw new ShellException("Cannot find required resource", ex); } catch (Exception ex) { throw new ShellFailureException("CommandShell initialization failed", ex); } } /** * This constructor builds a partial command shell for test purposes only. * * @param aliasMgr test framework supplies an alias manager * @param syntaxMgr test framework supplies a syntax manager */ protected CommandShell(AliasManager aliasMgr, SyntaxManager syntaxMgr) { this.debugEnabled = true; this.aliasMgr = aliasMgr; this.syntaxMgr = syntaxMgr; propertyMap = initShellProperties(true); setupStreams( new InputStreamReader(System.in), new OutputStreamWriter(System.out), new OutputStreamWriter(System.err)); } private HashMap<String, String> initShellProperties(boolean emu) { HashMap<String, String> map = new HashMap<String, String>(); map.put(PROMPT_PROPERTY_NAME, DEFAULT_PROMPT); map.put(DEBUG_PROPERTY_NAME, DEBUG_DEFAULT); map.put(HISTORY_PROPERTY_NAME, HISTORY_DEFAULT); map.put(INVOKER_PROPERTY_NAME, emu ? EMU_INVOKER : INITIAL_INVOKER); map.put(INTERPRETER_PROPERTY_NAME, INITIAL_INTERPRETER); return map; } private void setupStreams(Reader in, Writer out, Writer err) { this.cout = new CommandOutput(out); this.cerr = new CommandOutput(err); this.cin = new CommandInput(in); this.in = cin.getReader(); this.outPW = cout.getPrintWriter(); this.errPW = cerr.getPrintWriter(); } /** * Run this shell until exit. * * @see java.lang.Runnable#run() */ public void run() { // Here, we are running in the CommandShell (main) Thread // so, we can register ourself as the current shell // (it will also be the current shell for all children Thread) try { configureShell(); } catch (ShellException ex) { throw new ShellFailureException("Shell setup failure", ex); } // Run commands from the JNode command line first final String cmdLine = System.getProperty(CMDLINE_PROPERTY_NAME, ""); final StringTokenizer tok = new StringTokenizer(cmdLine); while (tok.hasMoreTokens()) { final String e = tok.nextToken(); try { if (e.startsWith(COMMAND_KEY)) { final String cmd = e.substring(COMMAND_KEY.length()); runCommand(cmd); } } catch (Throwable ex) { errPW.println("Error while processing bootarg commands: " + ex.getMessage()); stackTrace(ex); } } if (bootShell) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { final String java_home = System.getProperty(JAVA_HOME_PROPERTY_NAME, ""); final String name = "jnode.ini"; final File jnode_ini = new File(java_home + '/' + name); try { if (jnode_ini.exists()) { runCommandFile(jnode_ini, null, null); } else if (getClass().getResource(name) != null) { runCommandResource(name, null); } } catch (ShellException ex) { errPW.println("Error while processing " + jnode_ini + ": " + ex.getMessage()); stackTrace(ex); } return null; } }); } AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { final String user_home = System.getProperty(USER_HOME_PROPERTY_NAME, ""); final String name = "shell.ini"; final File shell_ini = new File(user_home + '/' + name); try { if (shell_ini.exists()) { runCommandFile(shell_ini, null, null); } else if (getClass().getResource(name) != null) { runCommandResource(name, null); } } catch (ShellException ex) { errPW.println("Error while processing " + shell_ini + ": " + ex.getMessage()); stackTrace(ex); } return null; } }); while (!isExited() && !VmSystem.isShuttingDown()) { CommandShellReader reader = null; try { clearEof(); try { // Each interactive command is launched with a fresh history // for input completion applicationHistory.set(new InputHistory()); reader = new CommandShellReader(this, interpreter, outPW, in); interpreter.interpret(this, reader, false, null, null); } finally { applicationHistory.set(null); } } catch (ShellException ex) { diagnose(ex, null); } catch (VmExit ex) { // This should only happen if the interpreter wants the shell to // exit. The interpreter will typically intercept any VmExits // resulting from commands calling AbstractCommand.exit(int). exit(); } catch (Throwable ex) { errPW.println("Uncaught exception while processing command(s): " + ex.getMessage()); stackTrace(ex); try { Thread.sleep(100000); } catch (InterruptedException ex2) { //ignore } } finally { if (reader != null) { for (String line : reader.getLines()) { addToCommandHistory(line); } } } } } public void configureShell() throws ShellException { try { ShellUtils.getShellManager().registerShell(this); ShellUtils.registerCommandInvoker(DefaultCommandInvoker.FACTORY); ShellUtils.registerCommandInvoker(ThreadCommandInvoker.FACTORY); ShellUtils.registerCommandInvoker(ProcletCommandInvoker.FACTORY); ShellUtils.registerCommandInvoker(IsolateCommandInvoker.FACTORY); ShellUtils.registerCommandInterpreter(DefaultInterpreter.FACTORY); ShellUtils.registerCommandInterpreter(RedirectingInterpreter.FACTORY); } catch (NameNotFoundException ex) { throw new ShellFailureException( "Bailing out: fatal error during CommandShell configuration", ex); } try { setupFromProperties(); } catch (Throwable ex) { errPW.println("Problem shell configuration"); errPW.println(ex.getMessage()); stackTrace(ex); errPW.println("Retrying shell configuration with fallback invoker/interpreter settings"); propertyMap.put(INVOKER_PROPERTY_NAME, FALLBACK_INVOKER); propertyMap.put(INTERPRETER_PROPERTY_NAME, FALLBACK_INTERPRETER); try { setupFromProperties(); } catch (Throwable ex2) { throw new ShellFailureException( "Bailing out: fatal error during CommandShell configuration", ex2); } } // Now become interactive ownThread = Thread.currentThread(); } protected void setReadingCommand(boolean readingCommand) { this.readingCommand = readingCommand; } @Override public String getProperty(String propName) { return propertyMap.get(propName); } @Override public void removeProperty(String key) throws ShellException { if (key.equals(INTERPRETER_PROPERTY_NAME) || key.equals(INVOKER_PROPERTY_NAME) || key.equals(DEBUG_PROPERTY_NAME) || key.equals(PROMPT_PROPERTY_NAME) || key.equals(HISTORY_PROPERTY_NAME)) { throw new ShellException("Property '" + key + "' cannot be removed"); } propertyMap.remove(key); } @Override public void setProperty(String propName, String value) throws ShellException { String oldValue = propertyMap.get(propName); propertyMap.put(propName, value); try { setupFromProperties(); } catch (ShellException ex) { // Try to undo the change propertyMap.put(propName, oldValue); try { setupFromProperties(); } catch (ShellException ex2) { // This may be our only chance to diagnose the original exception .... errPW.println(ex.getMessage()); stackTrace(ex); throw new ShellFailureException("Failed to revert shell properties", ex2); } throw ex; } } @Override public TreeMap<String, String> getProperties() { return new TreeMap<String, String>(propertyMap); } private void setupFromProperties() throws ShellException { setCommandInvoker(propertyMap.get(INVOKER_PROPERTY_NAME)); setCommandInterpreter(propertyMap.get(INTERPRETER_PROPERTY_NAME)); debugEnabled = Boolean.parseBoolean(propertyMap.get(DEBUG_PROPERTY_NAME)); historyEnabled = Boolean.parseBoolean(propertyMap.get(HISTORY_PROPERTY_NAME)); } private synchronized void setCommandInvoker(String name) throws ShellException { if (invoker == null || !name.equals(invoker.getName())) { boolean alreadySet = invoker != null; invoker = ShellUtils.createInvoker(name, this); if (alreadySet) { outPW.println("Switched to " + name + " invoker"); } } } private synchronized void setCommandInterpreter(String name) throws ShellException { if (interpreter == null || !name.equals(interpreter.getName())) { boolean alreadySet = interpreter != null; interpreter = ShellUtils.createInterpreter(name); if (alreadySet) { outPW.println("Switched to " + name + " interpreter"); } } } private void stackTrace(Throwable ex) { if (this.debugEnabled) { ex.printStackTrace(errPW); } } private void clearEof() { if (in instanceof KeyboardReader) { ((KeyboardReader) in).clearSoftEOF(); } } /** * Parse and run a command line using the CommandShell's current * interpreter. * * @param command the command line. * @throws ShellException */ public int runCommand(String command) throws ShellException { return interpreter.interpret(this, new StringReader(command), true, null, null); } /** * Run a command encoded as a CommandLine object. The command line will give * the command name (alias), the argument list and the IO stream. The * command is run using the CommandShell's current invoker. * * @param cmdLine the CommandLine object. * @param env * @param sysProps * @return the command's return code * @throws ShellException */ public int invoke(CommandLine cmdLine, Properties sysProps, Map<String, String> env) throws ShellException { if (this.invoker instanceof CommandInvoker) { return ((CommandInvoker) this.invoker).invoke(cmdLine, sysProps, env); } else { return this.invoker.invoke(cmdLine); } } /** * Prepare a CommandThread to run a command encoded as a CommandLine object. * When the thread's "start" method is called, the command will be executed * using the CommandShell's current (now) invoker. * * @param cmdLine the CommandLine object. * @return the command's return code * @throws ShellException */ public CommandThread invokeAsynchronous(CommandLine cmdLine) throws ShellException { return this.invoker.invokeAsynchronous(cmdLine); } /** * Gets a {@link CommandInfo} object representing the given command/alias. * * If the given command is a known alias, and the {@code Class} for the alias * is a type of {@link Command} then a {@code CommandInfo} object for a JNode * command will be returned. If the {@code Class} is a non-JNode command and * a bare command definition for the alias exists, then a {@code CommandInfo} * object will be created that contains an {@link org.jnode.shell.syntax.ArgumentBundle ArgumentBundle} * will be created. * * If the given command is not an alias, then it will be assumed to be a * class name, and an attempt will be made to load the a {@code Class} for * that name, and create a {@code CommandInfo} object for it. * * @param cmd an alias or class name * @return a {@code CommandInfo} object representing the given command * @throws ShellException, if the class could not be found */ public CommandInfo getCommandInfo(String cmd) throws ShellException { SyntaxBundle syntaxBundle = getSyntaxManager().getSyntaxBundle(cmd); try { Class<?> cls = aliasMgr.getAliasClass(cmd); if (Command.class.isAssignableFrom(cls)) { return new CommandInfo(cls, cmd, syntaxBundle, aliasMgr.isInternal(cmd)); } else { // check if this alias has a bare command definition ArgumentBundle argBundle = getSyntaxManager().getArgumentBundle(cmd); return new CommandInfo(cls, cmd, syntaxBundle, argBundle); } } catch (ClassNotFoundException ex) { throw new ShellInvocationException("Cannot load the command class for alias '" + cmd + "'", ex); } catch (NoSuchAliasException ex) { try { final ClassLoader cl = Thread.currentThread().getContextClassLoader(); return new CommandInfo(cl.loadClass(cmd), cmd, syntaxBundle, false); } catch (ClassNotFoundException ex2) { throw new ShellInvocationException( "Cannot find an alias or load a command class for '" + cmd + "'", ex); } } } protected ArgumentBundle getCommandArgumentBundle(CommandInfo commandInfo) { return commandInfo.getArgumentBundle(); } boolean isDebugEnabled() { return debugEnabled; } /** * Gets the alias manager of this shell */ public AliasManager getAliasManager() { return aliasMgr; } /** * Gets the shell's command InputHistory object. */ public InputHistory getCommandHistory() { return commandHistory; } /** * Gets the shell's currently active InputHistory object. */ public InputHistory getInputHistory() { if (readingCommand) { return commandHistory; } else { return CommandShell.applicationHistory.get(); } } /** * This method is called by the console input driver to perform command line * completion in response to a * {@link org.jnode.driver.console.textscreen.KeyboardReaderAction#KR_COMPLETE} * action; typically a TAB character. */ public CompletionInfo complete(String partial) { if (!readingCommand) { // dummy completion behavior for application input. return new CommandCompletions(); } // workaround to set the currentShell to this shell // FIXME is this needed? try { ShellUtils.getShellManager().registerShell(this); } catch (NameNotFoundException ex) { log.error("Cannot find shell manager", ex); } // do command completion completion = new CommandCompletions(interpreter); try { Completable cl = interpreter.parsePartial(this, partial); if (cl != null) { cl.complete(completion, this); } } catch (ShellException ex) { outPW.println(); // next line errPW.println("Cannot parse: " + ex.getMessage()); stackTrace(ex); } catch (Throwable ex) { outPW.println(); // next line errPW.println("Problem in completer: " + ex.getMessage()); stackTrace(ex); } // Make sure that the shell's completion context gets nulled. CompletionInfo myCompletion = completion; completion = null; return myCompletion; } /** * This method is responsible for generating incremental help in response * to a @link org.jnode.driver.console.textscreen.KeyboardReaderAction#KR_HELP} * action. */ public boolean help(String partial, PrintWriter pw) { if (!readingCommand) { return false; } try { return interpreter.help(this, partial, pw); } catch (ShellException ex) { outPW.println(); // next line errPW.println("Cannot parse: " + ex.getMessage()); stackTrace(ex); return false; } catch (Throwable ex) { outPW.println(); // next line errPW.println("Problem in incremental help: " + ex.getMessage()); stackTrace(ex); return false; } } private void addToCommandHistory(String line) { // Add this line to the command history. if (isHistoryEnabled() && !line.equals(lastCommandLine)) { commandHistory.addLine(line); lastCommandLine = line; } } private void addToInputHistory(String line) { // Add this line to the application input history. if (isHistoryEnabled() && !line.equals(lastInputLine)) { InputHistory history = applicationHistory.get(); if (history != null) { history.addLine(line); lastInputLine = line; } } } private CommandInput getInputStream() { if (isHistoryEnabled()) { // Insert a filter on the input stream that adds completed input // lines to the application input history. (Since the filter is stateless, // it doesn't really matter if we do this multiple times.) // FIXME if we partition the app history by application, we will // need to bind the history object in the history input stream // constructor. return new CommandInput(new HistoryInputStream(cin.getInputStream())); } else { return cin; } } /** * This subtype of FilterInputStream captures the console input for an * application in the application input history. */ private class HistoryInputStream extends FilterInputStream { // TODO - replace with a Reader private StringBuilder line = new StringBuilder(); public HistoryInputStream(InputStream in) { super(in); } @Override public int read() throws IOException { int res = super.read(); if (res != -1) { filter((byte) res); } return res; } @Override public int read(byte[] buf, int offset, int len) throws IOException { int res = super.read(buf, offset, len); for (int i = 0; i < res; i++) { filter(buf[offset + i]); } return res; } @Override public int read(byte[] buf) throws IOException { return read(buf, 0, buf.length); } private void filter(byte b) { if (b == '\n') { addToInputHistory(line.toString()); line.setLength(0); } else { line.append((char) b); } } } public int runCommandFile(File file, String alias, String[] args) throws ShellException { boolean enabled = setHistoryEnabled(false); try { CommandInterpreter interpreter = createInterpreter(new FileReader(file)); if (alias == null) { alias = file.getAbsolutePath(); } return interpreter.interpret(this, new FileReader(file), true, alias, args); } catch (IOException ex) { throw new ShellInvocationException("Cannot open command file: " + ex.getMessage(), ex); } finally { setHistoryEnabled(enabled); } } /** * Run a command script located using the shell's classloader. The behavior is analogous * to {@link #runCommandFile(File, String, String[])}, with the resourceName used as the * alias. * * @param resourceName the script resource name. */ public int runCommandResource(String resourceName, String[] args) throws ShellException { boolean enabled = setHistoryEnabled(false); try { InputStream input = getClass().getResourceAsStream(resourceName); if (input == null) { throw new ShellInvocationException("Cannot find resource '" + resourceName + "'"); } CommandInterpreter interpreter = createInterpreter(new InputStreamReader(input)); Reader reader = new InputStreamReader(getClass().getResourceAsStream(resourceName)); return interpreter.interpret(this, reader, true, resourceName, args); } finally { setHistoryEnabled(enabled); } } private CommandInterpreter createInterpreter(Reader reader) throws ShellException { try { final BufferedReader br = new BufferedReader(reader); CommandInterpreter interpreter; String line = br.readLine(); if (line != null && line.startsWith("#!")) { String name = line.substring(2); interpreter = ShellUtils.createInterpreter(name); if (interpreter == null) { throw new ShellException("Cannot execute script: no '" + name + "' interpreter is registered"); } } else { interpreter = this.interpreter; } return interpreter; } catch (IOException ex) { throw new ShellException("Cannot open command file: " + ex.getMessage(), ex); } finally { if (reader != null) { try { reader.close(); } catch (IOException ex) { // ignore } } } } public void exit() { exit0(); console.close(); } public void consoleClosed(ConsoleEvent event) { if (!exited) { if (Thread.currentThread() == ownThread) { exit0(); } else { synchronized (this) { exit0(); notifyAll(); } } } } private void exit0() { exited = true; } private synchronized boolean isExited() { return exited; } private boolean isHistoryEnabled() { return historyEnabled; } private boolean setHistoryEnabled(boolean historyEnabled) { boolean res = this.historyEnabled; this.historyEnabled = historyEnabled; return res; } /** * This helper does the work of mapping stream marker objects to the streams * that they denote. A real stream maps to itself, and <code>null</code> * maps to a NullInputStream or NullOutputStream. * * @param stream A real stream or a stream marker * @return the real stream that the first argument maps to. */ protected CommandIO resolveStream(CommandIO stream) { if (stream == CommandLine.DEFAULT_STDIN) { return getInputStream(); } else if (stream == CommandLine.DEFAULT_STDOUT) { return cout; } else if (stream == CommandLine.DEFAULT_STDERR) { return cerr; } else if (stream == CommandLine.DEVNULL || stream == null) { return new CommandInputOutput(new NullInputStream(), new NullOutputStream()); } else { return stream; } } public void resolveStreams(CommandIO[] ios) { for (int i = 0; i < ios.length; i++) { ios[i] = resolveStream(ios[i]); } } public PrintStream resolvePrintStream(CommandIO io) { CommandIO tmp = resolveStream(io); return ((CommandOutput) tmp).getPrintStream(); } public InputStream resolveInputStream(CommandIO io) { CommandIO tmp = resolveStream(io); return ((CommandInput) tmp).getInputStream(); } public SyntaxManager getSyntaxManager() { return syntaxMgr; } @Override public void addConsoleOuputRecorder(Writer writer) { // FIXME do security check Writer out = cout.getWriter(); Writer err = cerr.getWriter(); if (out instanceof FanoutWriter) { ((FanoutWriter) out).addStream(writer); ((FanoutWriter) err).addStream(writer); } else { cout = new CommandOutput(new FanoutWriter(true, out, writer)); outPW = cout.getPrintWriter(); cerr = new CommandOutput(new FanoutWriter(true, err, writer)); errPW = cerr.getPrintWriter(); } errPW.println("Testing"); } @Override public String escapeWord(String word) { return interpreter.escapeWord(word); } /** * Diagnose an exception thrown during the invocation or completion of a command. * * @param ex the exception to be diagnosed * @param cmdLine the command line in which it occurred, or {@code null} */ public void diagnose(Throwable ex, CommandLine cmdLine) { if (ex instanceof CommandSyntaxException) { try { List<Context> argErrors = ((CommandSyntaxException) ex).getArgErrors(); if (argErrors != null) { // The parser can produce many errors as each of the alternatives // in the tree are explored. The following assumes that errors // produced when we get farthest along in the token stream are most // likely to be the "real" errors. errPW.println("Command syntax error(s): "); int rightmostPos = 0; for (Context context : argErrors) { if (context.sourcePos > rightmostPos) { rightmostPos = context.sourcePos; } } for (Context context : argErrors) { if (context.sourcePos < rightmostPos) { continue; } if (context.token != null) { errPW.println(" " + context.exception.getMessage() + ": " + context.token.text); } else { errPW.println(" " + context.exception.getMessage() + ": " + context.syntax.format()); } } } else { errPW.println("Command syntax error: " + ex.getMessage()); } if (cmdLine != null) { Help help = HelpFactory.getHelpFactory().getHelp( cmdLine.getCommandName(), cmdLine.getCommandInfo()); help.usage(errPW); } } catch (HelpException e) { errPW.println("Exception while trying to get the command usage"); stackTrace(ex); } } else if (ex instanceof Exception) { errPW.println("Exception in command: " + ex.getMessage()); stackTrace(ex); } else { errPW.println("Fatal error in command: " + ex.getMessage()); stackTrace(ex); } } }