/*
* $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.awt.event.KeyEvent;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.Properties;
import org.jnode.driver.input.KeyboardEvent;
import org.jnode.driver.input.KeyboardListener;
import org.jnode.shell.io.CommandIO;
/*
* User: Sam Reid Date: Dec 20, 2003 Time: 1:20:33 AM Copyright (c) Dec 20, 2003
* by Sam Reid
*
*/
/**
* This is a base class for command invokers that support launching of
* asynchronous commands
*
* @author Sam Reid
* @author Martin Husted Hartvig (hagar@jnode.org)
* @author crawley@jnode.org
*/
public abstract class AsyncCommandInvoker implements SimpleCommandInvoker,
KeyboardListener {
protected final CommandShell shell;
boolean blocking;
Thread blockingThread;
CommandThread threadProcess = null;
String cmdName;
public AsyncCommandInvoker(CommandShell shell) {
this.shell = shell;
// listen for ctrl-c
if (shell.getConsole() != null) {
shell.getConsole().addKeyboardListener(this);
}
// FIXME ... we need to figure out when / how to detach the listener.
// At the moment they probably stay attached for ever. That is not
// great if lots of invokers are created.
}
public int invoke(CommandLine commandLine) throws ShellException {
CommandRunner cr = setup(commandLine);
return runIt(commandLine, cr);
}
public CommandThread invokeAsynchronous(CommandLine commandLine) throws ShellException {
CommandRunner cr = setup(commandLine);
return forkIt(commandLine, cr);
}
protected CommandRunner setup(CommandLine cmdLine)
throws ShellException {
return setup(cmdLine, null, null);
}
protected CommandRunner setup(CommandLine cmdLine, Properties sysProps, Map<String, String> env)
throws ShellException {
CommandIO[] ios = cmdLine.getStreams();
boolean redirected = ios[Command.STD_IN] != CommandLine.DEFAULT_STDIN ||
ios[Command.STD_OUT] != CommandLine.DEFAULT_STDOUT ||
ios[Command.STD_ERR] != CommandLine.DEFAULT_STDERR;
try {
shell.resolveStreams(ios);
} catch (ClassCastException ex) {
throw new ShellFailureException("streams array broken", ex);
}
// Make sure that the command info is set
cmdLine.getCommandInfo(shell);
return new CommandRunner(this, cmdLine, ios, sysProps, env, redirected);
}
protected int runIt(CommandLine cmdLine, CommandRunner cr) throws ShellException {
Throwable terminatingException = null;
int rc = -1;
try {
if (cr.isInternal()) {
cr.run();
} else {
try {
threadProcess = createThread(cr);
} catch (Exception ex) {
throw new ShellInvocationException(
"Exception while creating command thread", ex);
}
this.blocking = true;
this.blockingThread = Thread.currentThread();
this.cmdName = cmdLine.getCommandName();
threadProcess.start(null);
threadProcess.waitFor();
}
terminatingException = cr.getTerminatingException();
rc = cr.getRC();
} catch (ShellInvocationException ex) {
throw ex;
} catch (Throwable ex) {
terminatingException = ex;
} finally {
this.blockingThread = null;
this.blocking = false;
}
if (terminatingException != null) {
if (terminatingException instanceof ShellControlException) {
throw (ShellControlException) terminatingException;
}
shell.diagnose(terminatingException, cmdLine);
}
return rc;
}
protected CommandThread forkIt(CommandLine cmdLine, CommandRunner cr) throws ShellInvocationException {
if (cr.isInternal()) {
throw new ShellFailureException("unexpected internal command");
}
try {
return createThread(cr);
} catch (Exception ex) {
throw new ShellInvocationException(
"Exception while creating command thread", ex);
}
}
protected abstract CommandThread createThread(CommandRunner cr)
throws ShellInvocationException;
public void keyPressed(KeyboardEvent ke) {
// FIXME - use KeyEventBindings, etc to make the ^C, ^Z keys soft
if (ke.isControlDown() && ke.getKeyCode() == KeyEvent.VK_C) {
// This seems to 'work' now: at least is it not causing kernel
// panics all of the time. But if it proves to be unreliable
// it should be disabled again.
doCtrlC();
ke.consume();
} else if (ke.isControlDown() && ke.getKeyCode() == KeyEvent.VK_Z) {
doCtrlZ();
ke.consume();
}
}
private void doCtrlZ() {
// FIXME - this should suspend the current command, not put it into the
// background. It is a BAD IDEA to put a command that is reading from
// the keyboard into the background because it will compete with the shell
// for input. If we figure out how to prevent this, then ^Z could mean
// "background, losing control of keyboard input".
if (blockingThread != null && blockingThread.isAlive()) {
System.err.println("ctrl-z: Returning focus to console. ("
+ cmdName + " is still running)");
unblock();
}
}
private void doCtrlC() {
if (threadProcess != null && threadProcess.isAlive()
&& blockingThread != null && blockingThread.isAlive()) {
System.err.println("ctrl-c: Returning focus to console. ("
+ cmdName + " has been killed)");
unblock();
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
threadProcess.stop(new ThreadDeath());
return null;
}
});
}
}
/**
* Unblock the thread that is waiting for the invoker to finish. This should
* only be used in response to 'job control' actions ("^C", etc), due to the
* "nasty" way we currently implement it.
*/
private final void unblock() {
blocking = false;
if (blockingThread != null) {
// FIXME - this is wrong. The thread may not actually be waiting yet,
// and the interrupt may interfere with whatever is is doing; e.g. in the
// 'start' method for IsolateCommandThreadImpl.
blockingThread.interrupt();
}
}
final boolean isBlocking() {
return blocking;
}
public void keyReleased(KeyboardEvent event) {
}
@Override
public boolean isDebugEnabled() {
return shell.isDebugEnabled();
}
}