/**
* Copyright (C) 2014 Daniel Leong
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.eclim.plugin.core.command.project;
import java.util.HashMap;
import java.util.Iterator;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;
/**
* Manages Launches started by ProjectRunCommand
*/
public class EclimLaunchManager implements Runnable
{
private static HashMap<String, LaunchSet> sLaunches =
new HashMap<String, LaunchSet>();
private static Thread thread;
@Override
public void run()
{
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// we're probably done
thread = null;
break;
}
cleanLaunches();
}
}
private static synchronized void cleanLaunches()
{
Iterator<LaunchSet> iter = sLaunches.values().iterator();
while (iter.hasNext()) {
final LaunchSet set = iter.next();
if (set == null) {
continue;
}
if (set.launch.isTerminated()) {
iter.remove();
set.output.sendTerminated();
}
}
}
public static synchronized boolean isRunning(final String launchId)
{
return sLaunches.containsKey(launchId);
}
public static synchronized boolean terminate(final String launchId)
throws DebugException
{
LaunchSet set = sLaunches.remove(launchId);
if (set == null) {
return false;
}
terminate(set);
return true;
}
private static void terminate(final LaunchSet set)
throws DebugException
{
if (set.launch.isTerminated()) {
// already terminated... consider success?
return;
}
set.launch.terminate();
set.output.sendTerminated();
}
public static synchronized void terminateAll()
{
Iterator<LaunchSet> iter = sLaunches.values().iterator();
while (iter.hasNext()) {
LaunchSet set = iter.next();
try {
terminate(set);
} catch (DebugException e) {
// just keep moving
}
iter.remove();
}
}
/**
* @throws IllegalArgumentException if the OutputHandler
* provided doesn't support async output and we're trying
* to perform it. The original exception can be retreived
* from #getCause()
*/
public static synchronized void manage(
final ILaunch launch,
final OutputHandler output)
throws IllegalArgumentException
{
final IProcess[] procs = launch.getProcesses();
if (procs == null || procs.length == 0) {
// we're done here
return;
}
// attach NOW so we don't miss anything
IStreamListener errListener = new IStreamListener()
{
@Override
public void streamAppended(String text, IStreamMonitor monitor)
{
output.sendErr(text);
}
};
IStreamListener outListener = new IStreamListener()
{
@Override
public void streamAppended(String text, IStreamMonitor monitor)
{
output.sendOut(text);
}
};
for (final IProcess proc : procs) {
IStreamMonitor stdout = proc.getStreamsProxy().getOutputStreamMonitor();
IStreamMonitor stderr = proc.getStreamsProxy().getErrorStreamMonitor();
// dump buffered content, if any
final String pendingOut = stdout.getContents();
final String pendingErr = stderr.getContents();
if (pendingOut.length() > 0){
output.sendOut(pendingOut);
}
if (pendingErr.length() > 0){
output.sendErr(pendingErr);
}
// attach listeners
stdout.addListener(outListener);
stderr.addListener(errListener);
}
final String id = allocateId(launch);
// procs remaining; prepare the output
try {
output.prepare(id);
} catch (final Exception e) {
remove(id);
try {
launch.terminate();
} catch (final DebugException e2) {
// we're quitting anyway
}
// re-raise
throw new IllegalArgumentException(
"OutputHandler does not support async output", e);
}
sLaunches.put(id, new LaunchSet(launch, output));
if (thread == null) {
thread = new Thread(new EclimLaunchManager());
thread.setDaemon(true);
thread.start();
}
}
private static synchronized String allocateId(ILaunch launch)
{
final String name = launch.getLaunchConfiguration().getName();
if (!sLaunches.containsKey(name)) {
// reserve
sLaunches.put(name, null);
return name;
}
final int token = 1;
String id;
do {
id = String.format("%s:%d", name, token);
} while (sLaunches.containsKey(id));
sLaunches.put(id, null);
return id;
}
private static synchronized void remove(String id)
{
sLaunches.remove(id);
}
public interface OutputHandler
{
/**
* NB: If your OutputHandler's prepare might take
* a non-trivial amount of time, you MUST
* queue up any outputs that come along before
* you started. Future work could abstract that
* out as necessary
*/
public void prepare(final String launchId)
throws Exception;
public void sendErr(String line);
public void sendOut(String line);
public void sendTerminated();
}
private static class LaunchSet
{
final ILaunch launch;
final OutputHandler output;
LaunchSet(ILaunch launch, OutputHandler output)
{
this.launch = launch;
this.output = output;
}
}
}