| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Pawel Piech - Bug 82003: The IDisconnect implementation by Launch module is too restrictive. |
| * Andrey Loskutov <loskutov@gmx.de> - Bug 506182 - Launch is not multi-thread safe |
| *******************************************************************************/ |
| package org.eclipse.debug.core; |
| |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReadWriteLock; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.MultiStatus; |
| import org.eclipse.core.runtime.PlatformObject; |
| import org.eclipse.debug.core.model.IDebugTarget; |
| import org.eclipse.debug.core.model.IDisconnect; |
| import org.eclipse.debug.core.model.IProcess; |
| import org.eclipse.debug.core.model.ISourceLocator; |
| import org.eclipse.debug.internal.core.DebugCoreMessages; |
| import org.eclipse.debug.internal.core.LaunchManager; |
| |
| /** |
| * A launch is the result of launching a debug session |
| * and/or one or more system processes. This class provides |
| * a public implementation of <code>ILaunch</code> for client |
| * use. |
| * <p> |
| * Clients may instantiate this class. Clients may subclass this class. |
| * </p> |
| * @see ILaunch |
| * @see ILaunchManager |
| */ |
| public class Launch extends PlatformObject implements ILaunch, IDisconnect, ILaunchListener, ILaunchConfigurationListener, IDebugEventSetListener { |
| |
| /** |
| * Lock object for controlling access to processes and targets |
| */ |
| private final ReadWriteLock lock = new ReentrantReadWriteLock(); |
| |
| private final Lock readLock = lock.readLock(); |
| |
| private final Lock writeLock = lock.writeLock(); |
| |
| /** |
| * The debug targets associated with this |
| * launch (the primary target is the first one |
| * in this collection), or empty if |
| * there are no debug targets. |
| */ |
| private List<IDebugTarget> fTargets = new ArrayList<>(); |
| |
| /** |
| * The configuration that was launched, or null. |
| */ |
| private ILaunchConfiguration fConfiguration= null; |
| |
| /** |
| * The system processes associated with |
| * this launch, or empty if none. |
| */ |
| private List<IProcess> fProcesses = new ArrayList<>(); |
| |
| /** |
| * The source locator to use in the debug session |
| * or <code>null</code> if not supported. |
| */ |
| private ISourceLocator fLocator= null; |
| |
| /** |
| * The mode this launch was launched in. |
| */ |
| private String fMode; |
| |
| /** |
| * Table of client defined attributes |
| */ |
| private HashMap<String, String> fAttributes; |
| |
| /** |
| * Flag indicating that change notification should |
| * be suppressed. <code>true</code> until this |
| * launch has been initialized. |
| */ |
| private boolean fSuppressChange = true; |
| |
| /** |
| * Constructs a launch with the specified attributes. |
| * |
| * @param launchConfiguration the configuration that was launched |
| * @param mode the mode of this launch - run or debug (constants |
| * defined by <code>ILaunchManager</code>) |
| * @param locator the source locator to use for this debug session, or |
| * <code>null</code> if not supported |
| */ |
| public Launch(ILaunchConfiguration launchConfiguration, String mode, ISourceLocator locator) { |
| fConfiguration = launchConfiguration; |
| setSourceLocator(locator); |
| fMode = mode; |
| fSuppressChange = false; |
| getLaunchManager().addLaunchListener(this); |
| getLaunchManager().addLaunchConfigurationListener(this); |
| } |
| |
| /** |
| * Registers debug event listener. |
| */ |
| private void addEventListener() { |
| DebugPlugin.getDefault().addDebugEventListener(this); |
| } |
| |
| /** |
| * Removes debug event listener. |
| */ |
| private void removeEventListener() { |
| DebugPlugin.getDefault().removeDebugEventListener(this); |
| } |
| |
| /** |
| * @see org.eclipse.debug.core.model.ITerminate#canTerminate() |
| */ |
| @Override |
| public boolean canTerminate() { |
| readLock.lock(); |
| try { |
| for (IProcess process : getProcesses0()) { |
| if (process.canTerminate()) { |
| return true; |
| } |
| } |
| for (IDebugTarget target : getDebugTargets0()) { |
| if (target.canTerminate() || target.canDisconnect()) { |
| return true; |
| } |
| } |
| } finally { |
| readLock.unlock(); |
| } |
| return false; |
| } |
| |
| /** |
| * @see ILaunch#getChildren() |
| */ |
| @Override |
| public Object[] getChildren() { |
| readLock.lock(); |
| ArrayList<Object> children; |
| try { |
| children = new ArrayList<>(getDebugTargets0()); |
| children.addAll(getProcesses0()); |
| } finally { |
| readLock.unlock(); |
| } |
| return children.toArray(); |
| } |
| |
| /** |
| * @see ILaunch#getDebugTarget() |
| */ |
| @Override |
| public IDebugTarget getDebugTarget() { |
| readLock.lock(); |
| try { |
| if (!getDebugTargets0().isEmpty()) { |
| return getDebugTargets0().get(0); |
| } |
| } finally { |
| readLock.unlock(); |
| } |
| return null; |
| } |
| |
| /** |
| * @see ILaunch#getProcesses() |
| */ |
| @Override |
| public IProcess[] getProcesses() { |
| readLock.lock(); |
| try { |
| return getProcesses0().toArray(new IProcess[getProcesses0().size()]); |
| } finally { |
| readLock.unlock(); |
| } |
| } |
| |
| /** |
| * Returns the processes associated with this |
| * launch, in its internal form - a list. |
| * |
| * @return list of processes |
| */ |
| protected List<IProcess> getProcesses0() { |
| return fProcesses; |
| } |
| |
| /** |
| * @see ILaunch#getSourceLocator() |
| */ |
| @Override |
| public ISourceLocator getSourceLocator() { |
| return fLocator; |
| } |
| |
| /** |
| * @see ILaunch#setSourceLocator(ISourceLocator) |
| */ |
| @Override |
| public void setSourceLocator(ISourceLocator sourceLocator) { |
| fLocator = sourceLocator; |
| } |
| |
| /** |
| * @see org.eclipse.debug.core.model.ITerminate#isTerminated() |
| */ |
| @Override |
| public boolean isTerminated() { |
| readLock.lock(); |
| try { |
| if (getProcesses0().isEmpty() && getDebugTargets0().isEmpty()) { |
| return false; |
| } |
| for (IProcess process : getProcesses0()) { |
| if (!process.isTerminated()) { |
| return false; |
| } |
| } |
| for (IDebugTarget target : getDebugTargets0()) { |
| if (!(target.isTerminated() || target.isDisconnected())) { |
| return false; |
| } |
| } |
| } finally { |
| readLock.unlock(); |
| } |
| return true; |
| } |
| |
| /** |
| * @see org.eclipse.debug.core.model.ITerminate#terminate() |
| */ |
| @Override |
| public void terminate() throws DebugException { |
| MultiStatus status= |
| new MultiStatus(DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED, DebugCoreMessages.Launch_terminate_failed, null); |
| //stop targets first to free up and sockets, etc held by the target |
| // terminate or disconnect debug target if it is still alive |
| IDebugTarget[] targets = getDebugTargets(); |
| for (IDebugTarget target : targets) { |
| if (target != null) { |
| if (target.canTerminate()) { |
| try { |
| target.terminate(); |
| } catch (DebugException e) { |
| status.merge(e.getStatus()); |
| } |
| } else { |
| if (target.canDisconnect()) { |
| try { |
| target.disconnect(); |
| } catch (DebugException de) { |
| status.merge(de.getStatus()); |
| } |
| } |
| } |
| } |
| } |
| //second kill the underlying process |
| // terminate the system processes |
| IProcess[] processes = getProcesses(); |
| for (IProcess process : processes) { |
| if (process.canTerminate()) { |
| try { |
| process.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); |
| } |
| |
| /** |
| * @see ILaunch#getLaunchMode() |
| */ |
| @Override |
| public String getLaunchMode() { |
| return fMode; |
| } |
| |
| /** |
| * @see ILaunch#getLaunchConfiguration() |
| */ |
| @Override |
| public ILaunchConfiguration getLaunchConfiguration() { |
| return fConfiguration; |
| } |
| |
| /** |
| * @see ILaunch#setAttribute(String, String) |
| */ |
| @Override |
| public void setAttribute(String key, String value) { |
| if (fAttributes == null) { |
| fAttributes = new HashMap<>(5); |
| } |
| fAttributes.put(key, value); |
| } |
| |
| /** |
| * @see ILaunch#getAttribute(String) |
| */ |
| @Override |
| public String getAttribute(String key) { |
| if (fAttributes == null) { |
| return null; |
| } |
| return fAttributes.get(key); |
| } |
| |
| /** |
| * @see ILaunch#getDebugTargets() |
| */ |
| @Override |
| public IDebugTarget[] getDebugTargets() { |
| readLock.lock(); |
| try { |
| return fTargets.toArray(new IDebugTarget[fTargets.size()]); |
| } finally { |
| readLock.unlock(); |
| } |
| } |
| |
| /** |
| * Returns the debug targets associated with this |
| * launch, in its internal form - a list |
| * |
| * @return list of debug targets |
| */ |
| protected List<IDebugTarget> getDebugTargets0() { |
| return fTargets; |
| } |
| |
| /** |
| * @see ILaunch#addDebugTarget(IDebugTarget) |
| */ |
| @Override |
| public void addDebugTarget(IDebugTarget target) { |
| if (target != null) { |
| writeLock.lock(); |
| boolean changed = false; |
| try { |
| if (!getDebugTargets0().contains(target)) { |
| addEventListener(); |
| changed = getDebugTargets0().add(target); |
| } |
| } finally { |
| writeLock.unlock(); |
| if (changed) { |
| fireChanged(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @see ILaunch#removeDebugTarget(IDebugTarget) |
| */ |
| @Override |
| public void removeDebugTarget(IDebugTarget target) { |
| if (target != null) { |
| writeLock.lock(); |
| boolean changed = false; |
| try { |
| changed = getDebugTargets0().remove(target); |
| } finally { |
| writeLock.unlock(); |
| if (changed) { |
| fireChanged(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @see ILaunch#addProcess(IProcess) |
| */ |
| @Override |
| public void addProcess(IProcess process) { |
| if (process != null) { |
| writeLock.lock(); |
| boolean changed = false; |
| try { |
| if (!getProcesses0().contains(process)) { |
| addEventListener(); |
| changed = getProcesses0().add(process); |
| } |
| } finally { |
| writeLock.unlock(); |
| if (changed) { |
| fireChanged(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @see ILaunch#removeProcess(IProcess) |
| */ |
| @Override |
| public void removeProcess(IProcess process) { |
| if (process != null) { |
| writeLock.lock(); |
| boolean changed = false; |
| try { |
| changed = getProcesses0().remove(process); |
| } finally { |
| writeLock.unlock(); |
| if (changed) { |
| fireChanged(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adds the given processes to this launch. |
| * |
| * @param processes processes to add |
| */ |
| protected void addProcesses(IProcess[] processes) { |
| if (processes != null) { |
| for (IProcess process : processes) { |
| addProcess(process); |
| fireChanged(); |
| } |
| } |
| } |
| |
| /** |
| * Notifies listeners that this launch has changed. |
| * Has no effect of this launch has not yet been |
| * properly created/initialized. |
| */ |
| protected void fireChanged() { |
| if (!fSuppressChange) { |
| ((LaunchManager)getLaunchManager()).fireUpdate(this, LaunchManager.CHANGED); |
| ((LaunchManager)getLaunchManager()).fireUpdate(new ILaunch[] {this}, LaunchManager.CHANGED); |
| } |
| } |
| |
| /** |
| * Notifies listeners that this launch has terminated. |
| * Has no effect of this launch has not yet been |
| * properly created/initialized. |
| */ |
| protected void fireTerminate() { |
| if (!fSuppressChange) { |
| ((LaunchManager)getLaunchManager()).fireUpdate(this, LaunchManager.TERMINATE); |
| ((LaunchManager)getLaunchManager()).fireUpdate(new ILaunch[] {this}, LaunchManager.TERMINATE); |
| } |
| removeEventListener(); |
| } |
| |
| /** |
| * @see ILaunch#hasChildren() |
| */ |
| @Override |
| public boolean hasChildren() { |
| return getProcesses0().size() > 0 || (getDebugTargets0().size() > 0); |
| } |
| |
| /** |
| * Returns whether any processes or targets can be disconnected. |
| * Ones that are already terminated or disconnected are ignored. |
| * |
| * @see org.eclipse.debug.core.model.IDisconnect#canDisconnect() |
| */ |
| @Override |
| public boolean canDisconnect() { |
| readLock.lock(); |
| try { |
| for (IProcess process : getProcesses0()) { |
| if (process instanceof IDisconnect) { |
| if (((IDisconnect) process).canDisconnect()) { |
| return true; |
| } |
| } |
| } |
| for (IDebugTarget target : getDebugTargets0()) { |
| if (target.canDisconnect()) { |
| return true; |
| } |
| } |
| } finally { |
| readLock.unlock(); |
| } |
| return false; |
| } |
| |
| /** |
| * @see org.eclipse.debug.core.model.IDisconnect#disconnect() |
| */ |
| @Override |
| public void disconnect() throws DebugException { |
| readLock.lock(); |
| try { |
| for (IProcess process : getProcesses0()) { |
| if (process instanceof IDisconnect) { |
| IDisconnect dis = (IDisconnect) process; |
| if (dis.canDisconnect()) { |
| dis.disconnect(); |
| } |
| } |
| } |
| for (IDebugTarget target : getDebugTargets0()) { |
| if (target.canDisconnect()) { |
| target.disconnect(); |
| } |
| } |
| } finally { |
| readLock.unlock(); |
| } |
| } |
| |
| /** |
| * Returns whether all of the contained targets and processes are |
| * disconnected. Processes that don't support disconnecting are not |
| * counted. |
| * |
| * @see org.eclipse.debug.core.model.IDisconnect#isDisconnected() |
| */ |
| @Override |
| public boolean isDisconnected() { |
| readLock.lock(); |
| try { |
| for (IProcess process : getProcesses0()) { |
| if (process instanceof IDisconnect) { |
| if (!((IDisconnect) process).isDisconnected()) { |
| return false; |
| } |
| } |
| } |
| for (IDebugTarget target : getDebugTargets0()) { |
| if (!target.isDisconnected()) { |
| return false; |
| } |
| } |
| } finally { |
| readLock.unlock(); |
| } |
| // only return true if there are processes or targets that are disconnected |
| return hasChildren(); |
| } |
| |
| @Override |
| public void launchRemoved(ILaunch launch) { |
| if (this.equals(launch)) { |
| removeEventListener(); |
| getLaunchManager().removeLaunchListener(this); |
| getLaunchManager().removeLaunchConfigurationListener(this); |
| } |
| } |
| |
| /** |
| * Returns the launch manager. |
| * |
| * @return the launch manager. |
| */ |
| protected ILaunchManager getLaunchManager() { |
| return DebugPlugin.getDefault().getLaunchManager(); |
| } |
| |
| @Override |
| public void launchAdded(ILaunch launch) { |
| } |
| |
| @Override |
| public void launchChanged(ILaunch launch) { |
| } |
| |
| /* |
| * If the launch configuration this launch is associated with is moved, |
| * update the underlying handle to the new location. |
| */ |
| @Override |
| public void launchConfigurationAdded(ILaunchConfiguration configuration) { |
| ILaunchConfiguration from = getLaunchManager().getMovedFrom(configuration); |
| if (from != null && from.equals(getLaunchConfiguration())) { |
| fConfiguration = configuration; |
| fireChanged(); |
| } |
| } |
| |
| @Override |
| public void launchConfigurationChanged(ILaunchConfiguration configuration) {} |
| |
| /* |
| * Update the launch configuration associated with this launch if the |
| * underlying configuration is deleted. |
| */ |
| @Override |
| public void launchConfigurationRemoved(ILaunchConfiguration configuration) { |
| if (configuration.equals(getLaunchConfiguration())) { |
| if (getLaunchManager().getMovedTo(configuration) == null) { |
| fConfiguration = null; |
| fireChanged(); |
| } |
| } |
| } |
| |
| @Override |
| public void handleDebugEvents(DebugEvent[] events) { |
| for (DebugEvent event : events) { |
| if (event.getKind() == DebugEvent.TERMINATE) { |
| Object object = event.getSource(); |
| ILaunch launch = null; |
| if (object instanceof IProcess) { |
| launch = ((IProcess)object).getLaunch(); |
| } else if (object instanceof IDebugTarget) { |
| launch = ((IDebugTarget)object).getLaunch(); |
| } |
| if (this.equals(launch)) { |
| if (isTerminated()) { |
| fireTerminate(); |
| } |
| } |
| } |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public <T> T getAdapter(Class<T> adapter) { |
| if (adapter.equals(ILaunch.class)) { |
| return (T) this; |
| } |
| //CONTEXTLAUNCHING |
| if(adapter.equals(ILaunchConfiguration.class)) { |
| return (T) getLaunchConfiguration(); |
| } |
| return super.getAdapter(adapter); |
| } |
| |
| |
| |
| } |