Index: /main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java
===================================================================
--- /main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java	(revision 31619)
+++ /main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java	(revision 31620)
@@ -90,81 +90,46 @@
     }
 
-//***************** Copied from gli's gui/FormatConversionDialog.java *************//
-
-    public int runProcess() {
-	return runProcess(null, null, null); // use default processing of all 3 of the process' iostreams
-    }
-
-    public int runProcess(CustomProcessHandler procInHandler,
-			   CustomProcessHandler procOutHandler,
-			   CustomProcessHandler procErrHandler)
-    {
+    private Process doRuntimeExec() throws IOException {
 	Process prcs = null;
-	SafeProcess.OutputStreamGobbler inputGobbler = null;
-	SafeProcess.InputStreamGobbler errorGobbler = null;
-	SafeProcess.InputStreamGobbler outputGobbler = null;
-
-	try {	    
-	    
-	    Runtime rt = Runtime.getRuntime();	    
-	    
-	    
-	    if(this.command != null) {
-		prcs = rt.exec(this.command);
-	    }
-	    else { // at least command_args must be set now
-
-		// http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
-		System.err.println("SafeProcess running: " + Arrays.toString(command_args));
-		///logger.info("SafeProcess running: " + Arrays.toString(command_args));
-
-		if(this.envp == null) { 
-		    prcs = rt.exec(this.command_args);
-		} else { // launch process using cmd str with env params		
-		    
-		    if(this.dir == null) {
-			///logger.info("\twith: " + Arrays.toString(this.envp));
-			///System.err.println("\twith: " + Arrays.toString(this.envp));
-			prcs = rt.exec(this.command_args, this.envp);
-		    } else {
-			///logger.info("\tfrom directory: " + this.dir);
-			///logger.info("\twith: " + Arrays.toString(this.envp));
-			///System.err.println("\tfrom directory: " + this.dir);
-			///System.err.println("\twith: " + Arrays.toString(this.envp));
+	Runtime rt = Runtime.getRuntime();
+	
+	if(this.command != null) {
+	    prcs = rt.exec(this.command);
+	}
+	else { // at least command_args must be set now
+	    
+	    // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
+	    //logger.info("SafeProcess running: " + Arrays.toString(command_args));
+	    System.err.println("SafeProcess running: " + Arrays.toString(command_args));
+	    
+	    if(this.envp == null) { 
+		prcs = rt.exec(this.command_args);
+	    } else { // launch process using cmd str with env params		
+		
+		if(this.dir == null) {
+		    ///logger.info("\twith: " + Arrays.toString(this.envp));
+		    ///System.err.println("\twith: " + Arrays.toString(this.envp));
+		    prcs = rt.exec(this.command_args, this.envp);
+		} else {
+		    ///logger.info("\tfrom directory: " + this.dir);
+		    ///logger.info("\twith: " + Arrays.toString(this.envp));
+		    ///System.err.println("\tfrom directory: " + this.dir);
+		    ///System.err.println("\twith: " + Arrays.toString(this.envp));
 			prcs = rt.exec(this.command_args, this.envp, this.dir);
-		    }
 		}
 	    }
-
-	    // Create the streamgobblers and set any specified handlers on them
-
-	    // PROC INPUT STREAM
-	    if(procInHandler == null) {
-		// send inputStr to process. The following constructor can handle inputStr being null
-		inputGobbler = // WriterToProcessInputStream
-		    new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), this.inputStr);
-	    } else { // user will do custom handling of process' InputStream 
-		inputGobbler = new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), procInHandler);
-	    }
-
-	    // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
-	    if(procErrHandler == null) {
-		errorGobbler // ReaderFromProcessOutputStream
-		    = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), splitStdErrorNewLines);		
-	    } else {
-		errorGobbler
-		    = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), procErrHandler);
-	    }
-
-            // PROC OUT STREAM to monitor for the expected std output line(s)
-	    if(procOutHandler == null) {
-		outputGobbler
-		    = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), splitStdOutputNewLines);
-	    } else {
-		outputGobbler
-		    = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), procOutHandler);
-	    }
-
-	    
+	}
+
+	return prcs;
+    }
+
+    // Copied from gli's gui/FormatConversionDialog.java
+    private int waitForWithStreams(Process prcs,
+				   SafeProcess.OutputStreamGobbler inputGobbler,
+				   SafeProcess.InputStreamGobbler outputGobbler,
+				   SafeProcess.InputStreamGobbler errorGobbler)
+	throws IOException, InterruptedException
+    {
+
             // kick off the stream gobblers
             inputGobbler.start();
@@ -175,5 +140,6 @@
             this.exitValue = prcs.waitFor(); // can throw an InterruptedException if process did not terminate
 
-            ///System.err.println("Process exitValue: " + exitValue);
+            ///logger.info("Process exitValue: " + exitValue);
+	    ///System.err.println("Process exitValue: " + exitValue); 
 
 	    // From the comments of 
@@ -194,4 +160,101 @@
 	    // Set process to null so we don't forcibly terminate it below with process.destroy()
 	    prcs = null;
+
+	    return this.exitValue;
+    }
+
+
+    // Run a very basic process: with no reading from or writing to the Process' iostreams,
+    // this just execs the process and waits for it to return
+    public int runBasicProcess() {
+	Process prcs = null;
+	try {
+	    // 1. create the process
+	    prcs = doRuntimeExec();
+	    // 2. basic waitFor the process to finish
+	    this.exitValue = prcs.waitFor();
+
+	    
+	} catch(IOException ioe) {
+	    if(exceptionHandler != null) {
+		exceptionHandler.gotException(ioe);
+	    } else {
+		//logger.error("IOException: " + ioe.getMessage(), ioe);
+		System.err.println("IOException " + ioe.getMessage());
+		ioe.printStackTrace();
+	    }
+	} catch(InterruptedException ie) {
+
+	    if(exceptionHandler != null) {
+		exceptionHandler.gotException(ie);
+	    } else {
+		//logger.error("Process InterruptedException: " + ie.getMessage(), ie);
+		System.err.println("Process InterruptedException " + ie.getMessage());
+		///ie.printStackTrace(); // an interrupt here is not an error, it can be a cancel action
+	    }
+	    
+	    Thread.currentThread().interrupt();
+	} finally { 
+
+	    if( prcs != null ) {
+		prcs.destroy(); // see runProcess() below
+	    }
+	}
+	return this.exitValue;
+    }
+
+    // Runs a process with default stream processing
+    public int runProcess() {
+	return runProcess(null, null, null); // use default processing of all 3 of the process' iostreams
+    }
+
+    // Run a process with custom stream processing (any custom handlers passed in that are null
+    // will use the default stream processing)
+    public int runProcess(CustomProcessHandler procInHandler,
+			   CustomProcessHandler procOutHandler,
+			   CustomProcessHandler procErrHandler)
+    {
+	Process prcs = null;
+	SafeProcess.OutputStreamGobbler inputGobbler = null;
+	SafeProcess.InputStreamGobbler errorGobbler = null;
+	SafeProcess.InputStreamGobbler outputGobbler = null;
+
+	try {
+	    // 1. get the Process object
+	    prcs = doRuntimeExec();
+	    
+
+	    // 2. create the streamgobblers and set any specified handlers on them
+
+	    // PROC INPUT STREAM
+	    if(procInHandler == null) {
+		// send inputStr to process. The following constructor can handle inputStr being null
+		inputGobbler = // WriterToProcessInputStream
+		    new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), this.inputStr);
+	    } else { // user will do custom handling of process' InputStream 
+		inputGobbler = new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), procInHandler);
+	    }
+
+	    // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
+	    if(procErrHandler == null) {
+		errorGobbler // ReaderFromProcessOutputStream
+		    = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), splitStdErrorNewLines);		
+	    } else {
+		errorGobbler
+		    = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), procErrHandler);
+	    }
+
+            // PROC OUT STREAM to monitor for the expected std output line(s)
+	    if(procOutHandler == null) {
+		outputGobbler
+		    = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), splitStdOutputNewLines);
+	    } else {
+		outputGobbler
+		    = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), procOutHandler);
+	    }
+
+
+            // 3. kick off the stream gobblers
+	    this.exitValue = waitForWithStreams(prcs, inputGobbler, outputGobbler, errorGobbler);
        
 	} catch(IOException ioe) {
@@ -253,6 +316,103 @@
     }
     
-
-//**************** Inner class definitions (stream gobblers copied from GLI) **********//
+    public int runProcess(LineByLineHandler outLineByLineHandler, LineByLineHandler errLineByLineHandler)
+    {
+	Process prcs = null;
+	SafeProcess.OutputStreamGobbler inputGobbler = null;
+	SafeProcess.InputStreamGobbler errorGobbler = null;
+	SafeProcess.InputStreamGobbler outputGobbler = null;
+
+	try {
+	    // 1. get the Process object
+	    prcs = doRuntimeExec();
+	    
+
+	    // 2. create the streamgobblers and set any specified handlers on them
+
+	    // PROC INPUT STREAM
+	    // send inputStr to process. The following constructor can handle inputStr being null
+	    inputGobbler = // WriterToProcessInputStream
+		new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), this.inputStr);
+
+	    // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr    
+	    errorGobbler // ReaderFromProcessOutputStream
+		    = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), splitStdErrorNewLines);		
+            // PROC OUT STREAM to monitor for the expected std output line(s)
+	    outputGobbler
+		= new SafeProcess.InputStreamGobbler(prcs.getInputStream(), splitStdOutputNewLines);
+
+
+	    // 3. register line by line handlers, if any were set, for the process stderr and stdout streams
+	    if(outLineByLineHandler != null) {
+		outputGobbler.setLineByLineHandler(outLineByLineHandler);
+	    }
+	    if(errLineByLineHandler != null) {
+		errorGobbler.setLineByLineHandler(errLineByLineHandler);
+	    }	    
+
+
+            // 4. kick off the stream gobblers
+	    this.exitValue = waitForWithStreams(prcs, inputGobbler, outputGobbler, errorGobbler);
+       
+	} catch(IOException ioe) {
+	    if(exceptionHandler != null) {
+		exceptionHandler.gotException(ioe);
+	    } else {
+		//logger.error("IOexception: " + ioe.getMessage(), ioe);
+		System.err.println("IOexception " + ioe.getMessage());
+		ioe.printStackTrace();
+	    }
+	} catch(InterruptedException ie) {
+
+	    if(exceptionHandler != null) {
+		exceptionHandler.gotException(ie);
+	    } else {
+		//logger.error("Process InterruptedException: " + ie.getMessage(), ie);
+		System.err.println("Process InterruptedException " + ie.getMessage());
+		///ie.printStackTrace(); // an interrupt here is not an error, it can be a cancel action
+	    }
+
+	    // propagate interrupts to worker threads here?
+	    // unless the interrupt emanated from any of them in any join()...
+	    // Only if the thread SafeProcess runs in was interrupted
+	    // do we propagate the interrupt to the worker threads.
+	    // http://stackoverflow.com/questions/2126997/who-is-calling-the-java-thread-interrupt-method-if-im-not
+	    // "I know that in JCiP it is mentioned that you should never interrupt threads you do not own"
+	    // But SafeProcess owns the worker threads, so it have every right to interrupt them
+	    // Also read http://stackoverflow.com/questions/13623445/future-cancel-method-is-not-working?noredirect=1&lq=1
+	    if(Thread.currentThread().isInterrupted()) {
+		inputGobbler.interrupt();
+		errorGobbler.interrupt();
+		outputGobbler.interrupt();	    
+	    }
+
+	    // On catchingInterruptedException, re-interrupt the thread.
+	    // This is just how InterruptedExceptions tend to be handled
+	    // See also http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
+	    // and https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
+
+	    // http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java
+	    // http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
+	    // "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."
+	    // Does that mean that since this code implements this thread's interruption policy, it's ok
+	    // to swallow the interrupt this time and not let it propagate by commenting out the next line?
+	    Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop?
+	} finally { 
+
+	    // Moved into here from GS2PerlConstructor which said
+	    // "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."
+	    // http://steveliles.github.io/invoking_processes_from_java.html
+	    // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
+	    // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec	    
+	    if( prcs != null ) {		
+		prcs.destroy();
+	    }
+	}
+	
+	return this.exitValue;
+    }
+
+
+//******************** Inner class interface definitions ********************//
 // Static inner classes can be instantiated without having to instantiate an object of the outer class first
 
@@ -279,4 +439,13 @@
 }
 
+// When using the default stream processing to read from a process' stdout or stderr stream,
+// you can create a LineByLineHandler for the process' err and out streams
+// to do something on a line by line basis, such as sending the line to a log
+public static interface LineByLineHandler {
+    public void gotLine(String line);
+    public void gotException(Exception e); // for when an exception occurs instead of getting a line
+}
+
+//**************** StreamGobbler Inner class definitions (stream gobblers copied from GLI) **********//
 
 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
@@ -289,4 +458,5 @@
     private boolean split_newlines = false;
     private CustomProcessHandler customHandler = null;
+    private LineByLineHandler lineByLineHandler = null;
 
     public InputStreamGobbler(InputStream is)
@@ -306,4 +476,8 @@
 	this.is = is;
 	this.customHandler = customHandler;
+    }
+
+    public void setLineByLineHandler(LineByLineHandler lblHandler) {
+	this.lineByLineHandler = lblHandler;
     }
 
@@ -318,5 +492,6 @@
 
 		if(this.isInterrupted()) { // should we not instead check if SafeProcess thread was interrupted?
-		    System.err.println("Got interrupted when reading lines from process err/out stream.");
+		    //logger.info("Got interrupted when reading lines from process err/out stream.");
+		    //System.err.println("InputStreamGobbler.runDefault() Got interrupted when reading lines from process err/out stream.");
 		    break; // will go to finally block
 		}
@@ -327,10 +502,17 @@
 		    outputstr.append(Utility.NEWLINE); // "\n" is system dependent (Win must be "\r\n")
 		}
-	    }
-	} catch (IOException ioe) {	    
-	    //logger.error("Exception when reading from a process' stdout/stderr stream: ", ioe);
-	    System.err.println("Exception when reading from a process' stdout/stderr stream: ");
-	    ioe.printStackTrace();  
-	    
+
+		if(lineByLineHandler != null) { // let handler deal with newlines
+		    lineByLineHandler.gotLine(line);
+		}
+	    }
+	} catch (IOException ioe) {
+	    if(lineByLineHandler != null) {
+		lineByLineHandler.gotException(ioe);
+	    } else {
+		//logger.error("Exception when reading from a process' stdout/stderr stream: ", ioe);
+		System.err.println("Exception when reading from a process' stdout/stderr stream: ");
+		ioe.printStackTrace();  
+	    }
 	} finally {
 	    SafeProcess.closeResource(br);
@@ -433,5 +615,5 @@
 
 
-//**************** Copied from GLI's Utility.java ******************
+//**************** Useful static methods. Copied from GLI's Utility.java ******************
     // For safely closing streams/handles/resources. 
     // For examples of use look in the Input- and OutputStreamGobbler classes.
Index: /main/trunk/greenstone3/src/java/org/greenstone/util/SafeProcess.java
===================================================================
--- /main/trunk/greenstone3/src/java/org/greenstone/util/SafeProcess.java	(revision 31619)
+++ /main/trunk/greenstone3/src/java/org/greenstone/util/SafeProcess.java	(revision 31620)
@@ -100,6 +100,6 @@
 	    
 	    // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
-	    ///System.err.println("SafeProcess running: " + Arrays.toString(command_args));
 	    logger.info("SafeProcess running: " + Arrays.toString(command_args));
+	    //System.err.println("SafeProcess running: " + Arrays.toString(command_args));
 	    
 	    if(this.envp == null) { 
@@ -141,4 +141,5 @@
 
             ///logger.info("Process exitValue: " + exitValue); 
+	    ///System.err.println("Process exitValue: " + exitValue); 
 
 	    // From the comments of 
@@ -492,4 +493,5 @@
 		if(this.isInterrupted()) { // should we not instead check if SafeProcess thread was interrupted?
 		    logger.info("Got interrupted when reading lines from process err/out stream.");
+		    //System.err.println("InputStreamGobbler.runDefault() Got interrupted when reading lines from process err/out stream.");
 		    break; // will go to finally block
 		}
