/******************************************************************************* * Copyright (c) 2009, 2010 QNX Software Systems 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: * QNX Software Systems - initial API and implementation * Freescale Semiconductor *******************************************************************************/ package org.eclipse.cdt.launch.internal; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.cdt.launch.internal.MultiLaunchConfigurationDelegate.LaunchElement.EPostLaunchAction; import org.eclipse.cdt.launch.internal.ui.LaunchMessages; import org.eclipse.cdt.launch.internal.ui.LaunchUIPlugin; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.SubProgressMonitor; 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.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.ILaunchesListener2; import org.eclipse.debug.core.Launch; import org.eclipse.debug.core.model.ILaunchConfigurationDelegate2; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.core.model.LaunchConfigurationDelegate; import org.eclipse.debug.internal.core.DebugCoreMessages; import org.eclipse.debug.internal.ui.DebugUIPlugin; import org.eclipse.debug.ui.IDebugUIConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.osgi.util.NLS; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.activities.WorkbenchActivityHelper; /** * Group Launch delegate. Launches each configuration in the user selected mode */ public class MultiLaunchConfigurationDelegate extends LaunchConfigurationDelegate implements ILaunchConfigurationDelegate2 { public static final String DEFAULT_MODE = "default"; //$NON-NLS-1$ private static final String NAME_PROP = "name"; //$NON-NLS-1$ private static final String ENABLED_PROP = "enabled"; //$NON-NLS-1$ private static final String MODE_PROP = "mode"; //$NON-NLS-1$ private static final String ACTION_PROP = "action"; //$NON-NLS-1$ private static final String ACTION_PARAM_PROP = "actionParam"; //$NON-NLS-1$ public static String MULTI_LAUNCH_CONSTANTS_PREFIX = "org.eclipse.cdt.launch.launchGroup"; //$NON-NLS-1$ public static class LaunchElement { public static enum EPostLaunchAction { NONE, WAIT_FOR_TERMINATION, DELAY }; /** * Allows us decouple the enum identifier in the code from its textual representation in the GUI */ public static String actionEnumToStr(EPostLaunchAction action) { switch (action) { case NONE: return LaunchMessages.MultiLaunchConfigurationDelegate_Action_None; case WAIT_FOR_TERMINATION: return LaunchMessages.MultiLaunchConfigurationDelegate_Action_WaitUntilTerminated; case DELAY: return LaunchMessages.MultiLaunchConfigurationDelegate_Action_Delay; default: assert false : "new post launch action type is missing logic"; //$NON-NLS-1$ return LaunchMessages.MultiLaunchConfigurationDelegate_Action_None; } } /** * Allows us decouple the enum identifier in the code from its textual representation in the GUI */ public static EPostLaunchAction strToActionEnum(String str) { if (str.equals(LaunchMessages.MultiLaunchConfigurationDelegate_Action_None)) { return EPostLaunchAction.NONE; } else if (str.equals(LaunchMessages.MultiLaunchConfigurationDelegate_Action_WaitUntilTerminated)) { return EPostLaunchAction.WAIT_FOR_TERMINATION; } else if (str.equals(LaunchMessages.MultiLaunchConfigurationDelegate_Action_Delay)) { return EPostLaunchAction.DELAY; } else { assert false : "new post launch action type is missing logic"; //$NON-NLS-1$ return EPostLaunchAction.NONE; } } public int index; public boolean enabled; public String mode; public EPostLaunchAction action; public Object actionParam; public String name; public ILaunchConfiguration data; } public MultiLaunchConfigurationDelegate() { // nothing } /** * A specialization of launch to track sublaunches lifecycle, also terminates itself when all sublaunches are terminated * */ private class MultiLaunch extends Launch implements ILaunchesListener2{ /** * Whether this process has been terminated */ private boolean fTerminated; /** * A map of all our sub-launches and the current processes that belong * to each one. */ private Map<ILaunch, IProcess[]> subLaunches = new HashMap<ILaunch, IProcess[]>(); public MultiLaunch(ILaunchConfiguration launchConfiguration, String mode) { super(launchConfiguration, mode, null); getLaunchManager().addLaunchListener((ILaunchesListener2)this); } /** * Associate the launch * @param subLaunch */ public void addSubLaunch(ILaunch subLaunch) { subLaunches.put(subLaunch, new IProcess[]{}); } private ILaunch[] getSubLaunches() { return subLaunches.keySet().toArray(new ILaunch[subLaunches.keySet().size()]); } private boolean isChild(ILaunch launch) { for (ILaunch subLaunch : getSubLaunches()) { if (subLaunch == launch) { return true; } } return false; } /** * Override default behavior by querying all sub-launches to see if they are terminated * @see org.eclipse.debug.core.Launch#isTerminated() */ @Override public boolean isTerminated() { if (fTerminated) return true; if (subLaunches.size() == 0) return false; for (ILaunch launch : getSubLaunches()) { if (!launch.isTerminated()) { return false; } } return true; } /** * Override default behavior by querying all sub-launches if they can be terminated * @see org.eclipse.debug.core.Launch#canTerminate() */ @Override public boolean canTerminate() { if (subLaunches.size() == 0) return false; for (ILaunch launch : getSubLaunches()) { if (launch.canTerminate()) { return true; } } return false; } /** * Override default behavior by terminating all sub-launches * @see org.eclipse.debug.core.Launch#terminate() */ @Override public void terminate() throws DebugException { MultiStatus status= new MultiStatus(DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED, DebugCoreMessages.Launch_terminate_failed, null); for (ILaunch launch : getSubLaunches()) { if (launch.canTerminate()) { try { launch.terminate(); } catch (DebugException e) { status.merge(e.getStatus()); } } } if (status.isOK()) { return; } IStatus[] children= status.getChildren(); if (children.length == 1) { throw new DebugException(children[0]); } throw new DebugException(status); } /** * Handle terminated sub-launch * @param launch */ private void launchTerminated(ILaunch launch) { if (this == launch) return; // Remove sub launch, keeping the processes of the terminated launch to // show the association and to keep the console content accessible if (subLaunches.remove(launch) != null) { // terminate ourselves if this is the last sub launch if (subLaunches.size() == 0) { fTerminated = true; fireTerminate(); } } } /* (non-Javadoc) * @see org.eclipse.debug.core.Launch#launchChanged(org.eclipse.debug.core.ILaunch) */ public void launchChanged(ILaunch launch) { if (this == launch) return; // add/remove processes if (isChild(launch)) { // Remove old processes IProcess[] oldProcesses = subLaunches.get(launch); IProcess[] newProcesses = launch.getProcesses(); // avoid notifications when processes have not changed. if (!Arrays.equals(oldProcesses, newProcesses)) { for (IProcess oldProcess : oldProcesses) { removeProcess(oldProcess); } // Add new processes for (IProcess newProcess : newProcesses) { addProcess(newProcess); } // Replace the processes of the changed launch subLaunches.put(launch, newProcesses); } } } /* (non-Javadoc) * @see org.eclipse.debug.core.Launch#launchRemoved(org.eclipse.debug.core.ILaunch) */ @Override public void launchRemoved(ILaunch launch) { if (this == launch) { super.launchRemoved(launch); // Remove the processes we got from the sub-launches from this launch IProcess[] processes = getProcesses(); for (IProcess process : processes) { removeProcess(process); } getLaunchManager().removeLaunchListener((ILaunchesListener2)this); } } /* (non-Javadoc) * @see org.eclipse.debug.core.ILaunchesListener2#launchesTerminated(org.eclipse.debug.core.ILaunch[]) */ public void launchesTerminated(ILaunch[] launches) { for (ILaunch launch : launches) { launchTerminated(launch); } } /* (non-Javadoc) * @see org.eclipse.debug.core.ILaunchesListener#launchesAdded(org.eclipse.debug.core.ILaunch[]) */ public void launchesAdded(ILaunch[] launches) { for (ILaunch launch : launches) { launchAdded(launch); } } /* (non-Javadoc) * @see org.eclipse.debug.core.ILaunchesListener#launchesChanged(org.eclipse.debug.core.ILaunch[]) */ public void launchesChanged(ILaunch[] launches) { for (ILaunch launch : launches) { launchChanged(launch); } } /* (non-Javadoc) * @see org.eclipse.debug.core.ILaunchesListener#launchesRemoved(org.eclipse.debug.core.ILaunch[]) */ public void launchesRemoved(ILaunch[] launches) { for (ILaunch launch : launches) { launchRemoved(launch); } } } /* (non-Javadoc) * @see org.eclipse.debug.core.model.LaunchConfigurationDelegate#getLaunch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String) */ @Override public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) throws CoreException { return new MultiLaunch(configuration, mode); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.ILaunchConfigurationDelegate#launch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IProgressMonitor) */ public void launch(ILaunchConfiguration configuration, String mode, final ILaunch launch, IProgressMonitor monitor) throws CoreException { // Have to temporarily turn off the "remove terminated launches when new one created" // preference because it does not work well for multilaunch final IPreferenceStore prefStore = DebugUIPlugin.getDefault().getPreferenceStore(); boolean dstore = prefStore.getBoolean(IDebugUIConstants.PREF_AUTO_REMOVE_OLD_LAUNCHES); try { monitor.beginTask(LaunchMessages.MultiLaunchConfigurationDelegate_0 + configuration.getName(), 1000); prefStore.setValue(IDebugUIConstants.PREF_AUTO_REMOVE_OLD_LAUNCHES, false); List<LaunchElement> launches = createLaunchElements(configuration, new ArrayList<LaunchElement>()); for (LaunchElement le : launches) { if (!le.enabled) continue; // find launch; if not found, skip (error?) final ILaunchConfiguration conf = findLaunch(le.name); if (conf == null) continue; // determine mode for each launch final String localMode; if (le.mode != null && !le.mode.equals(DEFAULT_MODE)) { localMode = le.mode; } else { localMode = mode; } if (!conf.supportsMode(localMode)) { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { public void run() { MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), LaunchMessages.LaunchUIPlugin_Error, NLS.bind(LaunchMessages.MultiLaunchConfigurationDelegate_Cannot, conf.toString(), localMode)); } }); continue; } try { if (configuration.getName().equals(conf.getName())) throw new StackOverflowError(); // LAUNCH child here ILaunch subLaunch = DebugUIPlugin.buildAndLaunch(conf, localMode, new SubProgressMonitor(monitor, 1000 / launches.size())); ((MultiLaunch)launch).addSubLaunch(subLaunch); // Now that we added the launch in our list, we have already // received the real launchChanged event, and did not know it was part of our list // So, fake another event now. ((MultiLaunch)launch).launchChanged(subLaunch); postLaunchAction(subLaunch, le.action, le.actionParam, monitor); } catch (StackOverflowError e) { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { public void run() { MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), LaunchMessages.LaunchUIPlugin_Error, NLS.bind(LaunchMessages.MultiLaunchConfigurationDelegate_Loop, conf.toString())); } }); } } if (!launch.hasChildren()) { DebugPlugin.getDefault().getLaunchManager().removeLaunch(launch); } } finally { prefStore.setValue(IDebugUIConstants.PREF_AUTO_REMOVE_OLD_LAUNCHES, dstore); monitor.done(); } } private void postLaunchAction(ILaunch subLaunch, EPostLaunchAction action, Object actionParam, IProgressMonitor monitor) { switch (action) { case NONE: return; case WAIT_FOR_TERMINATION: monitor.subTask(LaunchMessages.MultiLaunchConfigurationDelegate_Action_WaitingForTermination + " " + subLaunch.getLaunchConfiguration().getName()); //$NON-NLS-1$ while (!subLaunch.isTerminated() && !monitor.isCanceled()) { try { Thread.sleep(1000); } catch (InterruptedException e) { break; } } monitor.subTask(""); //$NON-NLS-1$ break; case DELAY: Integer waitSecs = (Integer)actionParam; if (waitSecs != null) { monitor.subTask(NLS.bind(LaunchMessages.MultiLaunchConfigurationDelegate_Action_Delaying, waitSecs.toString())); try { Thread.sleep(waitSecs * 1000); // param is milliseconds } catch (InterruptedException e) { // ok } } break; default: assert false : "new post launch action type is missing logic"; //$NON-NLS-1$ } } /* (non-Javadoc) * @see org.eclipse.debug.core.model.LaunchConfigurationDelegate#buildProjects(org.eclipse.core.resources.IProject[], org.eclipse.core.runtime.IProgressMonitor) */ protected void buildProjects(IProject[] projects, IProgressMonitor monitor) throws CoreException { // do nothing, project can be rebuild for each launch individually } /* (non-Javadoc) * @see org.eclipse.debug.core.model.LaunchConfigurationDelegate#buildForLaunch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.core.runtime.IProgressMonitor) */ public boolean buildForLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor) throws CoreException { // not build for this one return false; } protected static ILaunchConfiguration findLaunch(String name) throws CoreException { ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); ILaunchConfiguration[] launchConfigurations = launchManager.getLaunchConfigurations(); for (int i = 0; i < launchConfigurations.length; i++) { ILaunchConfiguration lConf = launchConfigurations[i]; if (lConf.getName().equals(name)) return lConf; } return null; } public static List<LaunchElement> createLaunchElements(ILaunchConfiguration configuration, List<MultiLaunchConfigurationDelegate.LaunchElement> input) { try { Map<?,?> attrs = configuration.getAttributes(); for (Iterator<?> iterator = attrs.keySet().iterator(); iterator.hasNext();) { String attr = (String) iterator.next(); try { if (attr.startsWith(MultiLaunchConfigurationDelegate.MULTI_LAUNCH_CONSTANTS_PREFIX)) { String prop = attr.substring(MultiLaunchConfigurationDelegate.MULTI_LAUNCH_CONSTANTS_PREFIX .length() + 1); int k = prop.indexOf('.'); String num = prop.substring(0, k); int index = Integer.parseInt(num); String name = prop.substring(k + 1); if (name.equals(NAME_PROP)) { MultiLaunchConfigurationDelegate.LaunchElement el = new MultiLaunchConfigurationDelegate.LaunchElement(); el.index = index; el.name = (String) attrs.get(attr); Object actionParam = null; String actionStr = (String)attrs.get(getProp(index, ACTION_PROP)); EPostLaunchAction action; try { action = EPostLaunchAction.valueOf(actionStr); } catch (Exception e) { action = EPostLaunchAction.NONE; } if (action == EPostLaunchAction.DELAY) { try { actionParam = Integer.parseInt((String)attrs.get(getProp(index, ACTION_PARAM_PROP))); } catch (NumberFormatException exc) { LaunchUIPlugin.log(exc); } } el.action = action; el.actionParam = actionParam; el.mode = (String) attrs.get(getProp(index, MODE_PROP)); el.enabled = "true".equals(attrs.get(getProp(index, ENABLED_PROP))); //$NON-NLS-1$ try { el.data = findLaunch(el.name); } catch (Exception e) { el.data = null; } while (index >= input.size()) { input.add(null); } input.set(index, el); } } } catch (Exception e) { LaunchUIPlugin.log(e); } } } catch (CoreException e) { LaunchUIPlugin.log(e); } return input; } public static void storeLaunchElements(ILaunchConfigurationWorkingCopy configuration, List<LaunchElement> input) { int i = 0; removeLaunchElements(configuration); for (LaunchElement el : input) { if (el == null) continue; configuration.setAttribute(MultiLaunchConfigurationDelegate.getProp(i, NAME_PROP), el.name); configuration.setAttribute(MultiLaunchConfigurationDelegate.getProp(i, ACTION_PROP), el.action.toString()); // note: the saving of the action param will need to be enhanced if ever an action type is introduced that uses something that can't be reconstructed from its toString() configuration.setAttribute(MultiLaunchConfigurationDelegate.getProp(i, ACTION_PARAM_PROP), el.actionParam != null ? el.actionParam.toString() : null); configuration.setAttribute(MultiLaunchConfigurationDelegate.getProp(i, MODE_PROP), el.mode); configuration.setAttribute(MultiLaunchConfigurationDelegate.getProp(i, ENABLED_PROP), el.enabled + ""); //$NON-NLS-1$ i++; } } public static void removeLaunchElements(ILaunchConfigurationWorkingCopy configuration) { try { Map<?,?> attrs = configuration.getAttributes(); for (Iterator<?> iterator = attrs.keySet().iterator(); iterator.hasNext();) { String attr = (String) iterator.next(); try { if (attr.startsWith(MultiLaunchConfigurationDelegate.MULTI_LAUNCH_CONSTANTS_PREFIX)) { configuration.removeAttribute(attr); } } catch (Exception e) { LaunchUIPlugin.log(e); } } } catch (CoreException e) { LaunchUIPlugin.log(e); } } public static String getProp(int index, String string) { return MultiLaunchConfigurationDelegate.MULTI_LAUNCH_CONSTANTS_PREFIX + "." + index + "." + string; //$NON-NLS-1$ //$NON-NLS-2$ } /** * Test if a launch configuration is a valid reference. * @param config configuration reference * @return <code>true</code> if it is a valid reference, <code>false</code> if launch configuration should be filtered */ public static boolean isValidLaunchReference(ILaunchConfiguration config) { return DebugUIPlugin.doLaunchConfigurationFiltering( config) && !WorkbenchActivityHelper.filterItem(config); } }