blob: 46a76c4ebe5e4306c3bb2646659a6c18e44a7fdb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 IBM Corporation 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
*******************************************************************************/
package org.eclipse.dltk.internal.debug.core.model;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ListenerList;
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.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStepFilters;
import org.eclipse.debug.core.model.ITerminate;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.dltk.compiler.CharOperation;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.DLTKLanguageManager;
import org.eclipse.dltk.core.IDLTKLanguageToolkit;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.dbgp.IDbgpSession;
import org.eclipse.dltk.dbgp.IDbgpStackLevel;
import org.eclipse.dltk.dbgp.IDbgpStreamFilter;
import org.eclipse.dltk.dbgp.IDbgpThreadAcceptor;
import org.eclipse.dltk.debug.core.DLTKDebugLaunchConstants;
import org.eclipse.dltk.debug.core.DLTKDebugPlugin;
import org.eclipse.dltk.debug.core.ExtendedDebugEventDetails;
import org.eclipse.dltk.debug.core.IDbgpService;
import org.eclipse.dltk.debug.core.IDebugOptions;
import org.eclipse.dltk.debug.core.ScriptDebugManager;
import org.eclipse.dltk.debug.core.model.DefaultDebugOptions;
import org.eclipse.dltk.debug.core.model.IScriptBreakpoint;
import org.eclipse.dltk.debug.core.model.IScriptBreakpointLineMapper;
import org.eclipse.dltk.debug.core.model.IScriptBreakpointPathMapper;
import org.eclipse.dltk.debug.core.model.IScriptDebugTarget;
import org.eclipse.dltk.debug.core.model.IScriptDebugTargetListener;
import org.eclipse.dltk.debug.core.model.IScriptDebugThreadConfigurator;
import org.eclipse.dltk.debug.core.model.IScriptThread;
import org.eclipse.dltk.debug.core.model.IScriptVariable;
public class ScriptDebugTarget extends ScriptDebugElement implements
IScriptDebugTarget, IScriptThreadManagerListener, IStepFilters {
/**
* @deprecated
* @see #getScriptProject()
*/
@Deprecated
private static final String LAUNCH_CONFIGURATION_ATTR_PROJECT = "project"; //$NON-NLS-1$
private static final int THREAD_TERMINATION_TIMEOUT = 5000; // 5 seconds
private final ListenerList<IScriptDebugTargetListener> listeners;
private IScriptStreamProxy streamProxy;
private IProcess process;
private final ILaunch launch;
private String name;
private boolean disconnected;
private final IScriptThreadManager threadManager;
final ScriptBreakpointManager breakpointManager;
private final IDbgpService dbgpService;
private final String sessionId;
private final String modelId;
private static final WeakHashMap targets = new WeakHashMap();
private String[] stepFilters;
private boolean useStepFilters;
private final Object processLock = new Object();
private boolean initialized = false;
private boolean retrieveGlobalVariables;
private boolean retrieveClassVariables;
private boolean retrieveLocalVariables;
private final IDebugOptions options;
private IScriptBreakpointLineMapper lineMapper;
public static List getAllTargets() {
synchronized (targets) {
return new ArrayList(targets.keySet());
}
}
public ScriptDebugTarget(String modelId, IDbgpService dbgpService,
String sessionId, ILaunch launch, IProcess process) {
this(modelId, dbgpService, sessionId, launch, process,
DefaultDebugOptions.getDefaultInstance());
}
public ScriptDebugTarget(String modelId, IDbgpService dbgpService,
String sessionId, ILaunch launch, IProcess process,
IDebugOptions options) {
Assert.isNotNull(options);
this.modelId = modelId;
this.listeners = new ListenerList<>();
this.process = process;
this.launch = launch;
this.options = options;
this.threadManager = new /* New */ScriptThreadManager(this);
this.threadManager.addListener(this);
this.sessionId = sessionId;
this.dbgpService = dbgpService;
this.disconnected = false;
lineMapper = createLineMapper();
this.breakpointManager = new ScriptBreakpointManager(this,
createPathMapper(), lineMapper,
DebugPlugin.getDefault().getBreakpointManager(),
ScriptDebugManager.getInstance()::getDebugToolkitByDebugModel);
DebugEventHelper.fireCreateEvent(this);
synchronized (targets) {
targets.put(this, ""); //$NON-NLS-1$
}
}
public void shutdown() {
try {
terminate(true);
} catch (DebugException e) {
DLTKDebugPlugin.log(e);
}
}
@Override
public String getSessionId() {
return sessionId;
}
@Override
public IDebugTarget getDebugTarget() {
return this;
}
@Override
public String getModelIdentifier() {
return modelId;
}
@Override
public ILaunch getLaunch() {
return launch;
}
// IDebugTarget
@Override
public IProcess getProcess() {
synchronized (processLock) {
return process;
}
}
public void setProcess(IProcess process) {
synchronized (processLock) {
this.process = process;
}
}
@Override
public boolean hasThreads() {
return threadManager.hasThreads();
}
@Override
public IThread[] getThreads() {
return threadManager.getThreads();
}
@Override
public String getName() {
return name;
}
// ITerminate
@Override
public boolean canTerminate() {
synchronized (processLock) {
return threadManager.canTerminate()
|| process != null && process.canTerminate();
}
}
@Override
public boolean isTerminated() {
synchronized (processLock) {
return threadManager.isTerminated()
&& (process == null || process.isTerminated());
}
}
protected static boolean waitTerminated(ITerminate terminate, int chunk,
long timeout) {
final long start = System.currentTimeMillis();
while (!terminate.isTerminated()) {
if (System.currentTimeMillis() - start > timeout) {
return false;
}
try {
Thread.sleep(chunk);
} catch (InterruptedException e) {
// interrupted
}
}
return true;
}
@Override
public void terminate() throws DebugException {
terminate(true);
}
protected void terminate(boolean waitTermination) throws DebugException {
fireTargetTerminating();
threadManager.sendTerminationRequest();
if (waitTermination) {
final IProcess p = getProcess();
final int CHUNK = 500;
if (!(waitTerminated(threadManager, CHUNK,
THREAD_TERMINATION_TIMEOUT)
&& (p == null || waitTerminated(p, CHUNK,
THREAD_TERMINATION_TIMEOUT)))) {
// Debugging process is not answering, so terminating it
if (p != null && p.canTerminate()) {
p.terminate();
}
}
}
threadManager.removeListener(this);
breakpointManager.threadTerminated();
DebugEventHelper.fireTerminateEvent(this);
}
// ISuspendResume
@Override
public boolean canSuspend() {
return threadManager.canSuspend();
}
@Override
public boolean isSuspended() {
return threadManager.isSuspended();
}
@Override
public void suspend() throws DebugException {
threadManager.suspend();
}
@Override
public boolean canResume() {
return threadManager.canResume();
}
@Override
public void resume() throws DebugException {
threadManager.resume();
}
// IDisconnect
@Override
public boolean canDisconnect() {
// Detach feature support!!!
return false;
}
@Override
public void disconnect() {
disconnected = true;
}
@Override
public boolean isDisconnected() {
return disconnected;
}
// IMemoryBlockRetrieval
@Override
public boolean supportsStorageRetrieval() {
return false;
}
@Override
public IMemoryBlock getMemoryBlock(long startAddress, long length) {
return null;
}
public IScriptVariable findVariable(String variableName) {
// TODO Auto-generated method stub
return null;
}
// Request timeout
@Override
public int getRequestTimeout() {
return 0;
}
@Override
public void setRequestTimeout(int timeout) {
}
// IBreakpointListener
@Override
public void breakpointAdded(IBreakpoint breakpoint) {
breakpointManager.breakpointAdded(breakpoint);
}
@Override
public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) {
breakpointManager.breakpointChanged(breakpoint, delta);
}
@Override
public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) {
breakpointManager.breakpointRemoved(breakpoint, delta);
}
// Streams
@Override
public IScriptStreamProxy getStreamProxy() {
return streamProxy;
}
@Override
public void setStreamProxy(IScriptStreamProxy proxy) {
this.streamProxy = proxy;
if (proxy != null) {
proxy.setEncoding(getConsoleEncoding());
}
}
// IDbgpThreadManagerListener
@Override
public void threadAccepted(IScriptThread thread, boolean first) {
if (first) {
DebugEventHelper.fireExtendedEvent(this,
ExtendedDebugEventDetails.BEFORE_CODE_LOADED);
initialized = true;
fireTargetInitialized();
}
}
protected IScriptBreakpointPathMapper createPathMapper() {
return new NopScriptbreakpointPathMapper();
}
protected IScriptBreakpointLineMapper createLineMapper() {
return new NopScriptBreakpointLineMapper();
}
@Override
public void allThreadsTerminated() {
try {
if (streamProxy != null) {
streamProxy.close();
}
terminate(false);
} catch (DebugException e) {
DLTKDebugPlugin.log(e);
}
}
@Override
public String toString() {
return "Debugging engine (id = " + this.sessionId + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
// IScriptDebugTarget
@Override
public void runToLine(URI uri, int lineNumber) throws DebugException {
breakpointManager.setBreakpointUntilFirstSuspend(uri, lineNumber);
resume();
}
@Override
public boolean isInitialized() {
return initialized;
}
protected void fireTargetInitialized() {
for (IScriptDebugTargetListener listener : listeners) {
listener.targetInitialized();
}
}
protected void fireTargetTerminating() {
for (IScriptDebugTargetListener listener : listeners) {
listener.targetTerminating();
}
}
@Override
public void addListener(IScriptDebugTargetListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(IScriptDebugTargetListener listener) {
listeners.remove(listener);
}
@Override
public boolean supportsBreakpoint(IBreakpoint breakpoint) {
if (breakpoint instanceof IScriptBreakpoint) {
return StrUtils.equals(breakpoint.getModelIdentifier(),
getModelIdentifier());
}
return false;
}
@Override
public void setFilters(String[] activeFilters) {
this.stepFilters = activeFilters;
}
@Override
public String[] getFilters() {
if (this.stepFilters != null) {
return this.stepFilters;
}
return CharOperation.NO_STRINGS;
}
@Override
public boolean isUseStepFilters() {
return useStepFilters;
}
@Override
public void setUseStepFilters(boolean useStepFilters) {
this.useStepFilters = useStepFilters;
}
@Override
public IDLTKLanguageToolkit getLanguageToolkit() {
IScriptProject scriptProject = getScriptProject();
if (scriptProject != null) {
IDLTKLanguageToolkit toolkit = DLTKLanguageManager
.getLanguageToolkit(scriptProject);
return toolkit;
}
return null;
}
/**
* @deprecated project should not be used to detect nature, since the nature
* is already known before launch. Also, at some point we will
* have multiple natures in the same project, so it will not
* work correctly either.
*/
@Deprecated
protected IScriptProject getScriptProject() {
final ILaunchConfiguration configuration = launch
.getLaunchConfiguration();
if (configuration != null) {
try {
String projectName = configuration.getAttribute(
LAUNCH_CONFIGURATION_ATTR_PROJECT, (String) null);
if (projectName != null) {
projectName = projectName.trim();
if (projectName.length() > 0) {
IProject project = ResourcesPlugin.getWorkspace()
.getRoot().getProject(projectName);
return DLTKCore.create(project);
}
}
} catch (CoreException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
return null;
}
@Override
public boolean breakOnFirstLineEnabled() {
return DLTKDebugLaunchConstants.isBreakOnFirstLine(launch);
}
@Override
public void toggleGlobalVariables(boolean enabled) {
retrieveGlobalVariables = enabled;
threadManager.refreshThreads();
}
@Override
public void toggleClassVariables(boolean enabled) {
retrieveClassVariables = enabled;
threadManager.refreshThreads();
}
@Override
public void toggleLocalVariables(boolean enabled) {
retrieveLocalVariables = enabled;
threadManager.refreshThreads();
}
@Override
public boolean retrieveClassVariables() {
return retrieveClassVariables;
}
@Override
public boolean retrieveGlobalVariables() {
return retrieveGlobalVariables;
}
@Override
public boolean retrieveLocalVariables() {
return retrieveLocalVariables;
}
@Override
public String getConsoleEncoding() {
String encoding = "UTF-8"; //$NON-NLS-1$
try {
encoding = getLaunch().getLaunchConfiguration()
.getAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING, encoding);
} catch (CoreException e) {
e.printStackTrace();
}
return encoding;
}
public void setScriptDebugThreadConfigurator(
IScriptDebugThreadConfigurator configurator) {
this.threadManager.setScriptThreadConfigurator(configurator);
}
@Override
public IDebugOptions getOptions() {
return options;
}
@Override
public boolean isStepFiltersEnabled() {
return isUseStepFilters();
}
@Override
public void setStepFiltersEnabled(boolean enabled) {
setUseStepFilters(enabled);
}
@Override
public boolean supportsStepFilters() {
return true;
}
/*
* @see org.eclipse.dltk.debug.core.model.IScriptDebugTarget#getSessions()
*/
@Override
public IDbgpSession[] getSessions() {
return breakpointManager.getSessions();
}
@Override
public IScriptBreakpointPathMapper getPathMapper() {
return breakpointManager.bpPathMapper;
}
/**
* @param streamFilters
*/
public void setStreamFilters(IDbgpStreamFilter[] streamFilters) {
((ScriptThreadManager) threadManager).setStreamFilters(streamFilters);
}
public IDbgpService getDbgpService() {
return dbgpService;
}
public IDbgpThreadAcceptor getDbgpThreadAcceptor() {
return threadManager;
}
/**
* @since 2.0
*/
@Override
public boolean isRemote() {
return false;
}
/**
* Update stack traces if it is required.
*
* @since 5.2
*/
public void updateStackLevels(IDbgpStackLevel[] levels) {
if (lineMapper != null) {
for (int i = 0; i < levels.length; i++) {
IDbgpStackLevel level = lineMapper
.getSourceStackLevel(levels[i]);
if (level != null) {
levels[i] = level;
}
}
}
}
}