common.core: clean up ProcessUtils a bit
[deliverable/tracecompass.git] / common / org.eclipse.tracecompass.common.core / src / org / eclipse / tracecompass / common / core / process / ProcessUtils.java
CommitLineData
4bd7cc77
AM
1/*******************************************************************************
2 * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
3 *
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *******************************************************************************/
9
10package org.eclipse.tracecompass.common.core.process;
11
12import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
13
14import java.io.BufferedReader;
15import java.io.IOException;
16import java.io.InputStreamReader;
17import java.util.LinkedList;
18import java.util.List;
19import java.util.stream.Collectors;
20
21import org.eclipse.core.runtime.CoreException;
22import org.eclipse.core.runtime.IProgressMonitor;
23import org.eclipse.core.runtime.IStatus;
24import org.eclipse.core.runtime.MultiStatus;
25import org.eclipse.core.runtime.Status;
26import org.eclipse.jdt.annotation.Nullable;
27import org.eclipse.tracecompass.internal.common.core.Activator;
28
71535b53
MK
29import com.google.common.base.Charsets;
30
4bd7cc77
AM
31/**
32 * Common utility methods for launching external processes and retrieving their
33 * output.
34 *
35 * @author Alexandre Montplaisir
36 * @since 2.2
37 */
38public final class ProcessUtils {
39
71535b53
MK
40 private static final int PROGRESS_DURATION = 1000;
41
4bd7cc77
AM
42 private ProcessUtils() {}
43
44 /**
45 * Simple output-getting command. Cannot be cancelled, and will return null
46 * if the external process exits with a non-zero return code.
47 *
48 * @param command
49 * The command (executable + arguments) to launch
50 * @return The process's standard output upon completion
51 */
52 public static @Nullable List<String> getOutputFromCommand(List<String> command) {
53 try {
54 ProcessBuilder builder = new ProcessBuilder(command);
55 builder.redirectErrorStream(true);
56
57 Process p = builder.start();
71535b53 58 try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), Charsets.UTF_8));) {
4bd7cc77
AM
59 List<String> output = new LinkedList<>();
60
61 /*
62 * We must consume the output before calling Process.waitFor(),
63 * or else the buffers might fill and block the external program
64 * if there is a lot of output.
65 */
66 String line = br.readLine();
67 while (line != null) {
68 output.add(line);
69 line = br.readLine();
70 }
71
72 int ret = p.waitFor();
73 return (ret == 0 ? output : null);
74 }
75 } catch (IOException | InterruptedException e) {
76 return null;
77 }
78 }
79
80 /**
81 * Interface defining what do to with a process's output. For use with
82 * {@link #getOutputFromCommandCancellable}.
83 */
84 @FunctionalInterface
71535b53 85 public interface OutputReaderFunction {
4bd7cc77
AM
86
87 /**
88 * Handle the output of the process. This can include reporting progress
89 * to the monitor, and pre-processing the returned output.
90 *
91 * @param reader
92 * A buffered reader to the process's standard output.
93 * Managed internally, so you do not need to
94 * {@link BufferedReader#close()} it.
95 * @param monitor
96 * The progress monitor. Implementation should check
97 * periodically if it is cancelled to end processing early.
98 * The monitor's start and end will be managed, but progress
99 * can be reported via the {@link IProgressMonitor#worked}
100 * method. The total is 1000 work units.
101 * @return The process's output
102 * @throws IOException
103 * If there was a read error. Letting throw all exception
104 * from the {@link BufferedReader} is recommended.
105 */
106 List<String> readOutput(BufferedReader reader, IProgressMonitor monitor) throws IOException;
107 }
108
109 /**
110 * Cancellable output-getting command. The processing, as well as the
111 * external process itself, can be stopped by cancelling the passed progress
112 * monitor.
113 *
114 * @param command
115 * The command (executable + arguments) to execute
116 * @param monitor
117 * The progress monitor to check for cancellation and optionally
118 * progress
119 * @param mainTaskName
120 * The main task name of the job
121 * @param readerFunction
122 * What to do with the output. See {@link OutputReaderFunction}.
123 * @return The process's standard output, upon normal completion
124 * @throws CoreException
125 * If a problem happened with the execution of the external
126 * process. It can be reported to the user with the help of an
127 * ErrorDialog.
128 */
129 public static List<String> getOutputFromCommandCancellable(List<String> command,
130 IProgressMonitor monitor,
131 String mainTaskName,
132 OutputReaderFunction readerFunction)
133 throws CoreException {
134
135 CancellableRunnable cancellerRunnable = null;
136 Thread cancellerThread = null;
137
138 try {
71535b53 139 monitor.beginTask(mainTaskName, PROGRESS_DURATION);
4bd7cc77
AM
140
141 ProcessBuilder builder = new ProcessBuilder(command);
142 builder.redirectErrorStream(false);
143
144 Process p = checkNotNull(builder.start());
145
146 cancellerRunnable = new CancellableRunnable(p, monitor);
147 cancellerThread = new Thread(cancellerRunnable);
148 cancellerThread.start();
149
71535b53 150 try (BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(p.getInputStream(), Charsets.UTF_8));) {
4bd7cc77
AM
151
152 List<String> lines = readerFunction.readOutput(stdoutReader, monitor);
153
154 int ret = p.waitFor();
155
156 if (monitor.isCanceled()) {
157 /* We were interrupted by the canceller thread. */
158 IStatus status = new Status(IStatus.CANCEL, Activator.instance().getPluginId(), null);
159 throw new CoreException(status);
160 }
161
162 if (ret != 0) {
163 /*
164 * Something went wrong running the external process. We
165 * will gather the stderr and report it to the user.
166 */
167 BufferedReader stderrReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
168 List<String> stderrOutput = stderrReader.lines().collect(Collectors.toList());
169
170 MultiStatus status = new MultiStatus(Activator.instance().getPluginId(),
171 IStatus.ERROR, Messages.ProcessUtils_ErrorDuringExecution, null);
172 for (String str : stderrOutput) {
173 status.add(new Status(IStatus.ERROR, Activator.instance().getPluginId(), str));
174 }
175 if (stderrOutput.isEmpty()) {
176 /*
71535b53
MK
177 * At least say "no output", so an error message
178 * actually shows up.
4bd7cc77
AM
179 */
180 status.add(new Status(IStatus.ERROR, Activator.instance().getPluginId(), Messages.ProcessUtils_ErrorNoOutput));
181 }
182 throw new CoreException(status);
183 }
184
185 return lines;
186 }
187
188 } catch (IOException | InterruptedException e) {
189 IStatus status = new Status(IStatus.ERROR, Activator.instance().getPluginId(), Messages.ProcessUtils_ExecutionInterrupted, e);
190 throw new CoreException(status);
191
192 } finally {
193 if (cancellerRunnable != null) {
194 cancellerRunnable.setFinished();
195 }
196 if (cancellerThread != null) {
197 try {
198 cancellerThread.join();
199 } catch (InterruptedException e) {
71535b53
MK
200 /*
201 * If it is interrupted, process is terminated.
202 */
4bd7cc77
AM
203 }
204 }
205
206 monitor.done();
207 }
208 }
209
210 /**
211 * Internal wrapper class that allows forcibly stopping a {@link Process}
212 * when its corresponding progress monitor is cancelled.
213 */
214 private static class CancellableRunnable implements Runnable {
215
71535b53 216 private static final int SLEEP_DURATION = 500;
4bd7cc77
AM
217 private final Process fProcess;
218 private final IProgressMonitor fMonitor;
219
220 private boolean fIsFinished = false;
221
222 public CancellableRunnable(Process process, IProgressMonitor monitor) {
223 fProcess = process;
224 fMonitor = monitor;
225 }
226
227 public void setFinished() {
228 fIsFinished = true;
229 }
230
231 @Override
232 public void run() {
233 try {
234 while (!fIsFinished) {
71535b53 235 Thread.sleep(SLEEP_DURATION);
4bd7cc77
AM
236 if (fMonitor.isCanceled()) {
237 fProcess.destroy();
238 return;
239 }
240 }
241 } catch (InterruptedException e) {
71535b53
MK
242 /*
243 * If it is interrupted, process is terminated.
244 */
4bd7cc77
AM
245 }
246 }
247
248 }
249}
This page took 0.045863 seconds and 5 git commands to generate.