/******************************************************************************* * Copyright (c) 2013 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package melnorme.utilbox.process; import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail; import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.TimeoutException; import melnorme.utilbox.concurrency.ICancelMonitor; import melnorme.utilbox.concurrency.OperationCancellation; import melnorme.utilbox.core.CommonException; import melnorme.utilbox.core.fntypes.Result; import melnorme.utilbox.misc.ByteArrayOutputStreamExt; import melnorme.utilbox.misc.IByteSequence; import melnorme.utilbox.process.ExternalProcessHelper.ReadAllBytesTask; /** * Helper for running external processes. * Reads all stdout and stderr output into a byte array (using worker threads) * * @see ExternalProcessHandler */ public class ExternalProcessHelper extends ExternalProcessHandler<ReadAllBytesTask, ReadAllBytesTask> { public ExternalProcessHelper(ProcessBuilder pb) throws IOException { this(pb.start(), pb.redirectErrorStream() == false, true); } public ExternalProcessHelper(Process process, boolean readStdErr, boolean startReaders) { this(process, readStdErr, startReaders, null); } public ExternalProcessHelper(Process process, boolean readStdErr, boolean startReaders, ICancelMonitor cancelMonitor) { super(process, readStdErr, startReaders, cancelMonitor); } @Override protected ReadAllBytesTask init_StdOutReaderTask() { return new ReadAllBytesTask(process.getInputStream(), cancelMonitor); } @Override protected ReadAllBytesTask init_StdErrReaderTask() { return new ReadAllBytesTask(process.getErrorStream(), cancelMonitor); } @Override protected void completeStderrResult(ReadAllBytesTask stderrReaderTask) { stderrReaderTask.completeWithResult(new Result<>(null)); } public static class ReadAllBytesTask extends ReaderTask<ByteArrayOutputStreamExt> { protected final ByteArrayOutputStreamExt byteArray = new ByteArrayOutputStreamExt(32); public ReadAllBytesTask(InputStream is, ICancelMonitor cancelMonitor) { super(is, cancelMonitor); } @Override protected void notifyReadChunk2(byte[] buffer, int offset, int readCount) { byteArray.write(buffer, 0, readCount); } @Override protected ByteArrayOutputStreamExt doGetReturnValue() { return byteArray; } } /* ----------------- result helpers ----------------- */ protected ByteArrayOutputStreamExt getStdOutBytes() throws IOException { return stdoutReaderTask.getResult_forSuccessfulyCompleted().get(); } protected ByteArrayOutputStreamExt getStdErrBytes() throws IOException { return stderrReaderTask.getResult_forSuccessfulyCompleted().get(); } public static class ExternalProcessResult { public final int exitValue; public final ByteArrayOutputStreamExt stdout; public final ByteArrayOutputStreamExt stderr; public ExternalProcessResult(int exitValue, ByteArrayOutputStreamExt stdout, ByteArrayOutputStreamExt stderr) { this.exitValue = exitValue; this.stdout = assertNotNull(stdout); this.stderr = stderr != null ? stderr : new ByteArrayOutputStreamExt(); } public IByteSequence getStdOutBytes() { return stdout; } public IByteSequence getStdErrBytes() { return stderr; } } /* ----------------- Await termination ----------------- */ public ExternalProcessResult awaitTerminationAndResult(boolean destroyOnError) throws InterruptedException, OperationCancellation, IOException { try { return awaitTerminationAndResult(NO_TIMEOUT, destroyOnError); } catch (TimeoutException e) { throw assertFail(); // Cannot happen } } /** * Awaits for successful process termination, as well as successful termination of reader threads, * throws an exception otherwise (and destroys the process). * @param timeoutMs the timeout in milliseconds to wait for (or -1 for no TIMEOUT). * @param destroyOnError if the process should be destroyed should any exception occur * @return the process output result in an {@link ExternalProcessResult}. * @throws InterruptedException if thread interrupted while waiting. * @throws TimeoutException if timeout reached. * @throws OperationCancellation if reader thread cancellation has occured. * @throws IOException if an IO error occured in the reader threads. */ public ExternalProcessResult awaitTerminationAndResult(int timeoutMs, boolean destroyOnError) throws InterruptedException, TimeoutException, OperationCancellation, IOException { awaitTermination(timeoutMs, destroyOnError); return new ExternalProcessResult(process.exitValue(), getStdOutBytes(), getStdErrBytes()); } public ExternalProcessResult awaitTerminationAndResult_ce(boolean destroyOnError) throws CommonException, OperationCancellation { return awaitTerminationAndResult_ce(NO_TIMEOUT, destroyOnError); } public ExternalProcessResult awaitTerminationAndResult_ce(int timeout, boolean destroyOnError) throws CommonException, OperationCancellation { try { return awaitTerminationAndResult(timeout, destroyOnError); } catch (IOException e) { throw new CommonException(ProcessHelperMessages.ExternalProcess_ErrorStreamReaderIOException, e); } catch (TimeoutException te) { // at this point a TimeoutException can be one of two things, an actual timeout, // or an explicit cancellation. In both case we throw OperationCancellation throw new OperationCancellation(); } catch (InterruptedException e) { // InterrruptedException also threated as OperationCancellation throw new OperationCancellation(); } } }