/** Copyright (C) SAS Institute All rights reserved.
** General Public License: http://www.opensource.org/licenses/gpl-license.php
**/
package org.safs.tools.consoles;
import java.io.*;
import java.lang.Process;
import java.util.Vector;
import org.safs.tools.CaseInsensitiveFile;
import org.safs.tools.GenericProcessMonitor;
/**
* Attempts to capture out and err streams from a process for use by other processes.
* The process data is available via a Vector retrieved from getData().
* <p>
* By default, this subclass of {@link GenericProcessConsole} disables the out and err
* streams from appearing in the debug/console output. Users can change this behavior
* by overriding the debug() method and also by turning on
* the output of either or both of the streams via {@link #setShowOutStream(boolean)} and
* {@link #setShowErrStream(boolean)}.
* <p>
* Normal usage might be something like below:
* <pre>
* Process process = runtime.exec(procstr);
* GenericProcessCapture console = new GenericProcessCapture(process);
* Thread athread = new Thread(console);
* athread.start();
* //we can wait until process is finished
* try{ athread.join();}catch(InterruptedException x){;}
* console.shutdown();//precaution
* Vector data = console.getData();
* </pre>
* Alternatively, we can autostart the capture thread. We don't have to wait for
* it to end if we don't want to. The autostarted thread can be acquired, though:
* <pre>
* Process process = runtime.exec(procstr);
* GenericProcessCapture console = new GenericProcessCapture(process, null, true);
* //we can wait until process is finished if we want
* try{ console.thread.join();}catch(InterruptedException x){;}
* Vector data = console.getData();
* console.shutdown();//precautionary force shutdown
* </pre>
* Data for the Error and Output streams is captured into the single Vector storage.
* The Error stream data is prefixed with the ERR_PREFIX, while the Output data is
* prefixed with the OUT_PREFIX.
* <p>
* This class contains no extended SAFS dependencies and can be readily packaged and distributed
* for non-SAFS installations.
* @author canagl
*/
public class GenericProcessCapture extends GenericProcessConsole{
protected java.util.Vector data = new java.util.Vector();
/**
* If non-null, we will check to see if this CMD(Unix), IMAGE(Win), or PID is running.
* If the process is still running, we will not attempt to end our reading of the IO
* streams. */
public String monitor = null;
/**
* Returns true if the process we are capturing has exited.
* We may still be capturing additional data if a secondary process is being
* monitored, however.
*/
public boolean exited = false;
/**
* Returns the exitValue returned from the exited process.
* The value is only valid if exited = true;
*/
protected int exitValue = -99;
/**
* The (running) thread used to autostart the process capture, if applicable.
* Will normally be null if this thread was not autostarted.
*/
public Thread thread = null;
/**
* Default Constructor for GenericProcessCapture.
* This sets up a default console that does not monitor any secondary processes
* and does NOT autostart its own capture thread. This instance also will not route
* either the System.out or System.err to the debug() sink.
* @param process to capture IO in, out, and err streams.
* @see #thread
* @see #setShowOutStream(boolean)
* @see #setShowErrStream(boolean)
*/
public GenericProcessCapture(Process process) {
this(process, null, false);
}
/**
* Get the process exitValue.
* Use isExited() first to avoid the IllegalStateException, if desired.
* @return exitValue or IllegalStateException if process has not exited.
* @throws IllegalStateException if process is still running.
*/
public int getExitValue()throws IllegalStateException {
if(!exited) throw new IllegalStateException("Process still running...");
return exitValue;
}
/**
* Alternative constructor allowing the IO thread to remain open as long as
* a secondary process/pid remains running (if specified). This instance also will not route
* either the System.out or System.err to the debug() sink.
* @param process -- to capture IO in, out, and err streams.
* @param monitor -- optional secondary process name or pid to monitor for continued
* IO output.
* @param autostart -- true if we should automatically start the separate capture thread.
* @see #thread
* @see #setShowOutStream(boolean)
* @see #setShowErrStream(boolean)
*/
public GenericProcessCapture(Process process, String monitor, boolean autostart) {
this(process, monitor, autostart, false);
}
/**
* Alternative constructor allowing the IO thread to remain open as long as
* a secondary process/pid remains running (if specified). This version also allows
* the caller to change the default behavior for routing the System.out and System.err
* streams to the debug() sink.
* @param process -- to capture IO in, out, and err streams.
* @param monitor -- optional secondary process name or pid to monitor for continued
* IO output.
* @param autostart -- true if we should automatically start the separate capture thread.
* @param debug2console set true to have the out and err streams copied to the debug() sink.
*/
public GenericProcessCapture(Process process, String monitor, boolean autostart, boolean debug2console) {
super(process, debug2console);
if (monitor != null && monitor.length()>0) this.monitor = monitor;
try{
if(showOutStream)debug("GenericProcessCapture "+ process +" initializing...");
if(autostart){
thread = new Thread(this);
thread.start();
}
}
catch(Exception x){
debug("GenericProcessCapture initialization error for "+ process+", "+ x.getMessage());
}
}
/**
* Return a snapshot(copy) of the String lines of data from the streams.
* The output stream data is prefixed with OUT_PREFIX.
* The error stream data is prefixed with ERR_PREFIX.
* @return Vector storing a snapshot of the data read from Process out and err streams.
*/
public java.util.Vector getData(){
return (Vector) data.clone();
}
/**
* @return the count of lines in combined out and err streams, or 0.
*/
public int getDataLineCount(){
return data.size();
}
public void run(){
boolean outdata = true;
boolean errdata = true;
String linedata = null;
if(showOutStream) debug("GenericProcessCapture activated for "+ process);
try{
do{
outdata = out.ready();
if (outdata) {
linedata = "";
linedata = out.readLine();
if(showOutStream) debug(linedata);
if (linedata.length()>0){
data.add(OUT_PREFIX + linedata);
if (linedata.toLowerCase().startsWith("unhandled exception")){
exceptions.add(OUT_PREFIX + linedata);
}
}
}
errdata = err.ready();
if (errdata) {
linedata = "";
linedata = err.readLine();
if(showErrStream) debug(linedata);
if (linedata.length()>0){
data.add(ERR_PREFIX +linedata);
if (linedata.toLowerCase().startsWith("unhandled exception")){
exceptions.add(ERR_PREFIX + linedata);
}
}
}
//problem getting all stream output on an exited process!
if ((!outdata)&&(!errdata)){
try{
exitValue = process.exitValue();
exited = true;
if(showOutStream) debug("GenericProcessCapture "+ process +" exited with code: "+exitValue);
shutdown = (monitor == null)||(monitor.length()==0);
if(!shutdown){ // we must be monitoring a process
try{
shutdown = GenericProcessMonitor.isProcessRunning(monitor);
}
catch(Exception x){
debug("GenericProcessCapture process monitoring for '"+ monitor +"': "+
x.getClass().getSimpleName()+": "+ x.getMessage());
}
}
}catch(IllegalThreadStateException x){exited = false;}
if(!shutdown) Thread.sleep(100);
}
}while(! shutdown);
if(showOutStream) debug("GenericProcessCapture shutdown for "+ process);
}
catch(Exception x){
debug("GenericProcessCapture execution error for "+process +", "+ x.getMessage());
}
}
}