/* Run a list of commands. Copyright (c) 2006-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.util; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** Execute commands in a subprocess and send the results to stderr and stdout.
As an alternative to this class, see {@link ptolemy.gui.JTextAreaExec}, which uses Swing; and {@link ptolemy.util.StringBufferExec}, which writes to a StringBuffer.
Sample usage:
List execCommands = new LinkedList(); execCommands.add("date"); execCommands.add("sleep 3"); execCommands.add("date"); execCommands.add("notACommand"); final StreamExec exec = new StreamExec(); exec.setCommands(execCommands); exec.start();
Loosely based on Example1.java from http://java.sun.com/products/jfc/tsc/articles/threads/threads2.html
See also http://developer.java.sun.com/developer/qow/archive/135/index.jsp (1/11: Broken) and http://jw.itworld.com/javaworld/jw-12-2000/jw-1229-traps.html.
@see ptolemy.gui.JTextAreaExec @see ptolemy.util.StringBufferExec @author Christopher Hylands @version $Id: StreamExec.java 70402 2014-10-23 00:52:20Z cxh $ @since Ptolemy II 5.2 @Pt.ProposedRating Red (cxh) @Pt.AcceptedRating Red (cxh) */ public class StreamExec implements ExecuteCommands { /** Create a StreamExec. */ public StreamExec() { // Does nothing? } /////////////////////////////////////////////////////////////////// //// public methods //// /** Append to the path of the subprocess. If directoryName is already * in the path, then it is not appended. * @param directoryName The name of the directory to append to the path. */ @Override public void appendToPath(String directoryName) { // FIXME: Code Duplication from JTextAreaExec.java if (_debug) { stdout("StreamExec.appendToPath(): " + directoryName + "\n"); } // Might be Path, might be PATH String keyPath = "PATH"; String path = getenv(keyPath); if (path == null) { path = getenv("Path"); if (path != null) { keyPath = "Path"; } if (_debug) { stdout("StreamExec.appendToPath() Path: " + path + "\n"); } } else { if (_debug) { stdout("StreamExec.appendToPath() PATH: " + path + "\n"); } } if (path == null || path.indexOf(File.pathSeparatorChar + directoryName + File.pathSeparatorChar) == -1) { if (_debug) { stdout("StreamExec.appendToPath() updating\n"); } _envp = StreamExec.updateEnvironment(keyPath, File.pathSeparatorChar + directoryName + File.pathSeparatorChar); if (_debug) { // For debugging for (String element : _envp) { stdout("StreamExec.appendToPath() " + element); } } } } /** Cancel any running commands. */ @Override public void cancel() { //_worker.interrupt(); if (_process != null) { _process.destroy(); } } /** Clear the text area, status bar and progress bar. */ @Override public void clear() { updateStatusBar(""); _updateProgressBar(0); } /** Get the value of the environment of the subprocess. * @param key The key for which to search. * @return The value of the key. If the key is not set, then * null is returned. If appendToPath() has been called, and * the then the environment for the subprocess is checked, which * might be different than the environment for the current process * because appendToPath() was called. Note that that key is searched * for in a case-insensitive mode. */ @Override public String getenv(String key) { // FIXME: Code Duplication from JTextAreaExec.java if (_envp == null) { // Sigh. System.getEnv("PATH") and System.getEnv("Path") // will return the same thing, even though the variable // is Path. Updating PATH is wrong, the subprocess will // not see the change. So, we search the env for a direct // match MapCalling this method resets the log of previously received * matches.
* @return Any strings sent that match the value of the pattern. * The matches for stdout are returned first, then the matches * for stdout. * @see #setPattern(String) * @see #stdout(String) */ public String getPatternLog() { String patternOutLog = _patternOutLog.toString(); String patternErrorLog = _patternErrorLog.toString(); _patternOutLog = new StringBuffer(); _patternErrorLog = new StringBuffer(); return patternOutLog + patternErrorLog; } /** Return the return code of the last subprocess that was executed. * @return the return code of the last subprocess that was executed. */ @Override public int getLastSubprocessReturnCode() { return _subprocessReturnCode; } /** Set the list of commands. * @param commands A list of Strings, where each element is a command. */ @Override public void setCommands(List commands) { _commands = commands; } /** Set the pattern that is used to search data sent to stdout. *If the value of the pattern argument is non-null, then * each time {@link #stdout(String)} is called, the value of * the argument to stdout is compared with the pattern * regex. If there is a match, then the value is appended * to a StringBuffer that whose value may be obtained with * the {@link #getPatternLog()} method.
*Calling this method resets the log of previously received * matches.
* @param pattern The pattern used. * @see #getPatternLog() */ public void setPattern(String pattern) { _pattern = Pattern.compile(pattern); _patternOutLog = new StringBuffer(); _patternErrorLog = new StringBuffer(); } /** Determine whether the last subprocess is waited for or not. * @param waitForLastSubprocess True if the {@link #start()} * method should wait for the last subprocess to finish. */ public void setWaitForLastSubprocess(boolean waitForLastSubprocess) { _waitForLastSubprocess = waitForLastSubprocess; } /** Set the working directory of the subprocess. * @param workingDirectory The working directory of the * subprocess. If this argument is null, then the subprocess is * executed in the working directory of the current process. */ @Override public void setWorkingDirectory(File workingDirectory) { _workingDirectory = workingDirectory; } /** Start running the commands. * By default, the start() method returns after the last subprocess * finishes. See {@link #setWaitForLastSubprocess(boolean)}. */ @Override public void start() { String returnValue = _executeCommands(); updateStatusBar(returnValue); stdout(returnValue); } /** Append the text message to stderr. A derived class could * append to a StringBuffer. {@link ptolemy.gui.JTextAreaExec} appends to a * JTextArea. The output automatically gets a trailing newline * appended. * @param text The text to append to standard error. */ @Override public void stderr(final String text) { if (_pattern != null && _pattern.matcher(text).matches()) { _patternErrorLog.append(text + _eol); } System.err.println(text); System.err.flush(); } /** Append the text message to the output. A derived class could * append to a StringBuffer. {@link ptolemy.gui.JTextAreaExec} appends to a * JTextArea. * The output automatically gets a trailing newline appended. *If {@link #setPattern(String)} has been called with a * non-null argument, then any text that matches the pattern * regex will be appended to a log file. The log file * may be read with {@link #getPatternLog()}.
* @param text The text to append to standard out. */ @Override public void stdout(final String text) { if (_pattern != null && _pattern.matcher(text).matches()) { _patternOutLog.append(text + _eol); } System.out.println(text); System.out.flush(); } /** Update the environment and return the results. * Read the environment for the current process, append the value * of the value parameter to the environment variable named by * the key parameter. * @param key The environment variable to be updated. * @param value The value to append * @return An array of strings that consists of the subprocess * environment variable names and values in the form *name=value
with the environment variable
* named by the key parameter updated to include the value
* of the value parameter.
*/
public static String[] updateEnvironment(String key, String value) {
// This is static so that we can share it among
// ptolemy.util.StreamExec
// StringBufferExec, which extends Stream Exec
// and
// ptolemy.gui.JTextAreaExec, which extends JPanel
Mapname=value
. If this variable is null, then
* the environment of the calling process is used.
*/
private String[] _envp;
/** The regex pattern used to match against the output of
* the subprocess.
*/
private Pattern _pattern;
/** The StringBuffer that contains matches to calls to stdout(). */
private StringBuffer _patternErrorLog;
/** The StringBuffer that contains matches to calls to stderr(). */
private StringBuffer _patternOutLog;
/** The Process that we are running. */
private Process _process;
/** The return code of the last Runtime.exec() command. */
private int _subprocessReturnCode;
/** True if we should wait for the last subprocess. */
private boolean _waitForLastSubprocess = true;
/** The working directory of the subprocess. If null, then
* the subprocess is executed in the working directory of the current
* process.
*/
private File _workingDirectory;
}