/*******************************************************************************
* Copyright (c) 2009 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
*
* Contributors:
* IBM Corporation - initial API and implementation
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.debug.core.xdebug.dbgp.model;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IRegisterGroup;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.php.internal.debug.core.xdebug.dbgp.DBGpLogger;
import org.eclipse.php.internal.debug.core.xdebug.dbgp.protocol.DBGpResponse;
import org.eclipse.php.internal.debug.core.xdebug.dbgp.protocol.DBGpUtils;
import org.w3c.dom.Node;
public class DBGpStackFrame extends DBGpElement implements IStackFrame {
private static class DBGpVariableContainer {
IVariable[] locals = 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;
}
}
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;
}
}
private DBGpThread owningThread;
private String qualifiedFile = ""; // fully qualified name of //$NON-NLS-1$
// the file this stack frame is in
private String stackLevel; // the level of this stack frame
private String fileName; // workspace file relative to project, null if not
// in workspace
private int lineNo; // line within the file of this stack frame
private String name = ""; // string to display in debugger for //$NON-NLS-1$
// this stack frame
private DBGpVariableContainer currentVariables;
private DBGpVariableContainer previousVariables;
private Node descriptor;
// private IVariable[] variables; // variables exposed to this stack frame
public DBGpStackFrame(DBGpThread threadOwner, Node stackData) {
super(threadOwner.getDebugTarget());
owningThread = threadOwner;
update(stackData);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStackFrame#getCharEnd()
*/
public int getCharEnd() throws DebugException {
// Don't support expression level stepping, only line level stepping
return -1;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStackFrame#getCharStart()
*/
public int getCharStart() throws DebugException {
return -1;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStackFrame#getLineNumber()
*/
public int getLineNumber() throws DebugException {
DBGpLogger.debug(this.hashCode() + "::DBGpStackFrame=" + lineNo); //$NON-NLS-1$
return lineNo;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStackFrame#getName()
*/
public String getName() throws DebugException {
return name;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStackFrame#hasRegisterGroups()
*/
public boolean hasRegisterGroups() throws DebugException {
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStackFrame#getRegisterGroups()
*/
public IRegisterGroup[] getRegisterGroups() throws DebugException {
return new IRegisterGroup[0];
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStackFrame#getThread()
*/
public IThread getThread() {
return owningThread;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStackFrame#getVariables()
*/
public synchronized IVariable[] getVariables() throws DebugException {
DBGpLogger.debug("getting variables for stackframe on line: " + lineNo); //$NON-NLS-1$
if (currentVariables == null) {
currentVariables = new DBGpVariableContainer();
// fetch new set of variables
IVariable[] incoming = ((DBGpTarget) getDebugTarget()).getVariables(stackLevel);
currentVariables.locals = new IVariable[incoming.length];
for (int i = 0; i < incoming.length; i++) {
DBGpVariable variable = ((DBGpVariable) incoming[i]);
currentVariables.locals[i] = merge(variable);
}
}
return currentVariables.locals;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStackFrame#hasVariables()
*/
public boolean hasVariables() throws DebugException {
return (getVariables() != null && getVariables().length > 0);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#canStepInto()
*/
public boolean canStepInto() {
return owningThread.canStepInto();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#canStepOver()
*/
public boolean canStepOver() {
return owningThread.canStepOver();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#canStepReturn()
*/
public boolean canStepReturn() {
return owningThread.canStepReturn();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#isStepping()
*/
public boolean isStepping() {
return owningThread.isStepping();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#stepInto()
*/
public void stepInto() throws DebugException {
owningThread.stepInto();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#stepOver()
*/
public void stepOver() throws DebugException {
owningThread.stepOver();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#stepReturn()
*/
public void stepReturn() throws DebugException {
owningThread.stepReturn();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ISuspendResume#canResume()
*/
public boolean canResume() {
return owningThread.canResume();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ISuspendResume#canSuspend()
*/
public boolean canSuspend() {
return owningThread.canSuspend();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ISuspendResume#isSuspended()
*/
public boolean isSuspended() {
return owningThread.isSuspended();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ISuspendResume#resume()
*/
public void resume() throws DebugException {
owningThread.resume();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ISuspendResume#suspend()
*/
public void suspend() throws DebugException {
owningThread.suspend();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ITerminate#canTerminate()
*/
public boolean canTerminate() {
return owningThread.canTerminate();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ITerminate#isTerminated()
*/
public boolean isTerminated() {
return owningThread.isTerminated();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ITerminate#terminate()
*/
public void terminate() throws DebugException {
owningThread.terminate();
}
/**
* returns on the name of the file in this stackframe.
*
* @return
*/
public String getSourceName() {
return fileName;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (obj instanceof DBGpStackFrame) {
DBGpStackFrame sf = (DBGpStackFrame) obj;
try {
// a stack frame is equal if they are at the same level, same
// line and for
// the same file
//
// if a stack frame is equal then eclipse doesn't refresh the
// variables pane
// but subsequent new stackframes created at the same level (but
// on different
// line numbers are not used to get the variables, the first one
// at the level
// is used (eg a stackframe for line 2 is used to get variables
// for all other
// lines at the same stack level, even though a stack level for
// one at say line
// 4 exists).
//
// so to stop the refresh of the variables pane, stack frames at
// the same
// level should report as equal, but because of this a stack
// frame cannot
// cache the variables at that line as eclipse goes to the stack
// frame of
// another line number to get the stack variables.
boolean isEqual = sf.getQualifiedFile().equals(getQualifiedFile()) && sf.stackLevel.equals(stackLevel)
&& (sf.owningThread == owningThread) && sf.getLineNumber() == getLineNumber();
return isEqual;
} catch (Exception e) {
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return getQualifiedFile().hashCode() + stackLevel.hashCode() + owningThread.hashCode();
}
public String getQualifiedFile() {
return qualifiedFile;
}
public String getStackLevel() {
return stackLevel;
}
protected Node getDescriptor() {
return descriptor;
}
protected void update(Node stackData) {
// Reset state
this.previousVariables = currentVariables;
this.currentVariables = null;
// Set up new descriptor
descriptor = stackData;
String line = DBGpResponse.getAttribute(descriptor, "lineno"); //$NON-NLS-1$
stackLevel = DBGpResponse.getAttribute(descriptor, "level"); //$NON-NLS-1$
lineNo = Integer.parseInt(line);
qualifiedFile = DBGpUtils.getFilenameFromURIString(DBGpResponse.getAttribute(descriptor, "filename")); //$NON-NLS-1$
qualifiedFile = ((DBGpTarget) getDebugTarget()).mapToWorkspaceFileIfRequired(qualifiedFile);
String function = DBGpResponse.getAttribute(descriptor, "where"); //$NON-NLS-1$
// check to see if the file exists in the workspace
IResource fileFound = ResourcesPlugin.getWorkspace().getRoot().findMember(qualifiedFile);
if (fileFound != null) {
IFile file = (IFile) fileFound;
// get the file found in workspace and show project/file
String projectName = file.getProject().getName();
String projectRelPath = file.getProjectRelativePath().toString();
fileName = projectName + "/" + projectRelPath; //$NON-NLS-1$
} else {
// fileName = null;
fileName = qualifiedFile;
}
name = fileName + "." + function + "()"; //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Merges incoming variable. Merge is done by means of checking if related
* child variable existed in "one step back" state of a frame. If related
* variable existed, it is updated with the use of the most recent
* descriptor and returned instead of the incoming one.
*
* @param variable
* @param descriptor
* @return merged variable
*/
protected IVariable merge(IVariable variable) {
if (previousVariables == null)
return variable;
if (!(variable instanceof DBGpVariable))
return variable;
DBGpVariable incoming = (DBGpVariable) variable;
if (incoming.getFullName().isEmpty())
return incoming;
for (IVariable stored : previousVariables.locals) {
if (stored instanceof DBGpVariable) {
DBGpVariable previous = (DBGpVariable) stored;
if (previous.getFullName().equals(incoming.getFullName())) {
((DBGpVariable) stored).update(incoming.getDescriptor());
return stored;
}
}
}
return variable;
}
public synchronized IVariable findVariable(String varName) throws DebugException {
if (currentVariables == null) {
getVariables();
}
if (currentVariables != null) {
return (IVariable) currentVariables.findVariable(varName);
}
return null;
}
}