/* Execute a command in a subprocess. Copyright (c) 2004-2014 The Regents of the University of California. All rights reserved. Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy, modify, and distribute this software and its documentation for any purpose, provided that the above copyright notice and the following two paragraphs appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.actor.lib; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import ptolemy.actor.TypedIOPort; import ptolemy.actor.parameters.PortParameter; import ptolemy.data.ArrayToken; import ptolemy.data.BooleanToken; import ptolemy.data.IntToken; import ptolemy.data.RecordToken; import ptolemy.data.StringToken; import ptolemy.data.expr.FileParameter; import ptolemy.data.expr.Parameter; import ptolemy.data.type.ArrayType; import ptolemy.data.type.BaseType; import ptolemy.data.type.RecordType; import ptolemy.data.type.Type; import ptolemy.kernel.CompositeEntity; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.InternalErrorException; import ptolemy.kernel.util.NameDuplicationException; import ptolemy.kernel.util.Nameable; import ptolemy.util.StringUtilities; /////////////////////////////////////////////////////////////////// //// Exec /** Execute a command as a separately running subprocess.
This actor uses java.lang.Runtime.exec() to invoke a subprocess named by the command parameter in a specified directory with a specified environment. Data from the input port (if any) is passed to the input of the subprocess. The subprocess is run until it exits and then contents of the output and error streams of the subprocess (if any) are passed to the output and error ports.
If the subprocess generates no data on the output or error stream, then the data on the corresponding port(s) will consist of the empty string.
To get the effect of executing a command provided in a shell interpreter, set the prependPlatformDependentShellCommand parameter to true. This will prepend a default platform-dependent shell command to the command you wish to execute so that your command is executed within the shell. Alternatively, you can set command to "cmd" (Windows) or "sh" (Windows with Cygwin or Linux), and then provide commands at the input port. In this case, however, your model will only work on platforms that have the shell command you have specified. Note that in this case each command must be terminated with a newline. For example, to open a model in vergil and run it, you can set command to "sh" and use a Const actor to provide on the input port the string:
"vergil -run model.xml\n exit\n"
A much more interesting actor could be written using a Kahn Process Network. This actor would generate output asynchronously as the process was executing.
For information about Runtime.exec(), see:
http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html
and
http://mindprod.com/jgloss/exec.html
echo "Hello, world."
.
*
* The command parameter is read only once during fire(). * If you want to spawn another different command, * use life cycle management actors such RunCompositeActor.
*/ public PortParameter command; /** The directory in which to execute the command. * This parameter is read each time the subprocess is started * in fire(). Once the subprocess is running, this parameter is not * read again until fire() is called again. * *The initial default value of this parameter $CWD, which * corresponds with the value of the Java virtual machine * user.dir property which is the user's current working * directory. Note that if we are running inside a menu launched * application, then ptolemy.actor.gui.jnlp.MenuApplication will * change user.dir to be the value of user.home, which is the * name of the user's home directory.
*/ public FileParameter directory; /** The environment in which to execute the command. * This parameter is read each time the subprocess is started * in fire(). Once the subprocess is running, this parameter is not * read again until fire() is called again. * *This parameter is an array of records that name an environment * variable and the value for the value. The format is:
** {{name = "NAME1", value = "value1"}...} ** Where
NAME1
is the name of the environment
* variable, and value1
is the value.
* For example {{name = "PTII", value = "c:/ptII"}}
* would set the value of the PTII
to c:/ptII
.
If the initial value of the parameter is {{name="",
* value = ""}}
, then the environment from the calling or parent
* process is used in the new command.
Note that if this parameter sets any environment variable, * then under Windows the other environment variables in the calling * or parent process might not be passed to the subprocess. This * behaviour could be platform or JVM dependent. When in doubt, * try setting the command value to "env" to print out the * environment.
*/ public Parameter environment; /** Data that is generated by the subprocess on its standard * error. While the process is running, any error data generated * by the subprocess is stored until the subprocess exits and * then the stored error data is sent to the error port. * If the subprocess generates no data on standard error, then * the empty string (a string of length zero) is generated. * This port is an output port of type String. */ public TypedIOPort error; /** The exit code of the subprocess. Usually, a non-zero exit code * indicate that the subprocess had a problem. This port is an output * port of type int. */ public TypedIOPort exitCode; /** If true, ignore IOException errors from the subprocess. * The initial default value is false, indicating that * read errors are not ignored. */ public Parameter ignoreIOExceptionReadErrors; /** Strings to pass to the standard input of the subprocess. * Note that a newline is not appended to the string. If you * require a newline, add one using the AddSubtract actor. * This port is an input port of type String. */ public TypedIOPort input; /** Data that is generated by the subprocess on standard out. * While the process is running, any output data generated * by the subprocess is stored until the subprocess exits and * then the stored output data is sent to the output port. * If the subprocess generates no data on standard out, then * the empty string (a string of length zero) is generated. * This port is an output port of type String. */ // NOTE: output port is inherited from parent class. //public TypedIOPort output; /** If true, then prepend the platform dependent shell command * to the parsed value of the command parameter. * By setting this argument to true, it is possible to invoke * commands in a platform neutral method. *Under Windows NT or XP, the arguments "cmd.exe" and "/C" * are prepended. Under Windows 95, the arguments "command.com" * and "/C" are prepended. Under all other platforms, the * arguments "/bin/sh" and "-c" are prepended. *
By prepending sh or cmd, then this actor can use the * file redirection operations. *
The default value of this parameter is a boolean of value * false, which allows the user to arbitrarily invoke /bin/sh * scripts on all platforms. */ public Parameter prependPlatformDependentShellCommand; /** If true, then throw an exception if the subprocess returns * non-zero. * The default is a boolean of value true. * This parameter is ignored if waitForProcess is false. */ public Parameter throwExceptionOnNonZeroReturn; /** If true, then actor will wait until subprocess completes. The * default is a boolean of value true. */ public Parameter waitForProcess; /////////////////////////////////////////////////////////////////// //// public methods //// /** Invoke a subprocess, read the input data (if any) and * wait for the subprocess to terminate before sending any output * or error data to the appropriate ports. * *
If there is no data on the input port, then the * subprocess executes without reading any input. If there is no * output or error data from the subprocess, then the empty * string is sent to the appropriate port(s).
* * @exception IllegalActionException If the subprocess cannot be * started, if the input of the subprocess cannot be written, * if the subprocess gets interrupted, or if the return value * of the process is non-zero. */ @Override public void fire() throws IllegalActionException { // NOTE: This used to be synchronized, but this causes a // deadlock with the UI when parameters are edited while // model is running. super.fire(); String line = null; _exec(); if (input.numberOfSources() > 0 && input.hasToken(0)) { if ((line = ((StringToken) input.get(0)).stringValue()) != null) { if (_debugging) { _debug("Exec: Input: '" + line + "'"); } if (_inputBufferedWriter != null) { try { _inputBufferedWriter.write(line); _inputBufferedWriter.flush(); } catch (IOException ex) { throw new IllegalActionException(this, ex, "Problem writing input '" + command + "'"); } } } } boolean alreadySentOutput = false; try { // Close the stdin of the subprocess. _process.getOutputStream().close(); boolean waitForProcessValue = ((BooleanToken) waitForProcess .getToken()).booleanValue(); if (waitForProcessValue) { // The next line waits for the subprocess to finish. int processReturnCode = _process.waitFor(); if (processReturnCode != 0) { // We could have a parameter that would enable // or disable this. String outputString = ""; String errorString = ""; try { errorString = _errorGobbler.getAndReset(); } catch (Exception ex) { errorString = ex.toString(); } try { outputString = _outputGobbler.getAndReset(); } catch (Exception ex) { outputString = ex.toString(); } boolean throwExceptionOnNonZeroReturnValue = ((BooleanToken) throwExceptionOnNonZeroReturn .getToken()).booleanValue(); if (throwExceptionOnNonZeroReturnValue) { throw new IllegalActionException( this, "Executing command \"" + ((StringToken) command.getToken()) .stringValue() + "\" returned a non-zero return value of " + processReturnCode + ".\nThe last input was: " + line + ".\nThe standard output was: " + outputString + "\nThe error output was: " + errorString); } else { error.send(0, new StringToken(errorString)); output.send(0, new StringToken(outputString)); alreadySentOutput = true; } } exitCode.send(0, new IntToken(processReturnCode)); } } catch (InterruptedException interrupted) { throw new InternalErrorException(this, interrupted, "_process.waitFor() was interrupted"); } catch (IOException io) { throw new IllegalActionException(this, io, "Closing stdin of the subprocess threw an IOException."); } // if we sent output when the return value was non-zero, do not send // additional output. // see http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4326 if (!alreadySentOutput) { String outputString = _outputGobbler.getAndReset(); String errorString = _errorGobbler.getAndReset(); if (_debugging) { _debug("Exec: Error: '" + errorString + "'"); _debug("Exec: Output: '" + outputString + "'"); } // We could have a parameter that if it was set // we would throw an exception if there was any error data. error.send(0, new StringToken(errorString)); output.send(0, new StringToken(outputString)); } } /** Override the base class and terminate the process. */ @Override public void stop() { // NOTE: This method used to be synchronized, as // was the fire() method, but this caused deadlocks. EAL super.stop(); _terminateProcess(); } /** Override the base class to stop waiting for input data. */ @Override public void stopFire() { // NOTE: This method used to be synchronized, as // was the fire() method, but this caused deadlocks. EAL super.stopFire(); _stopFireRequested = true; _terminateProcess(); } /** Terminate the subprocess. * This method is invoked exactly once per execution * of an application. None of the other action methods should be * be invoked after it. * @exception IllegalActionException Not thrown in this base class. */ @Override public void wrapup() throws IllegalActionException { _terminateProcess(); } /////////////////////////////////////////////////////////////////// //// private methods //// // Execute a command, set _process to point to the subprocess // and set up _errorGobbler and _outputGobbler to read data. private void _exec() throws IllegalActionException { // FIXME: Exec, KeyStoreActor, JTextAreaExec have duplicate code. // This is a private method because fire() was getting too long. File directoryAsFile = null; try { _stopFireRequested = false; if (_process != null) { // Note that we assume that _process is null upon entry // to this method, but we check again here just to be sure. _terminateProcess(); } Runtime runtime = Runtime.getRuntime(); command.update(); List