/* * $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 gnu.java.security.action.InvokeAction; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedActionException; import java.util.Map; import java.util.Properties; import org.jnode.shell.io.CommandIO; import org.jnode.shell.io.CommandOutput; import org.jnode.vm.VmExit; /** * A CommandRunner is a Runnable with a field to record a command's return code. It also * provides a convenience method for conditionally printing a command stack-trace. * * @author crawley@jnode.org */ public class CommandRunner implements CommandRunnable { static final Class<?>[] MAIN_ARG_TYPES = new Class[] {String[].class}; static final Class<?>[] EXECUTE_ARG_TYPES = new Class[] { CommandLine.class, InputStream.class, PrintStream.class, PrintStream.class }; static final String MAIN_METHOD = "main"; static final String EXECUTE_METHOD = "execute"; private final SimpleCommandInvoker invoker; private final CommandIO[] ios; final CommandLine commandLine; final PrintWriter shellErr; final Properties sysProps; final Map<String, String> env; final boolean redirected; Class<?> targetClass; Method method; Object[] args; private int rc; private Throwable terminatingException; public CommandRunner(SimpleCommandInvoker invoker, CommandLine commandLine, CommandIO[] ios, Properties sysProps, Map<String, String> env, boolean redirected) { this.invoker = invoker; this.redirected = redirected; this.commandLine = commandLine; this.ios = ios; this.shellErr = ios[Command.SHELL_ERR].getPrintWriter(); this.env = env; this.sysProps = sysProps; } public void run() { try { prepare(); execute(); } catch (VmExit ex) { setRC(ex.getStatus()); } catch (Throwable ex) { setTerminatingException(ex); } } /** * Prepare the command for execution. * * If the command is not a type of Command then the main method will * be prepared for invoking with arguments supplied from the command * line. * * This will fail to complete if the command line parsing fails, or if * the main method for the class could not be found. */ private void prepare() throws ShellException { CommandInfo commandInfo; Command command; try { commandInfo = commandLine.getCommandInfo(); command = commandInfo.createCommandInstance(); } catch (Exception ex) { throw new ShellInvocationException("Problem while creating command instance", ex); } // make this happen before setting up the main() method call for // bare commands. For commands without a bare command definition // this will return silently having done nothing. commandInfo.parseCommandLine(commandLine); if (command == null) { try { method = commandInfo.getCommandClass().getMethod(MAIN_METHOD, MAIN_ARG_TYPES); int modifiers = method.getModifiers(); if ((modifiers & Modifier.STATIC) == 0 || (modifiers & Modifier.PUBLIC) == 0) { new ShellInvocationException("The 'main' method for " + commandInfo.getCommandClass() + " is not public static"); } if (redirected) { throw new ShellInvocationException( "The 'main' method for " + commandInfo.getCommandClass() + " does not allow redirection or pipelining"); } // We've checked the method access, and we must ignore the class access. method.setAccessible(true); targetClass = commandInfo.getCommandClass(); args = new Object[] {commandLine.getArguments()}; } catch (NoSuchMethodException e) { throw new ShellInvocationException( "No entry point method found for " + commandInfo.getCommandClass()); } } } /** * Executes the command. * * For a JNode command, this will initialize and execute the command. For * other commands the main method will be invoked via reflection with the * set of arguments supplied on the command line. */ private void execute() throws Exception { try { CommandInfo commandInfo = commandLine.getCommandInfo(); if (method != null) { try { // This saves the Command instance that has the command line state // associated in a thread-local so that the Abstract.execute(String[]) // method can get hold of it. This is the magic that allows a command // that implements 'main' as "new MyCommand().execute(args)" to get the // parsed command line arguments, etc. AbstractCommand.saveCurrentCommand(commandInfo.getCommandInstance()); // Call the command's entry point method reflectively Object obj = Modifier.isStatic(method.getModifiers()) ? null : targetClass.newInstance(); AccessController.doPrivileged(new InvokeAction(method, obj, args)); } finally { // This clears the current command to prevent possible leakage of // commands arguments, etc to the next command. AbstractCommand.retrieveCurrentCommand(); } } else { // For a command that implements the Command API, call the 'new' // execute method. If it is not 'execute()' is not overridden by the // command class, the default implementation from AbstractCommand will // bounce us to the older execute(CommandLine, InputStream, PrintStream, // PrintStream) method. Command cmd = commandInfo.createCommandInstance(); cmd.initialize(commandLine, getIOs()); cmd.execute(); } } catch (PrivilegedActionException ex) { Exception ex2 = ex.getException(); if (ex2 instanceof InvocationTargetException) { Throwable cause = ex2.getCause(); if (cause instanceof Exception) { throw (Exception) cause; } else { throw (Error) cause; } } else { throw ex2; } } } public int getRC() { return rc; } void setRC(int rc) { this.rc = rc; } public Throwable getTerminatingException() { return terminatingException; } void setTerminatingException(Throwable terminatingException) { this.terminatingException = terminatingException; } void stackTrace(Throwable ex) { if (ex != null && invoker.isDebugEnabled()) { ex.printStackTrace(shellErr); } } public Method getMethod() { return method; } public Class<?> getTargetClass() { return targetClass; } public Object[] getArgs() { return args; } public CommandIO[] getIOs() { return ios; } public CommandLine getCommandLine() { return commandLine; } public boolean isInternal() { return commandLine.isInternal(); } public String getCommandName() { return commandLine != null ? commandLine.getCommandName() : null; } public Properties getSysProps() { return sysProps; } public Map<String, String> getEnv() { return env; } public void flushStreams() { for (CommandIO io : ios) { if (io instanceof CommandOutput) { try { ((CommandOutput) io).flush(); } catch (IOException ex) { // Ignore for now. } } } } }