blob: 7e109f06e42d9573310c19fafd4d164cbe06dc7f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2018 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.internal.debug.core.model;
import java.net.URI;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IRegisterGroup;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.dbgp.IDbgpProperty;
import org.eclipse.dltk.dbgp.IDbgpStackLevel;
import org.eclipse.dltk.dbgp.commands.IDbgpContextCommands;
import org.eclipse.dltk.dbgp.exceptions.DbgpDebuggingEngineException;
import org.eclipse.dltk.dbgp.exceptions.DbgpException;
import org.eclipse.dltk.debug.core.DLTKDebugPlugin;
import org.eclipse.dltk.debug.core.IScriptVariableContainer;
import org.eclipse.dltk.debug.core.ScriptDebugManager;
import org.eclipse.dltk.debug.core.model.IRefreshableScriptVariable;
import org.eclipse.dltk.debug.core.model.IScriptStack;
import org.eclipse.dltk.debug.core.model.IScriptStackFrame;
import org.eclipse.dltk.debug.core.model.IScriptThread;
import org.eclipse.dltk.debug.core.model.IScriptVariable;
import org.eclipse.dltk.debug.core.model.ISourceOffsetLookup;
import org.eclipse.osgi.util.NLS;
public class ScriptStackFrame extends ScriptDebugElement
implements IScriptStackFrame {
private final IScriptThread thread;
private IDbgpStackLevel level;
private final IScriptStack stack;
private ScriptVariableContainer variables = null;
private boolean needRefreshVariables = false;
protected static IScriptVariable[] readVariables(
ScriptStackFrame parentFrame, int contextId,
IDbgpContextCommands commands) throws DbgpException {
try {
IDbgpProperty[] properties = commands
.getContextProperties(parentFrame.getLevel(), contextId);
IScriptVariable[] variables = new IScriptVariable[properties.length];
// Workaround for bug 215215
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=215215
// Remove this code when Tcl active state debugger fixed
Set<String> duplicates = findDuplicateNames(properties);
for (int i = 0; i < properties.length; ++i) {
IDbgpProperty property = properties[i];
String name = property.getName();
if (duplicates.contains(name)) {
name = property.getEvalName();
}
variables[i] = new ScriptVariable(parentFrame, name, property);
}
return variables;
} catch (DbgpDebuggingEngineException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
return new IScriptVariable[0];
}
}
private static Set<String> findDuplicateNames(IDbgpProperty[] properties) {
final Set<String> duplicates = new HashSet<>();
final Set<String> alreadyExsisting = new HashSet<>();
for (int i = 0; i < properties.length; ++i) {
final IDbgpProperty property = properties[i];
final String name = property.getName();
if (!alreadyExsisting.add(name)) {
duplicates.add(name);
}
}
return duplicates;
}
/**
* Return null in case suspend more is no more active during calculation of
* variables.
*
* @return
* @throws DbgpException
*/
protected ScriptVariableContainer readAllVariables() throws DbgpException {
final IDbgpContextCommands commands = thread.getDbgpSession()
.getCoreCommands();
// TODO: Until more sequence approach will be implemented
if (!thread.isSuspended()) {
return null;
}
final ScriptVariableContainer result = new ScriptVariableContainer();
final Map names = commands.getContextNames(getLevel());
if (thread.retrieveLocalVariables()
&& names.containsKey(
Integer.valueOf(IDbgpContextCommands.LOCAL_CONTEXT_ID))
&& thread.isSuspended()) {
result.locals = readVariables(this,
IDbgpContextCommands.LOCAL_CONTEXT_ID, commands);
}
if (thread.retrieveGlobalVariables()
&& names.containsKey(
Integer.valueOf(IDbgpContextCommands.GLOBAL_CONTEXT_ID))
&& thread.isSuspended()) {
result.globals = readVariables(this,
IDbgpContextCommands.GLOBAL_CONTEXT_ID, commands);
}
if (thread.retrieveClassVariables()
&& names.containsKey(
Integer.valueOf(IDbgpContextCommands.CLASS_CONTEXT_ID))
&& thread.isSuspended()) {
result.classes = readVariables(this,
IDbgpContextCommands.CLASS_CONTEXT_ID, commands);
}
// TODO: Until more sequence approach will be implemented
if (!thread.isSuspended()) {
return null;
}
return result;
}
private static class ScriptVariableContainer {
IVariable[] locals = null;
IVariable[] globals = null;
IVariable[] classes = null;
ScriptVariableWrapper globalsWrapper = null;
ScriptVariableWrapper classesWrapper = null;
ScriptVariableContainer sort(IDebugTarget target) {
final Comparator variableComparator = ScriptDebugManager
.getInstance().getVariableNameComparatorByDebugModel(
target.getModelIdentifier());
if (locals != null) {
Arrays.sort(locals, variableComparator);
}
if (globals != null) {
Arrays.sort(globals, variableComparator);
}
if (classes != null) {
Arrays.sort(classes, variableComparator);
}
return this;
}
private int size() {
int size = 0;
if (locals != null) {
size += locals.length;
}
if (globals != null) {
++size;
}
if (classes != null) {
++size;
}
return size;
}
IScriptVariable[] toArray(IDebugTarget target) {
final int size = size();
final IScriptVariable[] result = new IScriptVariable[size];
if (size != 0) {
int index = 0;
if (globals != null) {
if (globalsWrapper == null) {
globalsWrapper = new ScriptVariableWrapper(target,
Messages.ScriptStackFrame_globalVariables,
globals,
IScriptVariableContainer.ContainerKind.Global);
} else {
globalsWrapper.refreshValue(globals);
}
result[index++] = globalsWrapper;
}
if (classes != null) {
if (classesWrapper == null) {
classesWrapper = new ScriptVariableWrapper(target,
Messages.ScriptStackFrame_classVariables,
classes,
IScriptVariableContainer.ContainerKind.Class);
} else {
classesWrapper.refreshValue(classes);
}
result[index++] = classesWrapper;
}
if (locals != null) {
System.arraycopy(locals, 0, result, index, locals.length);
index += locals.length;
}
}
return result;
}
/**
* @return
*/
public boolean hasVariables() {
return locals != null && locals.length != 0 || classes != null
|| globals != null;
}
/**
* @param varName
* @return
* @throws DebugException
*/
public IVariable findVariable(String varName) throws DebugException {
if (locals != null) {
final IVariable variable = findVariable(varName, locals);
if (variable != null) {
return variable;
}
}
if (globals != null) {
final IVariable variable = findVariable(varName, globals);
if (variable != null) {
return variable;
}
}
return null;
}
private static IVariable findVariable(String varName, IVariable[] vars)
throws DebugException {
for (int i = 0; i < vars.length; i++) {
final IVariable var = vars[i];
if (var.getName().equals(varName)) {
return var;
}
}
return null;
}
}
public ScriptStackFrame(IScriptStack stack, IDbgpStackLevel stackLevel) {
this.stack = stack;
this.thread = stack.getThread();
this.level = stackLevel;
}
public synchronized void updateVariables() {
this.variables = null;
}
@Override
public IScriptStack getStack() {
return stack;
}
private static final int MULTI_LINE_COUNT = 2;
@Override
public int getCharStart() throws DebugException {
final int beginLine = level.getBeginLine();
if (beginLine > 0) {
final int endLine = level.getEndLine();
if (endLine > 0 && endLine >= beginLine) {
final ISourceOffsetLookup offsetLookup = DLTKDebugPlugin
.getSourceOffsetLookup();
if (offsetLookup != null) {
return offsetLookup.calculateOffset(this, beginLine,
level.getBeginColumn(), false);
}
}
}
return -1;
}
@Override
public int getCharEnd() throws DebugException {
final int endLine = level.getEndLine();
if (endLine > 0) {
final int beginLine = level.getBeginLine();
if (beginLine > 0 && endLine >= beginLine) {
final ISourceOffsetLookup offsetLookup = DLTKDebugPlugin
.getSourceOffsetLookup();
if (offsetLookup != null) {
if (endLine < beginLine + MULTI_LINE_COUNT) {
final int offset = offsetLookup.calculateOffset(this,
endLine, level.getEndColumn(), true);
if (offset >= 0) {
return offset + 1;
}
} else {
final int offset = offsetLookup.calculateOffset(this,
beginLine, -1, true);
if (offset >= 0) {
return offset + 1;
}
}
}
}
}
return -1;
}
@Override
public int getLineNumber() {
return level.getLineNumber();
}
@Override
public int getMethodOffset() {
return level.getMethodOffset();
}
@Override
public String getMethodName() {
return level.getMethodName();
}
@Override
public int getBeginLine() {
return level.getBeginLine();
}
@Override
public int getBeginColumn() {
return level.getBeginColumn();
}
@Override
public int getEndLine() {
return level.getEndLine();
}
@Override
public int getEndColumn() {
return level.getEndColumn();
}
@Override
public String getWhere() {
return level.getWhere().trim();
}
@Override
public String getName() throws DebugException {
String name = level.getWhere().trim();
if (name == null || name.length() == 0) {
name = toString();
}
name += " (" + level.getFileURI().getPath() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
return name;
}
@Override
public boolean hasRegisterGroups() throws DebugException {
return false;
}
@Override
public IRegisterGroup[] getRegisterGroups() throws DebugException {
return new IRegisterGroup[0];
}
@Override
public IThread getThread() {
return thread;
}
@Override
public synchronized boolean hasVariables() throws DebugException {
checkVariablesAvailable();
if (variables == null) {
return false;
}
return variables.hasVariables();
}
private synchronized void checkVariablesAvailable() throws DebugException {
if (!thread.isSuspended()) {
// Do not do variables lookup if not suspended, since it could
// become in running/stepping state until we get here.
return;
}
try {
if (variables == null) {
variables = readAllVariables();
if (variables != null) {
variables.sort(getDebugTarget());
}
} else if (needRefreshVariables) {
try {
refreshVariables();
} finally {
needRefreshVariables = false;
}
}
} catch (DbgpException e) {
variables = new ScriptVariableContainer();
final Status status = new Status(IStatus.ERROR,
DLTKDebugPlugin.PLUGIN_ID,
Messages.ScriptStackFrame_unableToLoadVariables, e);
DLTKDebugPlugin.log(status);
throw new DebugException(status);
}
}
/**
* @throws DebugException
* @throws DbgpException
*/
private void refreshVariables() throws DebugException, DbgpException {
final ScriptVariableContainer newVars = readAllVariables();
if (newVars == null) {
// No need to refresh, refresh will happen in next
variables = null;
return;
}
newVars.sort(getDebugTarget());
variables.locals = refreshVariables(newVars.locals, variables.locals);
variables.globals = refreshVariables(newVars.globals,
variables.globals);
variables.classes = refreshVariables(newVars.classes,
variables.classes);
}
/**
* @param newVars
* @param oldVars
* @return
* @throws DebugException
*/
static IVariable[] refreshVariables(IVariable[] newVars,
IVariable[] oldVars) throws DebugException {
if (oldVars != null) {
final Map<String, IVariable> map = new HashMap<>();
for (int i = 0; i < oldVars.length; ++i) {
final IVariable variable = oldVars[i];
if (variable instanceof IRefreshableScriptVariable) {
map.put(variable.getName(), variable);
}
}
if (newVars != null) {
for (int i = 0; i < newVars.length; ++i) {
final IVariable variable = newVars[i];
final IRefreshableScriptVariable old;
old = (IRefreshableScriptVariable) map
.get(variable.getName());
if (old != null) {
newVars[i] = old.refreshVariable(variable);
}
}
}
}
return newVars;
}
@Override
public synchronized IVariable[] getVariables() throws DebugException {
checkVariablesAvailable();
if (variables != null) {
return variables.toArray(getDebugTarget());
}
return new IVariable[0];
}
// IStep
@Override
public boolean canStepInto() {
return thread.canStepInto();
}
@Override
public boolean canStepOver() {
return thread.canStepOver();
}
@Override
public boolean canStepReturn() {
return thread.canStepReturn();
}
@Override
public boolean isStepping() {
return thread.isStepping();
}
@Override
public void stepInto() throws DebugException {
thread.stepInto();
}
@Override
public void stepOver() throws DebugException {
thread.stepOver();
}
@Override
public void stepReturn() throws DebugException {
thread.stepReturn();
}
// ISuspenResume
@Override
public boolean canResume() {
return thread.canResume();
}
@Override
public boolean canSuspend() {
return thread.canSuspend();
}
@Override
public boolean isSuspended() {
return thread.isSuspended();
}
@Override
public void resume() throws DebugException {
thread.resume();
}
@Override
public void suspend() throws DebugException {
thread.suspend();
}
// ITerminate
@Override
public boolean canTerminate() {
return thread.canTerminate();
}
@Override
public boolean isTerminated() {
return thread.isTerminated();
}
@Override
public void terminate() throws DebugException {
thread.terminate();
}
// IDebugElement
@Override
public IDebugTarget getDebugTarget() {
return thread.getDebugTarget();
}
@Override
public synchronized IScriptVariable findVariable(String varName)
throws DebugException {
checkVariablesAvailable();
if (variables != null) {
return (IScriptVariable) variables.findVariable(varName);
}
return null;
}
@Override
public int getLevel() {
return level.getLevel();
}
@Override
public String toString() {
return NLS.bind(Messages.ScriptStackFrame_stackFrame,
Integer.valueOf(level.getLevel()));
}
@Override
public String getSourceLine() {
return level.getWhere();
}
@Override
public URI getSourceURI() {
return level.getFileURI();
}
@Override
public IScriptThread getScriptThread() {
return (IScriptThread) getThread();
}
/**
* @param frame
* @param depth
* @return
*/
public ScriptStackFrame bind(IDbgpStackLevel newLevel) {
if (level.isSameMethod(newLevel)) {
level = newLevel;
needRefreshVariables = true;
return this;
}
return new ScriptStackFrame(stack, newLevel);
}
}