/*******************************************************************************
* Copyright (c) 2014 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.assertNotNull;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import melnorme.utilbox.collections.Indexable;
import melnorme.utilbox.concurrency.ICancelMonitor;
import melnorme.utilbox.misc.ILogHandler;
import melnorme.utilbox.status.Severity;
import melnorme.utilbox.status.StatusException;
/**
* Extends {@link ExternalProcessHelper} to allow optional listeners to be notified
* of output read from the external process, as well as process termination events.
*/
public class ExternalProcessNotifyingHelper extends ExternalProcessHelper {
public static enum FAwaitListeners { YES, NO ; public boolean isTrue() { return this == YES; } }
public static enum FStartReaders { YES, NO ; public boolean isTrue() { return this == YES; } }
protected final ILogHandler logHandler;
protected final Indexable<IProcessOutputListener> listeners;
protected final FAwaitListeners awaitListeners;
protected final CountDownLatch listenersTerminationLatch = new CountDownLatch(1);
public ExternalProcessNotifyingHelper(Process process, boolean readStdErr,
ICancelMonitor cancelMonitor, Indexable<IProcessOutputListener> listeners, ILogHandler logHandler) {
this(process, readStdErr, FStartReaders.YES, FAwaitListeners.YES, cancelMonitor, listeners, logHandler);
}
protected ExternalProcessNotifyingHelper(Process process, boolean readStdErr,
FStartReaders startReaders, FAwaitListeners awaitListeners,
ICancelMonitor cancelMonitor, Indexable<IProcessOutputListener> listeners, ILogHandler logHandler) {
super(process, readStdErr, false, cancelMonitor);
this.awaitListeners = assertNotNull(awaitListeners);
this.listeners = assertNotNull(listeners);
this.logHandler = assertNotNull(logHandler);
if(startReaders.isTrue()) {
startReaderThreads();
}
}
public static interface IProcessOutputListener {
void notifyStdOutListeners(byte[] buffer, int offset, int readCount);
void notifyStdErrListeners(byte[] buffer, int offset, int readCount);
/** Notifies that the underlying process has terminated, and all reader threads have finished processing. */
void notifyProcessTerminatedAndRead(int exitCode);
}
@Override
protected ReadAllBytesTask init_StdOutReaderTask() {
return new ReadAllBytesTask(process.getInputStream(), cancelMonitor) {
@Override
protected void notifyReadChunk2(byte[] buffer, int offset, int readCount) {
super.notifyReadChunk2(buffer, offset, readCount);
notifyDataRead(buffer, offset, readCount, true);
}
};
}
@Override
protected ReadAllBytesTask init_StdErrReaderTask() {
return new ReadAllBytesTask(process.getErrorStream(), cancelMonitor) {
@Override
protected void notifyReadChunk2(byte[] buffer, int offset, int readCount) {
super.notifyReadChunk2(buffer, offset, readCount);
notifyDataRead(buffer, offset, readCount, false);
}
};
}
protected void notifyDataRead(byte[] buffer, int offset, int readCount, boolean stdOut) {
for (IProcessOutputListener pol : listeners) {
try {
if(stdOut)
pol.notifyStdOutListeners(buffer, offset, readCount);
else {
pol.notifyStdErrListeners(buffer, offset, readCount);
}
} catch (RuntimeException e) {
handleListenerException("Listener internal error at notifyDataRead.", e);
}
}
}
@Override
public void mainReaderThread_Terminated() {
assertTrue(readersAndProcessTerminationLatch.getCount() == 0);
try {
// Notify listeners
mainReaderThread_notifyProcessTerminatedAndRead(process.exitValue());
} finally {
listenersTerminationLatch.countDown();
}
}
protected void mainReaderThread_notifyProcessTerminatedAndRead(int exitCode) {
for (IProcessOutputListener pol : listeners) {
try {
pol.notifyProcessTerminatedAndRead(exitCode);
} catch (RuntimeException e) {
handleListenerException("Listener internal error at notifyProcessTerminatedAndRead", e);
}
}
}
protected void handleListenerException(String message, RuntimeException e) {
logHandler.logStatus(new StatusException(Severity.ERROR, message, e));
}
@Override
protected boolean doAwaitTermination(int cancelPollPeriodMs) throws InterruptedException {
if(awaitListeners.isTrue()) {
return listenersTerminationLatch.await(cancelPollPeriodMs, TimeUnit.MILLISECONDS);
} else {
return super.doAwaitTermination(cancelPollPeriodMs);
}
}
}