/*
* Copyright 2012 Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.core.dart2js;
import com.google.common.base.Joiner;
import com.google.dart.tools.core.DartCore;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
/**
* Execute the process created by the given process builder; collect the results and the exit code.
* The process runs to completion before the run() method returns.
*
* @coverage dart.tools.core.dart2js
*/
public class ProcessRunner {
private ProcessBuilder processBuilder;
private int exitCode;
private StringBuilder stdout = new StringBuilder();
private StringBuilder stderr = new StringBuilder();
private Thread processThread;
private Process process;
public ProcessRunner(ProcessBuilder processBuilder) {
this.processBuilder = processBuilder;
}
/**
* Wait for process termination.
*
* @param millis
* @throws IOException
*/
public void await(IProgressMonitor monitor, int maxDelayMillis) throws IOException {
long exitTime = maxDelayMillis > 0 ? System.currentTimeMillis() + maxDelayMillis : 0;
try {
// Run the process; check periodically for user cancellation.
while (processThread.isAlive()) {
if (monitor != null && monitor.isCanceled()) {
return;
}
if (exitTime != 0 && System.currentTimeMillis() > exitTime) {
return;
}
processThread.join(100);
}
} catch (InterruptedException e) {
throw new IOException(e);
}
}
public void dispose() {
if (process != null) {
// This is set to null in runAsync().
process.destroy();
}
}
public int getExitCode() {
return exitCode;
}
public String getStdErr() {
return stderr.toString();
}
public String getStdOut() {
return stdout.toString();
}
/**
* Run the process asynchronously, returning immediately.
*
* @param monitor
* @throws IOException
*/
public void runAsync() throws IOException {
exitCode = 0;
stdout.setLength(0);
stderr.setLength(0);
process = processBuilder.start();
// Read from stdout.
final Thread stdoutThread = new Thread(new Runnable() {
@Override
public void run() {
pipeStdout(process.getInputStream(), stdout);
}
});
// Read from stderr.
final Thread stderrThread = new Thread(new Runnable() {
@Override
public void run() {
pipeStderr(process.getErrorStream(), stderr);
}
});
processThread = new Thread(new Runnable() {
@Override
public void run() {
try {
exitCode = process.waitFor();
process = null;
stdoutThread.join();
stderrThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
processThread.start();
stdoutThread.start();
stderrThread.start();
}
/**
* Execute the process created by the process builder; return the exit value. This call happens
* synchronously. The monitor parameter is optional; if used, it is polled to see if the user
* cancelled the operation.
*
* @param monitor
* @return
* @throws IOException
* @throws OperationCanceledException if the user cancelled the operation
*/
public int runSync(IProgressMonitor monitor) throws IOException {
exitCode = 0;
stdout.setLength(0);
stderr.setLength(0);
final Process process = processBuilder.start();
processThread = new Thread(new Runnable() {
@Override
public void run() {
try {
exitCode = process.waitFor();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
// Read from stdout.
Thread stdoutThread = new Thread(new Runnable() {
@Override
public void run() {
pipeStdout(process.getInputStream(), stdout);
}
});
// Read from stderr.
Thread stderrThread = new Thread(new Runnable() {
@Override
public void run() {
pipeStderr(process.getErrorStream(), stderr);
}
});
processThread.start();
stdoutThread.start();
stderrThread.start();
processStarted(process);
try {
// Run the process; check periodically for user cancellation.
while (processThread.isAlive()) {
if (monitor != null && monitor.isCanceled()) {
process.destroy();
throw new OperationCanceledException();
}
processThread.join(100);
}
// Make sure we've read all the output.
stdoutThread.join();
stderrThread.join();
return exitCode;
} catch (InterruptedException e) {
throw new IOException(e);
}
}
@Override
public String toString() {
return Joiner.on(" ").join(processBuilder.command());
}
protected void pipeOutput(InputStream in, StringBuilder builder) {
try {
Reader reader = new InputStreamReader(in, "UTF-8");
char[] buffer = new char[512];
int count = reader.read(buffer);
while (count != -1) {
builder.append(buffer, 0, count);
count = reader.read(buffer);
}
} catch (UnsupportedEncodingException e) {
DartCore.logError(e);
} catch (IOException e) {
// This exception is expected.
}
}
protected void pipeStderr(InputStream in, StringBuilder builder) {
pipeOutput(in, builder);
}
protected void pipeStdout(InputStream in, StringBuilder builder) {
pipeOutput(in, builder);
}
/**
* To be (optionally) implemented in subclasses. Getting a handle on the started process can be
* useful, for example, if you want to pipe it stdin.
*
* @param process the process
* @throws IOException if an exception is thrown while interacting with the process
*/
protected void processStarted(Process process) throws IOException {
}
}