/******************************************************************************* * Copyright (c) 2009, 2016 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 * SSI Schaefer *******************************************************************************/ package org.eclipse.debug.internal.core.groups; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; 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.ILaunchesListener2; import org.eclipse.debug.core.Launch; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.internal.core.DebugCoreMessages; /** * A specialization of launch to track sub-launches life-cycle, also terminates * itself when all sub-launches are terminated * * @since 3.11 */ public class GroupLaunch extends Launch implements ILaunchesListener2 { /** * Whether this process has been terminated */ private boolean fTerminated; /** * Keeps track of whether launching has been finished */ private boolean fLaunched = false; /** * 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 GroupLaunch(ILaunchConfiguration launchConfiguration, String mode) { super(launchConfiguration, mode, null); getLaunchManager().addLaunchListener((ILaunchesListener2) this); } public void markLaunched() { fLaunched = true; } /** * Associate the launch * * @param subLaunch */ public void addSubLaunch(ILaunch subLaunch) { subLaunches.put(subLaunch, new IProcess[] {}); } private boolean isChild(ILaunch launch) { for (ILaunch subLaunch : subLaunches.keySet()) { 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 fLaunched; // in case we're done launching and there is // nobody -> terminated } for (ILaunch launch : subLaunches.keySet()) { if (!launch.isTerminated()) { return false; } } return fLaunched; // we're done only if we're already done launching. // this is required for the WAIT_FOR_TERMINATION // mode. } /** * 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 : subLaunches.keySet()) { 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); // somebody want's to explicitly kill the whole group, which should // immediately terminate and stop launching. So allow termination of the // group when children disappear even if launching has not finished yet. markLaunched(); for (ILaunch launch : subLaunches.keySet()) { 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 && fLaunched) { fTerminated = true; fireTerminate(); } } } @Override 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); } } } @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); } } @Override public void launchesTerminated(ILaunch[] launches) { for (ILaunch launch : launches) { launchTerminated(launch); } } @Override public void launchesAdded(ILaunch[] launches) { for (ILaunch launch : launches) { launchAdded(launch); } } @Override public void launchesChanged(ILaunch[] launches) { for (ILaunch launch : launches) { launchChanged(launch); } } @Override public void launchesRemoved(ILaunch[] launches) { for (ILaunch launch : launches) { launchRemoved(launch); } } }