/*
* $Id$
*
* Copyright (c) 2004-2005 by the TeXlapse Team.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package net.sourceforge.texlipse.builder;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.Properties;
import net.sourceforge.texlipse.PathUtils;
import net.sourceforge.texlipse.TexlipsePlugin;
import net.sourceforge.texlipse.properties.TexlipseProperties;
/**
* Helper methods to run an external program.
*
* @author Kimmo Karlsson
* @author Boris von Loesch
*/
public class ExternalProgram {
// the command to run
private String[] command;
// the directory to the command in
private File dir;
// the process that executes the command
private Process process;
// output messages to this console
private String consoleOutput;
/**
* Creates a new command runner.
*/
public ExternalProgram() {
this.command = null;
this.dir = null;
this.process = null;
this.consoleOutput = null;
}
/**
* Resets the command runner.
*
* @param command command to run
* @param dir directory to run the command in
*/
public void setup(String[] command, File dir, String console) {
this.command = command;
this.dir = dir;
this.process = null;
this.consoleOutput = console;
}
/**
* Force termination of the running process.
*/
public void stop() {
if (process != null) {
process.destroy();
// can't null the process here, because run()-method of this class is still executing
//process = null;
}
}
/**
* Reads the contents of a stream.
*
* @param is the stream
* @return the contents of the stream as a String
*/
protected String readOutput(InputStream is) {
StringWriter store = new StringWriter();
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
store.write(line + '\n');
if (consoleOutput != null) {
BuilderRegistry.printToConsole(consoleOutput + "> " + line);
}
}
} catch (IOException e) {
}
store.flush();
return store.getBuffer().toString();
}
/**
* Runs the external program as a process and waits
* for the process to finish execution.
*
* @param queryMessage text which will trigger the query dialog
* @return the text produced to standard output by the process
* @throws Exception
*/
public String run(String[] queryMessage) throws Exception {
return run(true, queryMessage);
}
/**
* Runs the external program as a process and waits
* for the process to finish execution.
*
* @return the text produced to standard output by the process
* @throws Exception
*/
public String run() throws Exception {
return run(true, null);
}
/**
* Runs the external program as a process and waits
* for the process to finish execution.
*
* @param wait if true, this method will block until
* the process has finished execution
* @return the text produced to standard output by the process
* @throws IOException
*/
protected String run(boolean wait, String[] queryMessage) throws IOException {
String output = null;
String errorOutput = null;
if ((command != null) && (dir != null)) {
StringBuffer commandSB = new StringBuffer();
for (int i = 0; i < command.length; i++) {
commandSB.append(command[i]);
commandSB.append(" ");
}
BuilderRegistry.printToConsole("running: " + commandSB.toString());
Runtime rt = Runtime.getRuntime();
// Add builder program path to environmet variables.
// This is needed at least on Mac OS X, where Eclipse overwrites
// the "path" environment variable, and xelatex needs its directory in the path.
Properties envProp = PathUtils.getEnv();
int index = command[0].lastIndexOf(File.separatorChar);
if (index > 0) {
String commandPath = command[0].substring(0, index);
String key = PathUtils.findPathKey(envProp);
envProp.setProperty(key, envProp.getProperty(key) + File.pathSeparatorChar + commandPath);
}
String[] env = PathUtils.mergeEnvFromPrefs(envProp, TexlipseProperties.BUILD_ENV_SETTINGS);
process = rt.exec(command, env, dir);
} else {
throw new IllegalStateException();
}
final StringBuffer thErrorOutput = new StringBuffer();
final StringBuffer thOutput = new StringBuffer();
// scan the standard output stream
final OutputScanner scanner = new OutputScanner(process.getInputStream(),
process.getOutputStream(), queryMessage, consoleOutput);
// scan also the standard error stream
final OutputScanner errorScanner = new OutputScanner(process.getErrorStream(),
process.getOutputStream(), queryMessage, consoleOutput);
final Thread errorThread = new Thread() {
public void run() {
if (errorScanner.scanOutput()) {
thErrorOutput.append(errorScanner.getText());
}
};
};
final Thread outputThread = new Thread() {
public void run() {
if (scanner.scanOutput()) {
thOutput.append(scanner.getText());
} else {
// Abort by user: Abort build, clear all output
process.destroy();
try {
errorThread.join();
} catch (InterruptedException e) {
// Should not happen
TexlipsePlugin.log("Output scanner interrupted", e);
}
thOutput.setLength(0);
thErrorOutput.setLength(0);
}
};
};
outputThread.start();
errorThread.start();
try {
// Wait until stream read has finished
errorThread.join();
outputThread.join();
} catch (InterruptedException e) {
TexlipsePlugin.log("Output scanner interrupted", e);
// Should not happen
}
output = thOutput.toString();
errorOutput = thErrorOutput.toString();
if (wait) {
// the process status code is not useful here
//int code =
try {
process.waitFor();
} catch (InterruptedException e) {
//Should not happen
TexlipsePlugin.log("Process interrupted", e);
}
}
process = null;
// combine the error output with normal output
// to collect information from for example makeindex
if (errorOutput.length() > 0) {
output += "\n" + errorOutput;
}
return output;
}
}