/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: ExecProcess.java
* Written by Jonathan Gainsley, Sun Microsystems.
*
* Copyright (c) 2005, 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.simulation.test;
import java.io.*;
/**
* Runtime.exec() has many pitfalls to it's proper use. This class
* wraps external executes to make it easier to use.
*/
class ExecProcess 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;
/**
* Create a stream reader that will read from the stream
* @param in the input stream
*/
public ExecProcessReader(InputStream in) {
this(in, null);
setDaemon(true);
}
/**
* 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;
setName("ExecProcessReader");
}
public void run() {
try {
PrintWriter pw = null;
if (redirect != null) pw = new PrintWriter(redirect);
// read from stream
InputStreamReader input = new InputStreamReader(in);
BufferedReader reader = new BufferedReader(input);
String line = null;
char [] cbuf = new char[256];
int read = 0;
while ((read = reader.read(cbuf, 0, 256)) > -1) {
if (pw != null) {
pw.write(cbuf, 0, read);
pw.flush();
}
}
reader.close();
input.close();
} catch (java.io.IOException e) {
System.out.println(e.getMessage());
}
}
}
private final String command;
private final String [] exec;
private final String [] envVars;
private final File dir; // working directory
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;
/**
* Execute an external process.
* Note: command is not a shell command line command, it is a single program and arguments.
* Therefore, <code>/bin/sh -c /bin/ls > file.txt</code> will NOT work.
* <p>
* 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 ExecProcess(String command, String [] envVars, File dir, OutputStream outStreamRedir, OutputStream errStreamRedir) {
this.command = command;
this.exec = null;
this.envVars = envVars;
this.dir = dir;
this.outStreamRedir = outStreamRedir;
this.errStreamRedir = errStreamRedir;
this.processWriter = null;
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 ExecProcess(String [] exec, String [] envVars, File dir, OutputStream outStreamRedir, OutputStream errStreamRedir) {
this.command = null;
this.exec = exec;
this.envVars = envVars;
this.dir = dir;
this.outStreamRedir = outStreamRedir;
this.errStreamRedir = errStreamRedir;
this.processWriter = null;
setName(exec[0]);
}
public void run() {
try {
Runtime rt = Runtime.getRuntime();
outReader = null;
errReader = null;
// run program
synchronized(this) {
if (command != null)
p = rt.exec(command, envVars, dir);
else
p = rt.exec(exec, envVars, dir);
// 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
BufferedOutputStream bufout = new BufferedOutputStream(p.getOutputStream());
processWriter = new PrintWriter(bufout);
}
// wait for exit status
int 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<exec.length; i++)
com.append(exec[i]+" ");
}
System.out.println("Process finished [exit: "+exitVal+"]: "+com.toString());
synchronized(this) {
if (processWriter != null) {
processWriter.close();
processWriter = null;
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
/**
* 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("No process to write to: "+line);
return;
}
processWriter.println(line);
processWriter.flush();
}
}
/**
* End this process, if it is running. Otherwise, does nothing
*/
public synchronized void destroyProcess() {
if (p != null) {
p.destroy();
}
}
}