com.sun.electric.tool.user.Exec Maven / Gradle / Ivy
/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: Exec.java
*
* Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
*
* Electric(tm) is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Electric(tm) 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool.user;
import com.sun.electric.database.Environment;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.UserInterfaceExec;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* Runtime.exec() has many pitfalls to it's proper use. This class
* wraps external executes to make it easier to use.
*
* Usage:
*
* Exec exec = new Exec("ls", null, User.getWorkingDirectory(), System.out, System.out);
* exec.start(); // run in a new Thread
*
* You can also use exec.run() to run in the current thread.
*/
public class Exec extends Thread {
/**
* This class is used to read data from an external process.
* If something does not consume the data, it will fill up the default
* buffer and deadlock. This class also redirects data read
* from the process (the process' output) to another stream,
* if specified.
*/
public static class ExecProcessReader extends Thread {
private InputStream in;
private OutputStream redirect;
private char [] buf;
private final UserInterfaceExec userInterface = new UserInterfaceExec();
/**
* Create a stream reader that will read from the stream
* @param in the input stream
*/
public ExecProcessReader(InputStream in) {
this(in, null);
}
/**
* Create a stream reader that will read from the stream, and
* store the read text into buffer.
* @param in the input stream
* @param redirect read text is redirected to this
*/
public ExecProcessReader(InputStream in, OutputStream redirect) {
this.in = in;
this.redirect = redirect;
buf = new char[256];
setName("ExecProcessReader");
}
public void run() {
if (Thread.currentThread() == this) {
// Environment.setThreadEnvironment(launcherEnvironment);
Job.setUserInterface(userInterface);
}
try {
PrintWriter pw = null;
if (redirect != null) pw = new PrintWriter(redirect);
// read from stream
InputStreamReader input = new InputStreamReader(in);
BufferedReader reader = new BufferedReader(input);
int read = 0;
while ((read = reader.read(buf)) >= 0) {
if (pw != null) {
pw.write(buf, 0, read);
pw.flush();
}
}
reader.close();
input.close();
} catch (java.io.IOException e) {
ActivityLogger.logException(e);
}
}
}
/**
* Objects that want to be notified of the process finishing should
* implement this interface, and add themselves as a listener to the
* process.
*/
public interface FinishedListener {
public void processFinished(FinishedEvent e);
}
/**
* The event passed to listeners when the process finishes
*/
public static class FinishedEvent {
private Object source;
private String exec;
private int exitValue;
private File dir; // working directory
public FinishedEvent(Object source, String exec, File dir, int exitValue) {
this.source = source;
this.exec = exec;
this.exitValue = exitValue;
this.dir = dir;
}
public Object getSource() { return source; }
public String getExec() { return exec; }
public int getExitValue() { return exitValue; }
public File getWorkingDir() { return dir; }
}
private final String command;
private final String [] exec;
private final String [] envVars;
private final File dir; // working directory
private final Environment launcherEnvironment;
private final UserInterfaceExec userInterface;
private final OutputStream outStreamRedir; // output of process redirected to this stream
private final OutputStream errStreamRedir; // error messages of process redirected to this stream
private PrintWriter processWriter; // connect to input of process
//private ExecProcessReader outReader;
//private ExecProcessReader errReader;
private Process p = null;
private int exitVal;
private final ArrayList finishedListeners; // list of listeners waiting for process to finish
/**
* Execute an external process.
* Note: command is not a shell command line command, it is a single program and arguments.
* Therefore, /bin/sh -c /bin/ls > file.txt
will NOT work.
*
* Instead, use String[] exec = {"/bin/sh", "-c", "/bin/ls > file.txt"};
* and use the other constructor.
* @param command the command to run.
* @param envVars environment variables of the form name=value. If null, inherits vars from current process.
* @param dir the working directory. If null, uses the working dir from the current process
* @param outStreamRedir stdout of the process will be redirected to this stream if not null
* @param errStreamRedir stderr of the process will be redirected to this stream if not null
*/
public Exec(String command, String [] envVars, File dir, OutputStream outStreamRedir, OutputStream errStreamRedir) {
this.command = command;
this.exec = null;
this.envVars = envVars;
this.dir = dir;
launcherEnvironment = Environment.getThreadEnvironment();
userInterface = new UserInterfaceExec();
this.outStreamRedir = outStreamRedir;
this.errStreamRedir = errStreamRedir;
this.processWriter = null;
this.finishedListeners = new ArrayList();
this.exitVal = -1;
setName(command);
}
/**
* Execute an external process.
* Note: this is not a command-line command, it is a single program and arguments.
* @param exec the executable and arguments of the process
* @param envVars environment variables of the form name=value. If null, inherits vars from current process.
* @param dir the working directory. If null, uses the working dir from the current process
* @param outStreamRedir stdout of the process will be redirected to this stream if not null
* @param errStreamRedir stderr of the process will be redirected to this stream if not null
*/
public Exec(String [] exec, String [] envVars, File dir, OutputStream outStreamRedir, OutputStream errStreamRedir) {
this.command = null;
this.exec = exec;
this.envVars = envVars;
this.dir = dir;
launcherEnvironment = Environment.getThreadEnvironment();
userInterface = new UserInterfaceExec();
this.outStreamRedir = outStreamRedir;
this.errStreamRedir = errStreamRedir;
this.processWriter = null;
this.finishedListeners = new ArrayList();
this.exitVal = -1;
setName(exec[0]);
}
public void run() {
if (Thread.currentThread() == this) {
Environment.setThreadEnvironment(launcherEnvironment);
Job.setUserInterface(userInterface);
}
if (outStreamRedir instanceof OutputStreamChecker) {
((OutputStreamChecker)outStreamRedir).setExec(this);
}
if (errStreamRedir instanceof OutputStreamChecker) {
((OutputStreamChecker)errStreamRedir).setExec(this);
}
try {
Runtime rt = Runtime.getRuntime();
ExecProcessReader outReader = null;
ExecProcessReader errReader = null;
// run program
synchronized(this) {
try {
if (command != null)
p = rt.exec(command, envVars, dir);
else
p = rt.exec(exec, envVars, dir);
} catch (IOException e) {
System.out.println("Error running "+command+": "+e.getMessage());
return;
}
// eat output (stdout) and stderr from program so it doesn't block
outReader = new ExecProcessReader(p.getInputStream(), outStreamRedir);
errReader = new ExecProcessReader(p.getErrorStream(), errStreamRedir);
outReader.start();
errReader.start();
// attach to input of process
processWriter = new PrintWriter(p.getOutputStream());
}
// wait for exit status
exitVal = p.waitFor();
// also wait for redir threads to die, if doing redir
if (outStreamRedir != null) outReader.join();
if (errStreamRedir != null) errReader.join();
StringBuffer com = new StringBuffer();
if (command != null)
com.append(command);
else {
for (int i=0; i copy = new ArrayList();
// make copy cause listeners may want to remove themselves if process finished
for (FinishedListener l : finishedListeners) {
copy.add(l);
}
for (FinishedListener l : copy) {
l.processFinished(e);
}
}
synchronized(this) {
if (processWriter != null) {
processWriter.close();
processWriter = null;
}
}
} catch (Exception e) {
ActivityLogger.logException(e);
}
}
/**
* Send a line of text to the process. This is not useful
* if the process is not expecting any input.
* @param line a line of text to send to the process
*/
public void writeln(String line) {
synchronized(this) {
if (processWriter == null) {
System.out.println("Can't write to process: No valid process running.");
return;
}
processWriter.println(line);
processWriter.flush();
}
}
/**
* Add a Exec.FinishedListener
* @param a the listener
*/
public void addFinishedListener(FinishedListener a) {
synchronized(finishedListeners) {
finishedListeners.add(a);
}
}
/**
* Remove a Exec.FinishedListener
* @param a the listener
*/
public void removeFinishedListener(FinishedListener a) {
synchronized(finishedListeners) {
finishedListeners.remove(a);
}
}
/**
* End this process, if it is running. Otherwise, does nothing
*/
public synchronized void destroyProcess() {
if (p != null) {
p.destroy();
}
}
public int getExitVal() { return exitVal; }
/**
* Check for a string passed to the OutputStream. All chars passed to
* this class are also transparently passed to System.out.
* This only checks for strings within a single line of text.
* The strings are simple strings, not regular expressions.
*/
public static class OutputStreamChecker extends OutputStream implements Serializable {
private OutputStream ostream;
private String checkFor;
private StringBuffer lastLine;
private char [] buf;
private int bufOffset;
private boolean found;
private boolean regexp; // if checkFor string is a regular expression
private String foundLine;
private File copyToFile;
private PrintWriter out;
private List listeners;
private Exec exec = null;
/**
* Checks for string in output stream. The string may span multiple lines, or may be contained
* within a non-terminated line (such as an input query).
* @param ostream send read data to this output stream (usually System.out)
* @param checkFor the string to check for
*/
public OutputStreamChecker(OutputStream ostream, String checkFor) {
this(ostream, checkFor, false, null);
}
/**
* Checks for string in output stream. String must be contained within one line.
* @param ostream send read data to this output stream (usually System.out)
* @param checkFor the string to check for
* @param regexp if true, the string is considered a regular expression
* @param copyToFile if non-null, the output is copied to this file
*/
public OutputStreamChecker(OutputStream ostream, String checkFor, boolean regexp, File copyToFile) {
this.ostream = ostream;
this.checkFor = checkFor;
this.regexp = regexp;
lastLine = null;
buf = null;
bufOffset = 0;
lastLine = new StringBuffer();
if (!regexp)
buf = new char[checkFor.length()];
found = false;
foundLine = null;
out = null;
this.copyToFile = copyToFile;
if (copyToFile != null) {
try {
out = new PrintWriter(new BufferedWriter(new FileWriter(this.copyToFile)));
} catch (IOException e) {
System.out.println(e.getMessage());
out = null;
}
}
listeners = new ArrayList();
}
public void write(int b) throws IOException {
ostream.write(b);
if (out != null) out.write(b);
lastLine.append((char)b);
if (regexp) {
// match against regular expression on end-of-line
if (b == '\n') {
if (lastLine.toString().matches(checkFor)) {
found = true;
foundLine = lastLine.toString();
alertListeners(foundLine);
}
}
} else {
// store data in buffer of same length as string trying to match
buf[bufOffset] = (char)b;
bufOffset++;
if (bufOffset >= buf.length) bufOffset = 0;
// check against string. Since same length, when string is found bufOffset
// will be at start of string.
boolean matched = true;
for (int i=0; i