|
|||||||||||||||||||
Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
ProcessChain.java | 0% | 0% | 0% | 0% |
|
1 | /*BEGIN_COPYRIGHT_BLOCK | |
2 | * | |
3 | * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu) | |
4 | * All rights reserved. | |
5 | * | |
6 | * Redistribution and use in source and binary forms, with or without | |
7 | * modification, are permitted provided that the following conditions are met: | |
8 | * * Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * * Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the | |
14 | * names of its contributors may be used to endorse or promote products | |
15 | * derived from this software without specific prior written permission. | |
16 | * | |
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
21 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
28 | * | |
29 | * This software is Open Source Initiative approved Open Source Software. | |
30 | * Open Source Initative Approved is a trademark of the Open Source Initiative. | |
31 | * | |
32 | * This file is part of DrJava. Download the current version of this project | |
33 | * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/ | |
34 | * | |
35 | * END_COPYRIGHT_BLOCK*/ | |
36 | ||
37 | package edu.rice.cs.util; | |
38 | ||
39 | import java.io.*; | |
40 | import java.util.Set; | |
41 | import java.util.HashSet; | |
42 | ||
43 | import edu.rice.cs.drjava.ui.DrJavaErrorHandler; | |
44 | import edu.rice.cs.util.JoinInputStream; | |
45 | ||
46 | /** | |
47 | * This class represents a piping chain of processes, in which the output of the first | |
48 | * process is piped into the input of the second process, and so on. The class allows | |
49 | * the entire chain to be treated as if it were just one process. | |
50 | * The constructor also sets up the input and output streams of the individual processes | |
51 | * so they can function as a chain. | |
52 | */ | |
53 | ||
54 | public class ProcessChain extends Process { | |
55 | /** Separator used between different process chains. */ | |
56 | public static final char PROCESS_SEPARATOR_CHAR = '#'; | |
57 | ||
58 | /** Separator used between different process chains. */ | |
59 | public static final String PROCESS_SEPARATOR = String.valueOf(PROCESS_SEPARATOR_CHAR); | |
60 | ||
61 | /** Separator used between processes inside the same process chain. */ | |
62 | public static final char PIPE_SEPARATOR_CHAR = '|'; | |
63 | ||
64 | /** Separator used between processes inside the same process chain. */ | |
65 | public static final String PIPE_SEPARATOR = String.valueOf(PIPE_SEPARATOR_CHAR); | |
66 | ||
67 | /** The process creators that create the processes in this process chain. */ | |
68 | protected ProcessCreator[] _creators; | |
69 | ||
70 | /** The processes inside this piping chain. */ | |
71 | protected Process[] _processes; | |
72 | ||
73 | /** True when execution of this chain has been aborted. */ | |
74 | protected boolean _aborted = false; | |
75 | ||
76 | /** The redirector threads that move output (both stdout and stderr) from one process | |
77 | * to the input of the next process. */ | |
78 | protected Set<StreamRedirectThread> _redirectors = new HashSet<StreamRedirectThread>(); | |
79 | ||
80 | /** The combined input stream of all processes. */ | |
81 | protected PipedInputStream _combinedInputStream; | |
82 | ||
83 | /** The stream into which all outputs to stdout are written. */ | |
84 | protected PipedOutputStream _combinedStdOutStream; | |
85 | ||
86 | /** The combined input stream of all the processes, plus a debug stream. */ | |
87 | protected JoinInputStream _combinedInputJoinedWithDebugStream; | |
88 | ||
89 | /** Debug output that gets joined with the streams from the processes. */ | |
90 | protected PrintWriter _debugOutput; | |
91 | ||
92 | /** Debug input and output stream. */ | |
93 | protected PipedInputStream _debugInputStream; | |
94 | protected PipedOutputStream _debugOutputStream; | |
95 | ||
96 | /** The combined error stream of all processes. */ | |
97 | protected PipedInputStream _combinedErrorStream; | |
98 | ||
99 | /** The stream into which all outputs to stderr are written. */ | |
100 | protected PipedOutputStream _combinedStdErrStream; | |
101 | ||
102 | /** Threads that wait for the subprocesses to terminate. */ | |
103 | // protected Thread[] _deathThreads; | |
104 | ||
105 | /** Constructor for a process chain consisting of the individual processes provided. | |
106 | * @param pcs array of ProcessCreators */ | |
107 | 0 | public ProcessChain(ProcessCreator[] pcs) { |
108 | 0 | _creators = pcs; |
109 | 0 | _processes = new Process[_creators.length]; |
110 | ||
111 | 0 | _combinedInputStream = new PipedInputStream(); |
112 | 0 | try { |
113 | 0 | _combinedStdOutStream = new PipedOutputStream(_combinedInputStream); |
114 | 0 | _combinedInputStream.connect(_combinedStdOutStream); |
115 | } | |
116 | catch(IOException e) { /* ignore, no output if this goes wrong */ } | |
117 | ||
118 | 0 | _debugInputStream = new PipedInputStream(); |
119 | 0 | try { |
120 | 0 | _debugOutputStream = new PipedOutputStream(_debugInputStream); |
121 | 0 | _debugInputStream.connect(_debugOutputStream); |
122 | } | |
123 | catch(IOException e) { /* ignore, no output if this goes wrong */ } | |
124 | 0 | _combinedInputJoinedWithDebugStream = new JoinInputStream(_combinedInputStream, _debugInputStream); |
125 | 0 | _debugOutput = new PrintWriter(new OutputStreamWriter(_debugOutputStream)); |
126 | ||
127 | 0 | _combinedErrorStream = new PipedInputStream(); |
128 | 0 | try { |
129 | 0 | _combinedStdErrStream = new PipedOutputStream(_combinedErrorStream); |
130 | 0 | _combinedErrorStream.connect(_combinedStdErrStream); |
131 | } | |
132 | catch(IOException e) { /* ignore, no output if this goes wrong */ } | |
133 | ||
134 | // _deathThreads = new Thread[_creators.length]; | |
135 | 0 | for(int i = 0; i < _processes.length; ++i) { |
136 | // final int index = i; | |
137 | 0 | try { |
138 | 0 | _processes[i] = _creators[i].start(); |
139 | // _deathThreads[i] = new Thread(new Runnable() { | |
140 | // public void run() { | |
141 | // boolean interrupted = false; | |
142 | // do { | |
143 | // interrupted = false; | |
144 | // try { | |
145 | // _processes[index].waitFor(); | |
146 | // } | |
147 | // catch(InterruptedException e) { interrupted = true; } | |
148 | // } while(interrupted); | |
149 | // GeneralProcessCreator.LOG.log("Process " + index + " has terminated"); | |
150 | // } | |
151 | // }); | |
152 | // _deathThreads[i].start(); | |
153 | } | |
154 | catch(IOException e) { | |
155 | 0 | GeneralProcessCreator.LOG.log("\nIOException in external process: " + e.getMessage() + "\nCheck your command line.\n"); |
156 | // could not start the process, record error and abort | |
157 | 0 | _debugOutput.println("\nIOException in external process: " + e.getMessage() + "\nCheck your command line.\n"); |
158 | 0 | _debugOutput.flush(); |
159 | 0 | _aborted = true; |
160 | 0 | destroy(); |
161 | 0 | return; |
162 | } | |
163 | } | |
164 | ||
165 | 0 | for(int i = 0; i < _processes.length-1; ++i) { |
166 | // _processes.length-1 because we're processing the gaps between the processes: | |
167 | // (P0 P1 P2) has two gaps: P0-P1 and P1-P2. There's always one less gap than processes. | |
168 | 0 | StreamRedirectThread r = new StreamRedirectThread("stdout Redirector " + i, |
169 | _processes[i].getInputStream(), | |
170 | _processes[i+1].getOutputStream(), | |
171 | new ProcessChainThreadGroup(this)); | |
172 | 0 | _redirectors.add(r); |
173 | 0 | r.start(); |
174 | 0 | r = new StreamRedirectThread("stderr Redirector " + i, |
175 | _processes[i].getErrorStream(), | |
176 | _processes[i+1].getOutputStream(), | |
177 | new ProcessChainThreadGroup(this)); | |
178 | 0 | _redirectors.add(r); |
179 | 0 | r.start(); |
180 | } | |
181 | // now pipe output from the last process into our output streams | |
182 | 0 | StreamRedirectThread r = new StreamRedirectThread("stdout Redirector " + (_processes.length-1), |
183 | _processes[_processes.length-1].getInputStream(), | |
184 | _combinedStdOutStream, | |
185 | new ProcessChainThreadGroup(this)); | |
186 | 0 | _redirectors.add(r); |
187 | 0 | r.start(); |
188 | 0 | r = new StreamRedirectThread("stderr Redirector " + (_processes.length-1), |
189 | _processes[_processes.length-1].getErrorStream(), | |
190 | _combinedStdErrStream, | |
191 | new ProcessChainThreadGroup(this)); | |
192 | 0 | _redirectors.add(r); |
193 | 0 | r.start(); |
194 | // _debugOutput.println("\n\nProcessChain started\n\n"); | |
195 | // _debugOutput.flush(); | |
196 | } | |
197 | ||
198 | // /** | |
199 | // * Gets the output stream of the process chain, i.e. the output | |
200 | // * stream of the first process in the chain. | |
201 | // * | |
202 | // * @return the output stream of the process chain. | |
203 | // */ | |
204 | // public OutputStream getOutputStream() { | |
205 | // if (_aborted) { | |
206 | // return new OutputStream() { | |
207 | // public void write(int b) throws IOException { } | |
208 | // }; | |
209 | // } | |
210 | // return _processes[0].getOutputStream(); | |
211 | // } | |
212 | // | |
213 | // /** | |
214 | // * Gets the error stream of the process chain, i.e. the error | |
215 | // * stream of the last process in the chain. | |
216 | // * | |
217 | // * @return the error stream of the process chain. | |
218 | // */ | |
219 | // public InputStream getErrorStream() { | |
220 | // if (_aborted) { | |
221 | // return new InputStream() { | |
222 | // public int read() throws IOException { return -1; } | |
223 | // }; | |
224 | // } | |
225 | // return _processes[_processes.length-1].getErrorStream(); | |
226 | // } | |
227 | // | |
228 | // /** | |
229 | // * Gets the input stream of the process chain, i.e. the input | |
230 | // * stream of the first process in the chain. | |
231 | // * | |
232 | // * @return the input stream of the process chain | |
233 | // */ | |
234 | // public InputStream getInputStream() { | |
235 | // if (_aborted) { | |
236 | // return new InputStream() { | |
237 | // public int read() throws IOException { return -1; } | |
238 | // }; | |
239 | // } | |
240 | // return _processes[_processes.length-1].getInputStream(); | |
241 | // } | |
242 | ||
243 | /** | |
244 | * Gets the output stream of the process sequence, i.e. the combined | |
245 | * output stream of all the processes in the sequence. | |
246 | * | |
247 | * @return the output stream of the process sequence. | |
248 | */ | |
249 | 0 | public OutputStream getOutputStream() { |
250 | 0 | if (_aborted) { |
251 | 0 | return new OutputStream() { |
252 | 0 | public void write(int b) throws IOException { } |
253 | }; | |
254 | } | |
255 | else { | |
256 | 0 | return new BufferedOutputStream(_processes[0].getOutputStream()); |
257 | } | |
258 | } | |
259 | ||
260 | /** | |
261 | * Gets the error stream of the process sequence, i.e. the combined | |
262 | * error stream of all the processes in the sequence. | |
263 | * | |
264 | * @return the error stream of the process sequence. | |
265 | */ | |
266 | 0 | public InputStream getErrorStream() { |
267 | 0 | return _combinedErrorStream; |
268 | } | |
269 | ||
270 | /** | |
271 | * Gets the input stream of the process sequence, i.e. the combined | |
272 | * input stream of all the processes in the sequence. | |
273 | * | |
274 | * @return the input stream of the process chain | |
275 | */ | |
276 | 0 | public InputStream getInputStream() { |
277 | 0 | return _combinedInputJoinedWithDebugStream; |
278 | } | |
279 | ||
280 | /** | |
281 | * Causes the current thread to wait, if necessary, until the | |
282 | * process chain has terminated, i.e. until all processes in the | |
283 | * chain have terminated. This method returns immediately if | |
284 | * all subprocesses have already terminated. If any of the | |
285 | * subprocess has not yet terminated, the calling thread will be | |
286 | * blocked until all subprocesses exit. | |
287 | * | |
288 | * @return the exit value of the process chain, i.e. the | |
289 | * exit code of the last process in the chain. | |
290 | * @exception InterruptedException if the current thread is | |
291 | * {@link Thread#interrupt() interrupted} by another thread | |
292 | * while it is waiting, then the wait is ended and an | |
293 | * {@link InterruptedException} is thrown. | |
294 | */ | |
295 | 0 | public int waitFor() throws InterruptedException { |
296 | 0 | if (_aborted) { return -1; } |
297 | 0 | int exitCode = 0; |
298 | 0 | for(int i = 0; i < _processes.length; ++i) { |
299 | 0 | exitCode = _processes[i].waitFor(); |
300 | // if (i < _processes.length-1) { | |
301 | // _stdOutRedirectors.get(i).setStopFlag(); | |
302 | // _stdErrRedirectors.get(i).setStopFlag(); | |
303 | // } | |
304 | // try { | |
305 | // _processes[i].getInputStream().close(); | |
306 | // _processes[i].getErrorStream().close(); | |
307 | // if (i < _processes.length-1) { | |
308 | // _processes[i+1].getOutputStream().close(); | |
309 | // } | |
310 | // } | |
311 | // catch(IOException e) { /* ignore, just don't close streams */ } | |
312 | } | |
313 | // stopAllRedirectors(); | |
314 | 0 | return exitCode; |
315 | } | |
316 | ||
317 | /** | |
318 | * Returns the exit value for the subprocess. | |
319 | * | |
320 | * @return the exit value of the subprocess represented by this | |
321 | * <code>Process</code> object. by convention, the value | |
322 | * <code > 0</code> indicates normal termination. | |
323 | * @exception IllegalThreadStateException if the subprocess represented | |
324 | * by this <code>Process</code> object has not yet terminated. | |
325 | */ | |
326 | 0 | public int exitValue() { |
327 | 0 | if (_aborted) { return -1; } |
328 | 0 | int exitCode = 0; |
329 | // executing this loop guarantees that the IllegalThreadStateException | |
330 | // is thrown when at least one thread has not terminated yet. | |
331 | // just returning the exit value of the last process is not sufficient: | |
332 | // the last process could have terminated while other processes have not. | |
333 | 0 | for(int i = 0; i < _processes.length; ++i) { |
334 | 0 | exitCode = _processes[i].exitValue(); |
335 | } | |
336 | 0 | return exitCode; |
337 | } | |
338 | ||
339 | /** | |
340 | * Kills all subprocesses. The subprocesses represented by this | |
341 | * <code>ProcessChain</code> object is forcibly terminated. | |
342 | */ | |
343 | 0 | public void destroy() { |
344 | 0 | _aborted = true; |
345 | 0 | for(int i = 0; i < _processes.length; ++i) { |
346 | 0 | _processes[i].destroy(); |
347 | } | |
348 | 0 | stopAllRedirectors(); |
349 | } | |
350 | ||
351 | /** Set the stop flags for all redirector threads. */ | |
352 | 0 | protected void stopAllRedirectors() { |
353 | 0 | for(StreamRedirectThread r: _redirectors) { r.setStopFlag(); } |
354 | 0 | _redirectors.clear(); |
355 | } | |
356 | ||
357 | /** Thread group for all threads that deal with this process sequence. */ | |
358 | protected static class ProcessChainThreadGroup extends ThreadGroup { | |
359 | private ProcessChain _chain; | |
360 | private PrintWriter _debugOut; | |
361 | 0 | public ProcessChainThreadGroup(ProcessChain chain) { |
362 | 0 | super("Process Chain Thread Group"); |
363 | 0 | _chain = chain; |
364 | 0 | _debugOut = _chain._debugOutput; |
365 | } | |
366 | 0 | public void uncaughtException(Thread t, Throwable e) { |
367 | 0 | destroy(); |
368 | 0 | if ((e instanceof StreamRedirectException) && |
369 | (e.getCause() instanceof java.io.IOException)) { | |
370 | 0 | _debugOut.println("\n\n\nAn exception occurred during the execution of the command line:\n" + |
371 | e.toString() + "\n\n"); | |
372 | } | |
373 | else { | |
374 | 0 | DrJavaErrorHandler.record(e); |
375 | } | |
376 | } | |
377 | } | |
378 | } |
|