source: main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java@ 31642

Last change on this file since 31642 was 31642, checked in by ak19, 7 years ago

For Windows, process.destroy() doesn't get called on interruption because the join() calls complete and process is set to null, so not triggering process.destroy(). Now the code explicitly triggers process.destroy() on cancel/interruptedException and on any Exception, by means of new member forciblyTerminateProcess. And the code no longer sets process to null until the very end of finishing up with the process.

File size: 28.1 KB
Line 
1package org.greenstone.gatherer.util;
2
3import java.io.BufferedReader;
4import java.io.BufferedWriter;
5import java.io.Closeable;
6import java.io.File;
7import java.io.InputStream;
8import java.io.InputStreamReader;
9import java.io.IOException;
10import java.io.OutputStream;
11import java.io.OutputStreamWriter;
12import java.util.Arrays;
13
14import org.apache.log4j.*;
15
16import org.greenstone.gatherer.DebugStream;
17
18// Use this class to run a Java Process. It follows the good and safe practices at
19// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
20// to avoid blocking problems that can arise from a Process' input and output streams.
21
22public class SafeProcess {
23 //public static int DEBUG = 0;
24
25 public static final int STDERR = 0;
26 public static final int STDOUT = 1;
27 public static final int STDIN = 2;
28
29 // charset for reading process stderr and stdout streams
30 //public static final String UTF8 = "UTF-8";
31
32 ///static Logger logger = Logger.getLogger(org.greenstone.util.SafeProcess.class.getName());
33
34 // input to SafeProcess and initialising it
35 private String command = null;
36 private String[] command_args = null;
37 private String[] envp = null;
38 private File dir = null;
39 private String inputStr = null;
40 private Process process = null;
41 private boolean forciblyTerminateProcess = false;
42
43 // output from running SafeProcess.runProcess()
44 private String outputStr = "";
45 private String errorStr = "";
46 private int exitValue = -1;
47 //private String charset = null;
48
49 // allow callers to process exceptions of the main process thread if they want
50 private ExceptionHandler exceptionHandler = null;
51
52 // whether std/err output should be split at new lines
53 private boolean splitStdOutputNewLines = false;
54 private boolean splitStdErrorNewLines = false;
55
56 // call one of these constructors
57
58 // cmd args version
59 public SafeProcess(String[] cmd_args)
60 {
61 command_args = cmd_args;
62 }
63
64 // cmd string version
65 public SafeProcess(String cmdStr)
66 {
67 command = cmdStr;
68 }
69
70 // cmd args with env version, launchDir can be null.
71 public SafeProcess(String[] cmd_args, String[] envparams, File launchDir)
72 {
73 command_args = cmd_args;
74 envp = envparams;
75 dir = launchDir;
76 }
77
78 // The important methods:
79 // to get the output from std err and std out streams after the process has been run
80 public String getStdOutput() { return outputStr; }
81 public String getStdError() { return errorStr; }
82 public int getExitValue() { return exitValue; }
83
84 //public void setStreamCharSet(String charset) { this.charset = charset; }
85
86 // set any string to send as input to the process spawned by SafeProcess
87 public void setInputString(String sendStr) {
88 inputStr = sendStr;
89 }
90
91 // register a SafeProcess ExceptionHandler whose gotException() method will
92 // get called for each exception encountered
93 public void setExceptionHandler(ExceptionHandler exception_handler) {
94 exceptionHandler = exception_handler;
95 }
96
97 // set if you want the std output or err output to have \n at each newline read from the stream
98 public void setSplitStdOutputNewLines(boolean split) {
99 splitStdOutputNewLines = split;
100 }
101 public void setSplitStdErrorNewLines(boolean split) {
102 splitStdErrorNewLines = split;
103 }
104
105 private Process doRuntimeExec() throws IOException {
106 Process prcs = null;
107 Runtime rt = Runtime.getRuntime();
108
109 if(this.command != null) {
110 log("SafeProcess running: " + command);
111 prcs = rt.exec(this.command);
112 }
113 else { // at least command_args must be set now
114
115 // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
116 log("SafeProcess running: " + Arrays.toString(command_args));
117
118 if(this.envp == null) {
119 prcs = rt.exec(this.command_args);
120 } else { // launch process using cmd str with env params
121
122 if(this.dir == null) {
123 //log("\twith: " + Arrays.toString(this.envp));
124 prcs = rt.exec(this.command_args, this.envp);
125 } else {
126 //log("\tfrom directory: " + this.dir);
127 //log("\twith: " + Arrays.toString(this.envp));
128 prcs = rt.exec(this.command_args, this.envp, this.dir);
129 }
130 }
131 }
132
133 return prcs;
134 }
135
136 // Copied from gli's gui/FormatConversionDialog.java
137 private int waitForWithStreams(SafeProcess.OutputStreamGobbler inputGobbler,
138 SafeProcess.InputStreamGobbler outputGobbler,
139 SafeProcess.InputStreamGobbler errorGobbler)
140 throws IOException, InterruptedException
141 {
142 // kick off the stream gobblers
143 inputGobbler.start();
144 errorGobbler.start();
145 outputGobbler.start();
146
147 // any error???
148 try {
149 this.exitValue = process.waitFor(); // can throw an InterruptedException if process did not terminate
150 } catch(InterruptedException ie) {
151 log("*** Process interrupted (InterruptedException). Expected to be a Cancel operation.");
152 // don't print stacktrace: an interrupt here is not an error, it's expected to be a cancel action
153 if(exceptionHandler != null) {
154 exceptionHandler.gotException(ie);
155 }
156
157 // propagate interrupts to worker threads here
158 // unless the interrupt emanated from any of them in any join(),
159 // which will be caught by caller's catch on InterruptedException.
160 // Only if the thread that SafeProcess runs in was interrupted
161 // should we propagate the interrupt to the worker threads.
162 // http://stackoverflow.com/questions/2126997/who-is-calling-the-java-thread-interrupt-method-if-im-not
163 // "I know that in JCiP it is mentioned that you should never interrupt threads you do not own"
164 // But SafeProcess owns the worker threads, so it has every right to interrupt them
165 // Also read http://stackoverflow.com/questions/13623445/future-cancel-method-is-not-working?noredirect=1&lq=1
166
167 // http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java
168 // http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
169 // "Only code that implements a thread's interruption policy may swallow an interruption request. General-purpose task and library code should never swallow interruption requests."
170 // Does that mean that since this code implements this thread's interruption policy, it's ok
171 // to swallow the interrupt this time and not let it propagate by commenting out the next line?
172 //Thread.currentThread().interrupt(); // re-interrupt the thread
173
174 inputGobbler.interrupt();
175 errorGobbler.interrupt();
176 outputGobbler.interrupt();
177
178 // Since we have been cancelled (InterruptedException), or on any Exception, we need
179 // to forcibly terminate process eventually after the finally code first waits for each worker thread
180 // to die off. Don't set process=null until after we've forcibly terminated it if needs be.
181 this.forciblyTerminateProcess = true;
182
183 // even after the interrupts, we want to proceed to calling join() on all the worker threads
184 // in order to wait for each of them to die before attempting to destroy the process if it
185 // still hasn't terminated after all that.
186 } finally {
187
188 //log("Process exitValue: " + exitValue);
189
190 // From the comments of
191 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
192 // To avoid running into nondeterministic failures to get the process output
193 // if there's no waiting for the threads, call join() on each Thread (StreamGobbler) object.
194 // From Thread API: join() "Waits for this thread (the thread join() is invoked on) to die."
195
196 // Wait for each of the threads to die, before attempting to destroy the process
197 // Any of these can throw InterruptedExceptions too
198 // and will be processed by the calling function's catch on InterruptedException.
199 // However, no one besides us will interrupting these threads I think...
200 // and we won't be throwing the InterruptedException from within the threads...
201 // So if any streamgobbler.join() call throws an InterruptedException, that would be unexpected
202
203 outputGobbler.join();
204 errorGobbler.join();
205 inputGobbler.join();
206
207
208 // set the variables that the code which created a SafeProcess object may want to inspect
209 this.outputStr = outputGobbler.getOutput();
210 this.errorStr = errorGobbler.getOutput();
211 }
212
213 // Don't return from finally, it's considered an abrupt completion and exceptions are lost, see
214 // http://stackoverflow.com/questions/18205493/can-we-use-return-in-finally-block
215 return this.exitValue;
216 }
217
218
219 public synchronized boolean processRunning() {
220 if(process == null) return false;
221 return SafeProcess.processRunning(this.process);
222 }
223
224 // Run a very basic process: with no reading from or writing to the Process' iostreams,
225 // this just execs the process and waits for it to return
226 public int runBasicProcess() {
227 try {
228 this.forciblyTerminateProcess = true;
229
230 // 1. create the process
231 process = doRuntimeExec();
232 // 2. basic waitFor the process to finish
233 this.exitValue = process.waitFor();
234
235 this.forciblyTerminateProcess = false;
236 } catch(IOException ioe) {
237
238 if(exceptionHandler != null) {
239 exceptionHandler.gotException(ioe);
240 } else {
241 log("IOException: " + ioe.getMessage(), ioe);
242 }
243 } catch(InterruptedException ie) {
244
245 if(exceptionHandler != null) {
246 exceptionHandler.gotException(ie);
247 } else { // Unexpected InterruptedException, so printstacktrace
248 log("Process InterruptedException: " + ie.getMessage(), ie);
249 }
250
251 Thread.currentThread().interrupt();
252 } finally {
253
254 if( this.forciblyTerminateProcess ) {
255 process.destroy(); // see runProcess() below
256 }
257 process = null;
258 this.forciblyTerminateProcess = false; // reset
259 }
260 return this.exitValue;
261 }
262
263 // Runs a process with default stream processing. Returns the exitValue
264 public int runProcess() {
265 return runProcess(null, null, null); // use default processing of all 3 of the process' iostreams
266 }
267
268 // Run a process with custom stream processing (any custom handlers passed in that are null
269 // will use the default stream processing).
270 // Returns the exitValue from running the Process
271 public int runProcess(CustomProcessHandler procInHandler,
272 CustomProcessHandler procOutHandler,
273 CustomProcessHandler procErrHandler)
274 {
275 SafeProcess.OutputStreamGobbler inputGobbler = null;
276 SafeProcess.InputStreamGobbler errorGobbler = null;
277 SafeProcess.InputStreamGobbler outputGobbler = null;
278
279 try {
280 this.forciblyTerminateProcess = false;
281
282 // 1. get the Process object
283 process = doRuntimeExec();
284
285
286 // 2. create the streamgobblers and set any specified handlers on them
287
288 // PROC INPUT STREAM
289 if(procInHandler == null) {
290 // send inputStr to process. The following constructor can handle inputStr being null
291 inputGobbler = // WriterToProcessInputStream
292 new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
293 } else { // user will do custom handling of process' InputStream
294 inputGobbler = new SafeProcess.OutputStreamGobbler(process.getOutputStream(), procInHandler);
295 }
296
297 // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
298 if(procErrHandler == null) {
299 errorGobbler // ReaderFromProcessOutputStream
300 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);
301 } else {
302 errorGobbler
303 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), procErrHandler);
304 }
305
306 // PROC OUT STREAM to monitor for the expected std output line(s)
307 if(procOutHandler == null) {
308 outputGobbler
309 = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);
310 } else {
311 outputGobbler
312 = new SafeProcess.InputStreamGobbler(process.getInputStream(), procOutHandler);
313 }
314
315
316 // 3. kick off the stream gobblers
317 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
318
319 } catch(IOException ioe) {
320 this.forciblyTerminateProcess = true;
321
322 if(exceptionHandler != null) {
323 exceptionHandler.gotException(ioe);
324 } else {
325 log("IOexception: " + ioe.getMessage(), ioe);
326 }
327 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so print stack trace
328 this.forciblyTerminateProcess = true;
329
330 if(exceptionHandler != null) {
331 exceptionHandler.gotException(ie);
332 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
333 } else {
334 log("*** Unexpected InterruptException when waiting for process stream gobblers to die:" + ie.getMessage(), ie);
335 }
336
337 // see comments in other runProcess()
338 Thread.currentThread().interrupt();
339
340 } finally {
341 //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command;
342 //log("*** In finally of SafeProcess.runProcess(3 params): " + cmd);
343
344 if( this.forciblyTerminateProcess ) {
345 log("*** Going to call process.destroy 2");
346 process.destroy();
347 log("*** Have called process.destroy 2");
348 }
349 process = null;
350 this.forciblyTerminateProcess = false; // reset
351 }
352
353 return this.exitValue;
354 }
355
356 public int runProcess(LineByLineHandler outLineByLineHandler, LineByLineHandler errLineByLineHandler)
357 {
358 SafeProcess.OutputStreamGobbler inputGobbler = null;
359 SafeProcess.InputStreamGobbler errorGobbler = null;
360 SafeProcess.InputStreamGobbler outputGobbler = null;
361
362 try {
363 this.forciblyTerminateProcess = false;
364
365 // 1. get the Process object
366 process = doRuntimeExec();
367
368
369 // 2. create the streamgobblers and set any specified handlers on them
370
371 // PROC INPUT STREAM
372 // send inputStr to process. The following constructor can handle inputStr being null
373 inputGobbler = // WriterToProcessInputStream
374 new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
375
376 // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
377 errorGobbler // ReaderFromProcessOutputStream
378 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);
379 // PROC OUT STREAM to monitor for the expected std output line(s)
380 outputGobbler
381 = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);
382
383
384 // 3. register line by line handlers, if any were set, for the process stderr and stdout streams
385 if(outLineByLineHandler != null) {
386 outputGobbler.setLineByLineHandler(outLineByLineHandler);
387 }
388 if(errLineByLineHandler != null) {
389 errorGobbler.setLineByLineHandler(errLineByLineHandler);
390 }
391
392
393 // 4. kick off the stream gobblers
394 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
395
396 } catch(IOException ioe) {
397 this.forciblyTerminateProcess = true;
398
399 if(exceptionHandler != null) {
400 exceptionHandler.gotException(ioe);
401 } else {
402 log("IOexception: " + ioe.getMessage(), ioe);
403 }
404 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so log it
405 this.forciblyTerminateProcess = true;
406
407 if(exceptionHandler != null) {
408 exceptionHandler.gotException(ie);
409 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
410 } else {
411 log("*** Unexpected InterruptException when waiting for process stream gobblers to die: " + ie.getMessage(), ie);
412 }
413 // We're not causing any interruptions that may occur when trying to stop the worker threads
414 // So resort to default behaviour in this catch?
415 // "On catching InterruptedException, re-interrupt the thread."
416 // This is just how InterruptedExceptions tend to be handled
417 // See also http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
418 // and https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
419 Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop?
420
421 } finally {
422
423 // Moved into here from GS2PerlConstructor and GShell.runLocal() which said
424 // "I need to somehow kill the child process. Unfortunately Thread.stop() and Process.destroy() both fail to do this. But now, thankx to the magic of Michaels 'close the stream suggestion', it works fine (no it doesn't!)"
425 // http://steveliles.github.io/invoking_processes_from_java.html
426 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
427 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
428
429 //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command;
430 //log("*** In finally of SafeProcess.runProcess(2 params): " + cmd);
431
432 if( this.forciblyTerminateProcess ) {
433 log("*** Going to call process.destroy 1");
434 process.destroy();
435 log("*** Have called process.destroy 1");
436 }
437 process = null;
438 this.forciblyTerminateProcess = false; //reset
439 }
440
441 return this.exitValue;
442 }
443
444
445//******************** Inner class and interface definitions ********************//
446// Static inner classes can be instantiated without having to instantiate an object of the outer class first
447
448// Can have public static interfaces too,
449// see http://stackoverflow.com/questions/71625/why-would-a-static-nested-interface-be-used-in-java
450// Implementors need to take care that the implementations are thread safe
451// http://stackoverflow.com/questions/14520814/why-synchronized-method-is-not-included-in-interface
452public static interface ExceptionHandler {
453
454 // when implementing ExceptionHandler.gotException(), if it manipulates anything that's
455 // not threadsafe, declare gotException() as a synchronized method to ensure thread safety
456 public void gotException(Exception e); // can't declare as synchronized in interface method declaration
457}
458
459// Write your own run() body for any StreamGobbler. You need to create an instance of a class
460// extending CustomProcessHandler for EACH IOSTREAM of the process that you want to handle.
461// Do not create a single CustomProcessHandler instance and reuse it for all three streams,
462// i.e. don't call SafeProcess' runProcess(x, x, x); It should be runProcess(x, y, z).
463// Make sure your implementation is threadsafe if you're sharing immutable objects between the threaded streams
464// example implementation is in the GS2PerlConstructor.SynchronizedProcessHandler class.
465// CustomProcessHandler is made an abstract class instead of an interface to force classes that want
466// to use a CustomProcessHandler to create a separate class that extends CustomProcessHandler, rather than
467// that the classes that wish to use it "implementing" the CustomProcessHandler interface itself: the
468// CustomProcessHandler.run() method may then be called in the major thread from which the Process is being
469// executed, rather than from the individual threads that deal with each iostream of the Process.
470public static abstract class CustomProcessHandler {
471
472 protected final int source;
473
474 protected CustomProcessHandler(int src) {
475 this.source = src; // STDERR or STDOUT or STDIN
476 }
477
478 public String getThreadNamePrefix() {
479 return SafeProcess.streamToString(this.source);
480 }
481
482 public abstract void run(Closeable stream); //InputStream or OutputStream
483}
484
485// When using the default stream processing to read from a process' stdout or stderr stream, you can
486// create a class extending LineByLineHandler for the process' err stream and one for its output stream
487// to do something on a line by line basis, such as sending the line to a log
488public static abstract class LineByLineHandler {
489 protected final int source;
490
491 protected LineByLineHandler(int src) {
492 this.source = src; // STDERR or STDOUT
493 }
494
495 public String getThreadNamePrefix() {
496 return SafeProcess.streamToString(this.source);
497 }
498
499 public abstract void gotLine(String line); // first non-null line
500 public abstract void gotException(Exception e); // for when an exception occurs instead of getting a line
501}
502
503
504//**************** StreamGobbler Inner class definitions (stream gobblers copied from GLI) **********//
505
506// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
507// This class is used in FormatConversionDialog to properly read from the stdout and stderr
508// streams of a Process, Process.getInputStream() and Process.getErrorSream()
509public static class InputStreamGobbler extends Thread
510{
511 private InputStream is = null;
512 private StringBuffer outputstr = new StringBuffer();
513 private boolean split_newlines = false;
514 private CustomProcessHandler customHandler = null;
515 private LineByLineHandler lineByLineHandler = null;
516
517 protected InputStreamGobbler() {
518 super("InputStreamGobbler");
519 }
520
521 public InputStreamGobbler(InputStream is)
522 {
523 this(); // sets thread name
524 this.is = is;
525 this.split_newlines = false;
526 }
527
528 public InputStreamGobbler(InputStream is, boolean split_newlines)
529 {
530 this(); // sets thread name
531 this.is = is;
532 this.split_newlines = split_newlines;
533 }
534
535 public InputStreamGobbler(InputStream is, CustomProcessHandler customHandler)
536 {
537 this(); // thread name
538 this.is = is;
539 this.customHandler = customHandler;
540 this.adjustThreadName(customHandler.getThreadNamePrefix());
541 }
542
543
544 private void adjustThreadName(String prefix) {
545 this.setName(prefix + this.getName());
546 }
547
548 public void setLineByLineHandler(LineByLineHandler lblHandler) {
549 this.lineByLineHandler = lblHandler;
550 this.adjustThreadName(lblHandler.getThreadNamePrefix());
551 }
552
553 // default run() behaviour
554 public void runDefault()
555 {
556 BufferedReader br = null;
557 try {
558 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
559 String line=null;
560 while ( !this.isInterrupted() && (line = br.readLine()) != null ) {
561
562 //log("@@@ GOT LINE: " + line);
563 outputstr.append(line);
564 if(split_newlines) {
565 outputstr.append(Utility.NEWLINE); // "\n" is system dependent (Win must be "\r\n")
566 }
567
568 if(lineByLineHandler != null) { // let handler deal with newlines
569 lineByLineHandler.gotLine(line);
570 }
571 }
572
573 } catch (IOException ioe) {
574 if(lineByLineHandler != null) {
575 lineByLineHandler.gotException(ioe);
576 } else {
577 log("Exception when reading process stream with " + this.getName() + ": ", ioe);
578 }
579 } finally {
580 if(this.isInterrupted()) {
581 log("@@@ Successfully interrupted " + this.getName() + ".");
582 }
583 SafeProcess.closeResource(br);
584 }
585 }
586
587 public void runCustom() {
588 this.customHandler.run(is);
589 }
590
591 public void run() {
592 if(this.customHandler == null) {
593 runDefault();
594 } else {
595 runCustom();
596 }
597 }
598
599 public String getOutput() {
600 return outputstr.toString(); // implicit toString() call anyway. //return outputstr;
601 }
602} // end static inner class InnerStreamGobbler
603
604
605// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
606// This class is used in FormatConversionDialog to properly write to the inputstream of a Process
607// Process.getOutputStream()
608public static class OutputStreamGobbler extends Thread
609{
610 private OutputStream os = null;
611 private String inputstr = "";
612 private CustomProcessHandler customHandler = null;
613
614 protected OutputStreamGobbler() {
615 super("stdinOutputStreamGobbler"); // thread name
616 }
617
618 public OutputStreamGobbler(OutputStream os) {
619 this(); // set thread name
620 this.os = os;
621 }
622
623 public OutputStreamGobbler(OutputStream os, String inputstr)
624 {
625 this(); // set thread name
626 this.os = os;
627 this.inputstr = inputstr;
628 }
629
630 public OutputStreamGobbler(OutputStream os, CustomProcessHandler customHandler) {
631 this(); // set thread name
632 this.os = os;
633 this.customHandler = customHandler;
634 }
635
636 // default run() behaviour
637 public void runDefault() {
638
639 if (inputstr == null) {
640 return;
641 }
642
643 // also quit if the process was interrupted before we could send anything to its stdin
644 if(this.isInterrupted()) {
645 log(this.getName() + " thread was interrupted.");
646 return;
647 }
648
649 BufferedWriter osw = null;
650 try {
651 osw = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
652 //System.out.println("@@@ SENDING LINE: " + inputstr);
653 osw.write(inputstr, 0, inputstr.length());
654 osw.newLine();//osw.write("\n");
655 osw.flush();
656
657 // Don't explicitly send EOF when using StreamGobblers as below,
658 // as the EOF char is echoed to output.
659 // Flushing the write handle and/or closing the resource seems
660 // to already send EOF silently.
661
662 /*if(Utility.isWindows()) {
663 osw.write("\032"); // octal for Ctrl-Z, EOF on Windows
664 } else { // EOF on Linux/Mac is Ctrl-D
665 osw.write("\004"); // octal for Ctrl-D, see http://www.unix-manuals.com/refs/misc/ascii-table.html
666 }
667 osw.flush();
668 */
669 } catch (IOException ioe) {
670 log("Exception writing to SafeProcess' inputstream: ", ioe);
671 } finally {
672 SafeProcess.closeResource(osw);
673 }
674 }
675
676 // call the user's custom handler for the run() method
677 public void runCustom() {
678 this.customHandler.run(os);
679 }
680
681 public void run()
682 {
683 if(this.customHandler == null) {
684 runDefault();
685 } else {
686 runCustom();
687 }
688 }
689} // end static inner class OutputStreamGobbler
690
691//**************** Static methods **************//
692
693
694 // logger and DebugStream print commands are synchronized, therefore thread safe.
695 public static void log(String msg) {
696 //logger.info(msg);
697
698 System.err.println(msg);
699
700 //DebugStream.println(msg);
701 }
702
703 public static void log(String msg, Exception e) { // Print stack trace on the exception
704 //logger.error(msg, e);
705
706 System.err.println(msg);
707 e.printStackTrace();
708
709 //DebugStream.println(msg);
710 //DebugStream.printStackTrace(e);
711 }
712
713 public static void log(Exception e) {
714 //logger.error(e);
715
716 e.printStackTrace();
717
718 //DebugStream.printStackTrace(e);
719 }
720
721 public static void log(String msg, Exception e, boolean printStackTrace) {
722 if(printStackTrace) {
723 log(msg, e);
724 } else {
725 log(msg);
726 }
727 }
728
729 public static String streamToString(int src) {
730 String stream;
731 switch(src) {
732 case STDERR:
733 stream = "stderr";
734 break;
735 case STDOUT:
736 stream = "stdout";
737 break;
738 default:
739 stream = "stdin";
740 }
741 return stream;
742 }
743
744//**************** Useful static methods. Copied from GLI's Utility.java ******************
745 // For safely closing streams/handles/resources.
746 // For examples of use look in the Input- and OutputStreamGobbler classes.
747 // http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
748 // http://stackoverflow.com/questions/481446/throws-exception-in-finally-blocks
749 public static boolean closeResource(Closeable resourceHandle) {
750 boolean success = false;
751 try {
752 if(resourceHandle != null) {
753 resourceHandle.close();
754 resourceHandle = null;
755 success = true;
756 }
757 } catch(Exception e) {
758 log("Exception closing resource: " + e.getMessage(), e);
759 resourceHandle = null;
760 success = false;
761 } finally {
762 return success;
763 }
764 }
765
766 public static boolean closeProcess(Process prcs) {
767 boolean success = true;
768 if( prcs != null ) {
769 success = success && closeResource(prcs.getErrorStream());
770 success = success && closeResource(prcs.getOutputStream());
771 success = success && closeResource(prcs.getInputStream());
772 prcs.destroy();
773 }
774 return success;
775 }
776
777// Moved from GShell.java
778 /** Determine if the given process is still executing. It does this by attempting to throw an exception - not the most efficient way, but the only one as far as I know
779 * @param process the Process to test
780 * @return true if it is still executing, false otherwise
781 */
782 static public boolean processRunning(Process process) {
783 boolean process_running = false;
784
785 try {
786 process.exitValue(); // This will throw an exception if the process hasn't ended yet.
787 }
788 catch(IllegalThreadStateException itse) {
789 process_running = true;
790 }
791 catch(Exception exception) {
792 log(exception); // DebugStream.printStackTrace(exception);
793 }
794 return process_running;
795 }
796
797} // end class SafeProcess
Note: See TracBrowser for help on using the repository browser.