/******************************************************************************* * Copyright (c) 2012, 2014 Wind River Systems, Inc. and others. 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: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.te.runtime.processes; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PipedInputStream; import java.lang.reflect.Field; import java.security.AccessController; import java.security.PrivilegedAction; import org.eclipse.tcf.te.runtime.activator.CoreBundleActivator; /** * Monitor a given input streams and reads any incoming text from the streams. * If more than one stream is specified, the lines read from all streams are * combined within the reader to one single output. */ public class ProcessOutputReaderThread extends Thread { // Prefix for any output produced private String prefix; // The input stream instances as passed in private InputStream[] streams; // The reader instances to wrap the input streams private BufferedReader[] reader; // String builder to collect the read lines private StringBuilder lines; private String lastLine; private boolean buffering; // finished reading all the output private boolean finished; private boolean waiting; private Object waiterSemaphore; /** * Constructor. * <p> * Monitor multiple streams in one. * * @param prefix A <code>String</code> prefixing every line of might be produced output, or <code>null</code>. * @param streams The <code>InputStream</code>'s to monitor. Must not be <code>null</code>! */ public ProcessOutputReaderThread(String prefix, InputStream[] streams) { super("ProcessOutputReader-" + (prefix == null ? "" : prefix)); //$NON-NLS-1$ //$NON-NLS-2$ assert streams != null; lastLine = ""; //$NON-NLS-1$ buffering = true; finished = false; waiting = false; waiterSemaphore = new Object(); if (prefix == null) { this.prefix = ""; //$NON-NLS-1$ } else if (!prefix.trim().endsWith(":")) { //$NON-NLS-1$ this.prefix = prefix.trim() + ": "; //$NON-NLS-1$ } else { this.prefix = prefix; } // Set the input streams this.streams = streams; // connect to a stream reader reader = new BufferedReader[streams.length]; for (int i = 0; i < streams.length; i++) { CoreBundleActivator.getTraceHandler().trace(getPrefix() + " Input stream type (index " + i + ": " + streams[i].getClass().getName(), 3, this); //$NON-NLS-1$ //$NON-NLS-2$ reader[i] = new BufferedReader(new InputStreamReader(streams[i])); } lines = new StringBuilder(); } /** * Returns if or if not the process output reader is buffering the output read. * * @return <code>True</code> if the output reader is buffering the output read, <code>false</code> otherwise. */ public final boolean isBuffering() { return buffering; } /** * Toggle if or if not the process output reader is buffering the output read. * <p> * <b>Note:</b> Switching off the buffering will reset the buffer too. Read the * output buffer before switching off the buffering if needed. * * @param buffering <code>True</code> if the output reader shall buffer the output read, <code>false</code> otherwise. */ public final void setBuffering(boolean buffering) { this.buffering = buffering; if (!buffering) lines = new StringBuilder(); } /** * Returns if or if not the process output reader thread has finished. * * @return <code>true</code> if the thread is finished, <code>false</code> otherwise. */ public boolean isFinished() { return finished; } /** * Wait at most timeout milliseconds, or until the process we are reading is finished. * * @param timeout Timeout in milliseconds to wait for (maximum). */ public void waitForFinish(long timeout) { if (!finished) { waiting = true; synchronized (waiterSemaphore) { try { waiterSemaphore.wait(timeout); } catch (InterruptedException e) { // just end the wait } } } return; } /** * Wait until the process we are reading is finished. */ public void waitForFinish() { waitForFinish(0); } /** * Returns the monitored output till the time of the call. * * @return <code>String</code> containing the monitored output. */ public synchronized String getOutput() { return lines.toString(); } /** * Get the last line that was read. * * @return String last line */ public synchronized String getLastLine() { return lastLine; } /** * Process one line of output. May be overridden by subclasses to extend functionality. * @param line last line that was read */ protected synchronized void processLine(String line) { if (line != null) { StringBuffer buffer = new StringBuffer(line.trim()); while (buffer.length() > 0 && (buffer.charAt(buffer.length() - 1) == '\r' || buffer.charAt(buffer.length() - 1) == '\n')) { buffer.deleteCharAt(buffer.length() - 1); } line = buffer.toString(); lastLine = line; if (buffering) { lines.append(line); lines.append('\n'); } CoreBundleActivator.getTraceHandler().trace(getPrefix() + " processLine: " + line, 3, this); //$NON-NLS-1$ } } /** * Returns the trace line prefix. * * @return The trace line prefix or <code>null</code>. */ protected String getPrefix() { return prefix; } /** * Called when the process finished and no more input is available. May be overridden by * subclasses to extend functionality. */ protected void finish() { finished = true; if (waiting) { waiting = false; synchronized (waiterSemaphore) { waiterSemaphore.notifyAll(); } } } /** * Reads the available available input from the given stream. * * @return Total number of bytes read, or -1 if EOF reached. */ protected synchronized int readAvailableInput(BufferedReader reader) { if (reader != null) { int bytesRead = 0; try { while (reader.ready()) { String line = reader.readLine(); if (line != null) { bytesRead = line.length(); processLine(line); } } } catch (IOException e) { bytesRead = -1; } return bytesRead; } return -1; } /* * Workaround for the old Java I/O system not being interruptible while reading data from a stream * where possibly nothing is sent: We want to be able to interrupt the reader-thread if we think * that we are no more interested in the data... Unfortunately, this implementation does not * detect when the Stream is closed. inputStream.available() doesn't throw an exception in this * case... so either we block indefinitely (having to potentially destroy the thread), or we can't * detect when the stream is closed. alas, java.nio would solve both issues much more elegant, but * what can we do, getting in the crap old InputStream object... * * Note: Do not synchronize this method. It may lead to dead locks because of the sleep call! */ protected void readInputUntilInterrupted() { boolean allStreamsEOF = false; while (!allStreamsEOF) { allStreamsEOF = true; int totalBytesRead = 0; for (int i = 0; i < reader.length; i++) { // we do mark reader which have reached EOF already with null. if (reader[i] == null) { continue; } CoreBundleActivator.getTraceHandler().trace(getPrefix() + " calling readAvailableInput", 3, this); //$NON-NLS-1$ int bytesRead = readAvailableInput(reader[i]); CoreBundleActivator.getTraceHandler().trace(getPrefix() + " readAvailableInput returned. bytesRead = " + bytesRead, 3, this); //$NON-NLS-1$ // If readAvailableInput(...) returns 0 and the stream read is a PipedInputStream, // we need to know if the stream got closed by the writer if (bytesRead == 0 && streams[i] instanceof PipedInputStream) { PipedInputStream in = (PipedInputStream)streams[i]; try { final Field f = in.getClass().getDeclaredField("closedByWriter"); //$NON-NLS-1$ AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { f.setAccessible(true); return null; } }); // If the piped input stream is closed from the writer // side, in example because EOF received on writer side, // close the stream from the reader side too. if (f.getBoolean(in)) bytesRead = -1; } catch (Exception e) { /* ignored on purpose */ } } // is EOF for the current stream if (bytesRead == -1) { try { reader[i].close(); } catch (IOException e) { /* ignored on purpose */ } reader[i] = null; } else { // at least this stream is still not EOF allStreamsEOF = false; if (bytesRead >= 0) { totalBytesRead += bytesRead; } } } if (!allStreamsEOF && totalBytesRead == 0) { // nothing read till here, sleep a little bit try { sleep(50); } catch (InterruptedException e) { CoreBundleActivator.getTraceHandler().trace(getPrefix() + " received interrupt request", 3, this); //$NON-NLS-1$ // an interrupt to the sleep breaks the loop. allStreamsEOF = true; } } } } /** * (non-Javadoc) * @see java.lang.Runnable#run() */ @Override public void run() { try { CoreBundleActivator.getTraceHandler().trace(getPrefix() + " begin waiting for input", 3, this); //$NON-NLS-1$ readInputUntilInterrupted(); } finally { // close all readers if not done anyway for (BufferedReader element : reader) { if (element == null) { continue; } // should there be any input left, read it before closing the stream. readAvailableInput(element); // finally, close the stream now. try { element.close(); } catch (IOException e) { /* ignored on purpose */ } } // release all waiting threads. finish(); CoreBundleActivator.getTraceHandler().trace(getPrefix() + " stop waiting for input", 3, this); //$NON-NLS-1$ } } }