/*******************************************************************************
* Copyright (c) 2009, eXXcellent solutions gmbh
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the copyright holder
* listed above, as the Initial Contributor under such license. The text of
* such license is available at www.eclipse.org.
*
* Contributors:
* Achim Demelt - initial API and implementation
*******************************************************************************/
package org.eclipse.buckminster.core.commands;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.buckminster.cmdline.Option;
import org.eclipse.buckminster.cmdline.OptionDescriptor;
import org.eclipse.buckminster.cmdline.OptionValueType;
import org.eclipse.buckminster.cmdline.UsageException;
import org.eclipse.buckminster.core.CorePlugin;
import org.eclipse.buckminster.core.Messages;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.buckminster.runtime.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IFlushableStreamMonitor;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.debug.core.model.IStreamsProxy;
import org.eclipse.osgi.util.NLS;
public class Launch extends WorkspaceCommand {
private final class StreamListener implements IStreamListener {
private PrintStream stream;
public StreamListener(IStreamMonitor monitor, String outputFile, boolean stdout) {
Launch.this.listeners.add(this);
// open stream or use stdout/stderr
if ("-".equals(outputFile)) //$NON-NLS-1$
this.stream = stdout ? Logger.getOutStream() : Logger.getErrStream();
else
try {
this.stream = new PrintStream(outputFile);
} catch (FileNotFoundException e) {
CorePlugin.getLogger().error(e, Messages.Launch_Cannot_open_stream, outputFile);
this.stream = stdout ? Logger.getOutStream() : Logger.getErrStream();
}
// dump initial contents
stream.print(monitor.getContents());
// and now register to be notified of subsequent events
monitor.addListener(this);
}
public void close() {
if (stream != Logger.getOutStream() && stream != Logger.getErrStream())
stream.close();
}
@Override
public void streamAppended(String text, IStreamMonitor monitor) {
stream.print(text);
if (monitor instanceof IFlushableStreamMonitor) {
IFlushableStreamMonitor flushableMonitor = (IFlushableStreamMonitor) monitor;
flushableMonitor.flushContents();
}
}
}
private static final OptionDescriptor LAUNCH_DESCRIPTOR = new OptionDescriptor('l', "launch", //$NON-NLS-1$
OptionValueType.REQUIRED);
private static final OptionDescriptor STDOUT_DESCRIPTOR = new OptionDescriptor(null, "stdout", //$NON-NLS-1$
OptionValueType.OPTIONAL);
private static final OptionDescriptor STDERR_DESCRIPTOR = new OptionDescriptor(null, "stderr", //$NON-NLS-1$
OptionValueType.OPTIONAL);
private String launchName;
private String stdOutFile;
private String stdErrFile;
private IStreamMonitor[] stdOut;
private IStreamMonitor[] stdErr;
private List<StreamListener> listeners = new ArrayList<StreamListener>();
private ILaunch launch;
private boolean background = false;
public String getLaunchName() {
return launchName;
}
/**
* Returns a copy of the stderr streams of the launched processes.
*
* @return A copy of the stderr streams of the launched processes.
*/
public IStreamMonitor[] getRawStdErr() {
return stdErr.clone();
}
/**
* Returns a copy of the stdout streams of the launched processes.
*
* @return A copy of the stdout streams of the launched processes.
*/
public IStreamMonitor[] getRawStdOut() {
return stdOut.clone();
}
/**
* Returns the content of the standard error streams of all processes launch
* by the configuration. Note that this method may lead to memory shortage
* if the launch produces a large amount of output.
*
* @return The contents of all standard error streams. An empty string if no
* processes were launched or no content was produced.
*/
public String getStdErr() {
StringBuffer content = new StringBuffer();
for (IStreamMonitor err : stdErr) {
if (err != null)
content.append(err.getContents());
}
return content.toString();
}
/**
* Returns the content of the standard output streams of all processes
* launch by the configuration. Note that this method may lead to memory
* shortage if the launch produces a large amount of output.
*
* @return The contents of all standard output streams. An empty string if
* no processes were launched or no content was produced.
*/
public String getStdOut() {
StringBuffer content = new StringBuffer();
for (IStreamMonitor out : stdOut) {
if (out != null)
content.append(out.getContents());
}
return content.toString();
}
public void setBackground(boolean flag) {
background = flag;
}
/**
* Returns the launch mode that is used for launching. Defaults to
* {@link ILaunchManager#RUN_MODE}. Subclasses may override this to launch
* in other modes.
*
* @return The launch mode to use. Never null.
*/
protected String getLaunchMode() {
return ILaunchManager.RUN_MODE;
}
@Override
protected void getOptionDescriptors(List<OptionDescriptor> appendHere) throws Exception {
appendHere.add(LAUNCH_DESCRIPTOR);
appendHere.add(STDOUT_DESCRIPTOR);
appendHere.add(STDERR_DESCRIPTOR);
super.getOptionDescriptors(appendHere);
}
@Override
protected void handleOption(Option option) throws Exception {
if (option.is(LAUNCH_DESCRIPTOR))
launchName = option.getValue();
else if (option.is(STDOUT_DESCRIPTOR))
stdOutFile = option.getValue() == null ? "-" //$NON-NLS-1$
: option.getValue();
else if (option.is(STDERR_DESCRIPTOR))
stdErrFile = option.getValue() == null ? "-" //$NON-NLS-1$
: option.getValue();
else
super.handleOption(option);
}
@Override
protected int internalRun(IProgressMonitor monitor) throws Exception {
if (launchName == null)
throw new UsageException(Messages.Launch_No_launch_config);
IResource launchFile = ResourcesPlugin.getWorkspace().getRoot().findMember(launchName);
if (launchFile == null || launchFile.getType() != IResource.FILE || !launchFile.exists())
throw BuckminsterException.fromMessage(NLS.bind(Messages.Launch_Cannot_open_launch_config, launchName));
ILaunchConfiguration launchConfiguration = DebugPlugin.getDefault().getLaunchManager().getLaunchConfiguration((IFile) launchFile);
launch = launchConfiguration.launch(getLaunchMode(), monitor);
// capture stdout/stderr streams
IProcess[] processes = launch.getProcesses();
stdOut = new IStreamMonitor[processes.length];
stdErr = new IStreamMonitor[processes.length];
for (int i = 0; i < processes.length; i++) {
IProcess p = processes[i];
IStreamsProxy streamsProxy = p.getStreamsProxy();
if (streamsProxy != null) {
stdOut[i] = streamsProxy.getOutputStreamMonitor();
if (stdOutFile != null)
new StreamListener(stdOut[i], stdOutFile, true);
stdErr[i] = streamsProxy.getErrorStreamMonitor();
if (stdErrFile != null)
new StreamListener(stdErr[i], stdErrFile, false);
}
}
if (background) {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
launch.terminate();
} catch (DebugException e) {
e.printStackTrace();
}
for (StreamListener listener : listeners)
listener.close();
}
});
return 0;
}
try {
// TODO: wait for a configurable, finite time and terminate
// process
// if overdue
while (!launch.isTerminated())
Thread.sleep(500);
// check for process exit status
int result = 0;
for (IProcess p : processes)
if (p.getExitValue() != 0) {
CorePlugin.getLogger().warning(
NLS.bind(Messages.Launch_Terminated_with_exit_status, p.getLabel(), Integer.valueOf(p.getExitValue())));
result = p.getExitValue();
}
return result;
} finally {
for (StreamListener listener : listeners)
listener.close();
}
}
}