blob: ca9db699c94fc1fd4076215771c7e9ced14f5ea0 [file] [log] [blame]
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
/*
* ProcessStreamDrainer.java
*
* Created on October 26, 2006, 9:56 PM
*
*/
package com.sun.enterprise.universal.process;
import java.io.*;
import java.util.*;
/**
* If you don't drain a process' stdout and stderr it will cause a deadlock after a few hundred bytes of output.
* At that point the Process is blocked because its stdout and/or stderr buffer is full and it is waiting for the Java caller
* to drain it. Meanwhile the Java program is blocked waiting on the external process.
* This class makes this common, but messy and tricky, procedure easier.
* It creates 2 threads that drain output on stdout and stderr of the external process.
* <p> Sample Code:
*
* <pre>
* ProcessBuilder pb = new ProcessBuilder("ls", "-R", "c:/as");
* try
* {
* Process p = pb.start();
* ProcessStreamDrainer psd = ProcessStreamDrainer.drain("MyProcess", p);
* // or
* ProcessStreamDrainer psd = ProcessStreamDrainer.redirect("MyProcess", p);
* psd.waitFor(); // this is optional.
* }
* catch (Exception ex)
* {
* ex.printStackTrace();
* }
* </pre>
*
* @author bnevins
*/
public class ProcessStreamDrainer
{
/**
* Create an instance and drain the process' stderr and stdout
* @param process The Process to drain
* @param processName The name will be used to name the drainer threads
*/
public static ProcessStreamDrainer drain(String processName, Process process)
{
ProcessStreamDrainer psd = new ProcessStreamDrainer(processName, process, false, false);
psd.drain();
return psd;
}
/**
* Create an instance and drain the process' stderr and stdout and save it to
* strings.
* @param process The Process to drain
* @param processName The name will be used to name the drainer threads
*/
public static ProcessStreamDrainer save(String processName, Process process)
{
ProcessStreamDrainer psd = new ProcessStreamDrainer(processName, process, false, true);
psd.drain();
return psd;
}
/**
* Create an instance, drain and redirect the process' stderr and stdout to
* System.err and System.out respectively.
* @param process The Process to drain
* @param processName The name will be used to name the drainer threads
*/
public static ProcessStreamDrainer redirect(String processName, Process process)
{
ProcessStreamDrainer psd = new ProcessStreamDrainer(processName, process, true, false);
psd.drain();
return psd;
}
/**
* Create an instance, drain and throw away the process' stderr and stdout output.
* @param process The Process to drain
* @param processName The name will be used to name the drainer threads
*/
public static ProcessStreamDrainer dispose(String processName, Process process)
{
ProcessStreamDrainer psd = new ProcessStreamDrainer(processName, process, false, false);
psd.drain();
return psd;
}
/**
* Wait for the drain threads to die. This is guaranteed to occur after the
* external process dies. Note that this may, of course, block indefinitely.
*/
public final void waitFor() throws InterruptedException
{
errThread.join();
outThread.join();
}
/* Gets the stdout that was collected into a String
* @return an empty string if nothing is available
*/
public final String getOutString() {
return outWorker.getString();
}
/* Gets the stdout that was collected into a String
* @return an empty string if nothing is available
*/
public final String getErrString() {
return errWorker.getString();
}
/* Concatenates the stdout and stderr output and returns it as a String
* @return an empty string if nothing is available
*/
public final String getOutErrString() {
return outWorker.getString() + errWorker.getString();
}
///////////////////////////////////////////////////////////////////////////
private ProcessStreamDrainer(String processName, Process process, boolean redirect, boolean save)
{
if(process == null)
throw new NullPointerException("Internal Error: null Process object");
if(processName == null || processName.length() <= 0)
processName = "UnknownProcessName";
redirectStandardStreams = redirect;
if(redirectStandardStreams)
outWorker = new ProcessStreamDrainerWorker(process.getInputStream(), System.out, save);
else
outWorker = new ProcessStreamDrainerWorker(process.getInputStream(), null, save);
outThread = new Thread(outWorker, processName + "-" + OUT_DRAINER);
outThread.setDaemon(true);
if(redirectStandardStreams)
errWorker = new ProcessStreamDrainerWorker(process.getErrorStream(), System.err, save);
else
errWorker = new ProcessStreamDrainerWorker(process.getErrorStream(), null, save);
errThread = new Thread(errWorker, processName + "-" + ERROR_DRAINER);
errThread.setDaemon(true);
}
/**
* Start the draining.
* We start them here instead of the constructor so that "this" doesn't
* leak out of the constructor.
*/
private void drain()
{
outThread.start();
errThread.start();
}
///////////////////////////////////////////////////////////////////////////
private final ProcessStreamDrainerWorker outWorker;
private final ProcessStreamDrainerWorker errWorker;
private final Thread errThread;
private final Thread outThread;
private final boolean redirectStandardStreams;
private final static String ERROR_DRAINER = "StderrDrainer";
private final static String OUT_DRAINER = "StdoutDrainer";
///////////////////////////////////////////////////////////////////////////
}