blob: 545e412a4fedf5b1b3234aff55a026993e0ecf91 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.uima.pear.util;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
/**
* The <code>ProcessUtil</code> class encapsulates utilities that help in dealing with
* <code>Process</code> objects.
*
*/
public class ProcessUtil {
/**
* The <code>StdStreamListener</code> interface defines 2 methods that should be implemented by
* a standard stream listener to get console messages ('stderr' and 'stdout' streams) printed by
* selected process.
*
*/
public static interface StdStreamListener {
public void errMsgPrinted(String errMsg);
public void outMsgPrinted(String outMsg);
} // end of StdStreamListener
/**
* The <code>ProcessUtil.Runner</code> class allows collecting, printing and distributing
* console output (stdout and stderr) of the specified <code>Process</code> without blocking the
* caller. It allows adding standard stream listeners to receive messages printed to the console
* by selected process.
*
*/
public static class Runner implements Runnable {
// Default process name
private static final String DEF_PROC_NAME = "Process_";
// Static attributes
private static int __processNumber = 0;
// Attributes
private Process _process;
private String _pName;
private Thread _errThread;
private Thread _outThread;
private StringWriter _stdOut;
private StringWriter _stdErr;
private int _exitCode = Integer.MAX_VALUE;
private boolean _printToConsole = true;
private ArrayList _listeners = new ArrayList();
/**
* Constructor that takes a given <code>Process</code> object and assigns default process
* name.
*
* @param aProcess
* The given <code>Process</code> object.
*/
public Runner(Process aProcess) {
this(aProcess, null);
}
/**
* Constructor that takes a given <code>Process</code> object and a given process name.
*
* @param aProcess
* The given <code>Process</code> object.
* @param procName
* The given process name.
*/
public Runner(Process aProcess, String procName) {
this(aProcess, procName, true);
}
/**
* Constructor that takes a given <code>Process</code> object, a given process name, and a
* given <code>boolean</code> flag that enables/disables console printing. If the given
* process name is <code>null</code>, the default process name is assigned.
*
* @param aProcess
* The given <code>Process</code> object.
* @param procName
* The given process name.
* @param printToConsole
* <code>boolean</code> flag that enables/disables console printing.
*/
public Runner(Process aProcess, String procName, boolean printToConsole) {
// get next process number
synchronized (getClass()) {
__processNumber++;
}
_process = aProcess;
_pName = (procName != null) ? procName : DEF_PROC_NAME + Integer.toString(__processNumber);
_errThread = new Thread(this, "ERR@Runner");
_outThread = new Thread(this, "OUT@Runner");
_stdOut = new StringWriter();
_stdErr = new StringWriter();
_printToConsole = printToConsole;
start();
}
/**
* Adds a given object, implementing the <code>StdStreamListener</code> interface to the list
* of standard stream listeners. Sends to the new listener previously printed standard error and
* standard output messages.
*
* @param listener
* The given new standard stream listener.
*/
public synchronized void addStreamListener(StdStreamListener listener) {
if (!_listeners.contains(listener)) {
String errMsg = _stdErr.toString();
if (errMsg.length() > 0)
listener.errMsgPrinted(errMsg);
String outMsg = _stdOut.toString();
if (outMsg.length() > 0)
listener.outMsgPrinted(outMsg);
_listeners.add(listener);
}
}
/**
* @return Process exit code after the process finishes, otherwise
* <code>Integer.MAX_VALUE</code>.
*/
public synchronized int getExitCode() {
return _exitCode;
}
/**
* @return Standard error messages collected during the process execution.
*/
public synchronized String getErrOutput() {
return _stdErr.toString();
}
public Process getProcess() {
return _process;
}
/**
* @return Standard output messages collected during the process execution.
*/
public synchronized String getStdOutput() {
return _stdOut.toString();
}
/**
* Removes a given <code>StdStreamListener</code> object from the list of standard stream
* listeners.
*
* @param listener
* The given <code>StdStreamListener</code> object to be removed from the list.
*/
public synchronized void removeListener(StdStreamListener listener) {
_listeners.remove(listener);
}
/**
* Implements the <code>Runnable.run()</code> method, collecting and printing standard output
* or standard error messages during the process execution.
*/
public void run() {
String threadName = Thread.currentThread().getName();
String threadId = threadName.substring(0, 3);
BufferedReader reader = null;
PrintWriter writer = null;
try {
// create appropriate standard stream reader
reader = (threadId.equals("ERR")) ? new BufferedReader(new InputStreamReader(_process
.getErrorStream())) : new BufferedReader(new InputStreamReader(_process
.getInputStream()));
// create appropriate string writer
StringWriter sWriter = (threadId.equals("ERR")) ? _stdErr : _stdOut;
writer = new PrintWriter(sWriter);
// assign appropriate console stream
PrintStream console = (threadId.equals("ERR")) ? System.err : System.out;
String line = null;
// read standard stream
while ((line = reader.readLine()) != null) {
// print to console, if specified
if (_printToConsole)
console.println("[" + _pName + ":" + threadId + "] " + line);
synchronized (this) {
// get current string writer buffer length
int sIndex = sWriter.getBuffer().length();
// print new line to string buffer
writer.println(line);
// get last printed message
String message = sWriter.getBuffer().substring(sIndex);
// distribute message to listeners
Iterator list = _listeners.iterator();
while (message.length() > 0 && list.hasNext()) {
StdStreamListener listener = (StdStreamListener) list.next();
if (threadId.equals("ERR"))
listener.errMsgPrinted(message);
else
listener.outMsgPrinted(message);
}
}
}
int rc = _process.waitFor();
setExitCode(rc);
if (_printToConsole)
System.out.println("[" + _pName + " " + threadName + "]: completed (rc = " + rc + ")");
} catch (Throwable err) {
if (_printToConsole)
System.err.println("[" + _pName + " " + threadName + "]: terminated - " + err.toString());
} finally {
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
}
}
if (writer != null) {
try {
writer.close();
} catch (Exception e) {
}
}
}
synchronized (this) {
this.notifyAll();
}
}
/**
* Sets a given process exit code.
*
* @param exitCode
* The given process exit code.
*/
private synchronized void setExitCode(int exitCode) {
_exitCode = exitCode;
}
/**
* Starts 2 threads for collecting and printing console messages during the process execution.
*/
private void start() {
_errThread.start();
_outThread.start();
}
/**
* Allows the caller to wait for the completion of the process.
*
* @return Process exit code.
*/
public int waitFor() {
return waitFor(0);
}
/**
* Allows the caller to wait for the completion of the process, but no longer than a given
* timeout value.
*
* @param timeout
* The given timeout value (ms).
* @return Process exit code or <code>Integer.MAX_VALUE</code>, if the process has not
* finished yet.
*/
public int waitFor(long timeout) {
synchronized (this) {
if (getExitCode() == Integer.MAX_VALUE) {
try {
this.wait(timeout);
} catch (InterruptedException e) {
}
}
}
return getExitCode();
}
} // end of Runner
}