/*
* Created on 23.11.2004
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
package net.sourceforge.phpeclipse.xdebug.php.model;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Vector;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
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.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.core.model.IVariable;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* @author PHPeclipse team
* @author Axel
*
*/
public class XDebugStackFrame extends XDebugElement implements IStackFrame, Comparable {
private XDebugThread fThread;
private URL fName;
private int fLineNumber;
private int fLevel;
private String fType;
private String fWhere;
private IVariable[] fVariables;
private int fStepCount = 0;
private boolean fUpToDate = false;
private Vector varList; // Variables list
private boolean fAvailable; // Needed when updating the stackframe list, shows whether the stackframe
// is within the list which was received from XDebug
/**
* Constructs a stack frame in the given thread with the given
* frame data.
*
* @param thread
* @param data frame data
* @param id stack frame id (0 is the bottom of the stack)
*/
public XDebugStackFrame(XDebugThread thread, int id, String type, int lineNumber, String where, /*URL*/String filename) {
super(/*thread == null ? null : */(XDebugTarget) thread.getDebugTarget());
this.fLevel = id;
this.fThread = thread;
this.fType = type;
this.fLineNumber = lineNumber;
this.fWhere = where;
this.varList = new Vector();
try {
fName = new URL(filename);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public void incrementStepCounter() {
fStepCount++;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IStackFrame#getThread()
*/
public IThread getThread() {
return fThread;
}
/**
* @see IAdaptable#getAdapter(Class)
*/
public Object getAdapter(Class adapter) {
if (adapter == XDebugStackFrame.class) {
return this;
}
return super.getAdapter(adapter);
}
public IDebugTarget getDebugTarget() {
return this.getThread().getDebugTarget();
}
/**
*
*/
private void resetHasChangedInfo(Vector varList) {
int n;
XDebugVariable var;
XDebugAbstractValue val;
for (n = 0; n < varList.size (); n++) { // For every variable in 'DBG list'
var = (XDebugVariable) varList.get(n); // Get the variable
val = (XDebugAbstractValue) var.getValue(); // Get the variable's value
try {
if (val.hasVariables()) { // Do we have other variables within the value
if (!hasRecursion(var)) { // Is this variable (value) branch recursive?
resetHasChangedInfo(val.getChildVariables()); // No, go into branch
}
}
} catch (DebugException e) { // That's, because of the hasVariables method
}
var.setValueChanged(false); // Reset the 'has changed' flag
}
}
/**
* Go up the tree of PHPVariables
* look whether the PHPValue is a reference to a parent PHPValue
*
* TODO Check where this recursion can come from.
* Whether this back reference is legal or a bug.
*
* Typically $GLOBALS contains $GLOBALS
*
* @param var
* @return
*
* - false if the PHPValue is not a child of itself
*
- true if the PHPValue is
*
*/
private boolean hasRecursion(XDebugVariable var) {
XDebugVariable parentVar;
XDebugAbstractValue val;
val = (XDebugAbstractValue) var.getValue(); // Get the PHPValue from the current PHPVariable
while (var != null) { // As long as we have PHPVariable
parentVar = var.getParent(); // Get the parent PHPVariable
if (parentVar != null) { // Is there a parent?
if (parentVar.getValue().equals(val)) { // Get the PHPValue for the parent PHPVariable and check
// whether it is the same
return true; // Return, if we have recursion
}
}
var = parentVar;
}
return false; // No recursion found
}
/**
* This method updates the 'static' variables list.
* It does a replication between the 'static' list (the variable list which
* is a member of this DBG interface object) and the DBG variable list
* (the list of variables which is received from PHP via DBG with the current suspend)
* Replication is done in the following way:
*
* - It looks for new variables within the DBG variables list and
* adds them to the 'static' list.
*
- It looks for changed variables copies the current value to the variable within
* the 'static list' and mark these variables as 'hasChanged' (which uses the UI
* for showing the variable with a different color).
*
- It looks for variables within the 'static' list, and removes them
* from the 'static' list in case the do not appear within the DBG list.
*
*
* @param varListOld The 'static' list of variables which are to be updated.
* @param varListNew The new list of (current) variables from DBG.
*/
private void updateVariableList(Vector varListOld, Vector varListNew) {
XDebugVariable varOld; // The variable from the 'static' list
XDebugVariable varNew; // The variable from the DBG list
XDebugAbstractValue valOld; // The value of the current variable from 'static' list
XDebugAbstractValue valNew; // The value of the current variable from DBG list
int n; // Index for the DBG list
int o; // Index for the static list
// Add the variables (and childs) to the static list if they are new
// and update the values of variables which are already existend within
// the 'static' list.
for (n = 0; n < varListNew.size(); n++) { // For every variable in 'DBG list'
varNew = (XDebugVariable) varListNew.get(n); // Get the DBG variable
for (o = 0; o < varListOld.size(); o++) { // For every variable in static list
varOld = (XDebugVariable) varListOld.get(o); // Get the static variable
if (varNew.getName().equals(varOld.getName())) { // Did we found the variable within the 'static' list?
valOld = (XDebugAbstractValue) varOld.getValue(); // Get the value from 'static'
valNew = (XDebugAbstractValue) varNew.getValue(); // Get the value from DBG
try {
if (valOld.hasVariables() || // If the 'static' value has child variables
valNew.hasVariables()) { // or if the DBG value has child variables
if (!hasRecursion (varOld) &&
!hasRecursion (varNew)) { // Both branches should not have a recursion
updateVariableList (
valOld.getChildVariables(), // Update the variable list for the child variables
valNew.getChildVariables());
}
}
if (!valOld.getValueString().equals(
valNew.getValueString())) { // Has the value changed?
varOld.setValue (varNew.getValue ()); // Set the new value
varOld.setValueChanged (true); // and set the 'has changed' flag, so that the variable view
// could show the user the changed status with a different
// color
}
} catch (DebugException e) { // That's, because of the hasVariables method
}
break; // Found the variable,
}
}
if (o == varListOld.size()) { // Did we found the variable within the static list?
varListOld.add(varNew); // No, then add the DBG variable to the static list
}
}
// Look for the variables we can remove from the 'static' list
for (o = 0; o < varListOld.size(); o++) { // For every variable in 'static' list
varOld = (XDebugVariable) varListOld.get(o); // Get the static variable
for (n = 0; n < varListNew.size(); n++) { // For all variables in 'DBG' list
varNew = (XDebugVariable) varListNew.get(n); // Get the variable from the 'DBG' list
if (varNew.getName().equals(varOld.getName())) { // Did we found the 'static' list variable within the 'DBG' list?
break; // Yes we found the variable, then leave the loop
}
}
if (n == varListNew.size()) { // Did not find the 'static' list variable within the 'DBG' list?
varListOld.remove(o--); // then remove the 'static' list variable from list
}
}
}
/**
*
* This function returns the array of PHPVariables for this stackframe
* The PHPVariables should not change (newly build up) between two steps
* (or breaks).
* A PHPVariable with the same name but with different object ID is
* handled as a new variable.
*
* @return The array of PHPVariables for this stackframe.
*/
public synchronized IVariable[] getVariables() throws DebugException {
if (!fUpToDate) {
Node dfl = ((XDebugTarget) getDebugTarget()).getLocalVariables(fLevel);
Node dfg = ((XDebugTarget) getDebugTarget()).getGlobalVariables(fLevel);
parseVariable(dfl, dfg);
Vector newVariables = new Vector (Arrays.asList(fVariables));
resetHasChangedInfo (varList);
updateVariableList (varList, newVariables);
fUpToDate = true;
Collections.sort (varList, new XDebugVariableComparator ());
}
return (IVariable[]) varList.toArray (new IVariable[varList.size()]);
}
/**
*
* @param localVariables
* @param globalVariables
* @throws DebugException
*/
private void parseVariable(Node localVariables, Node globalVariables) throws DebugException {
NodeList property = localVariables.getChildNodes();
NodeList propertyGlobal = globalVariables.getChildNodes();
fVariables = new IVariable[property.getLength() + propertyGlobal.getLength()];
int length = property.getLength();
for (int i = 0; i < length; i++) {
XDebugVariable var = new XDebugVariable(this, property.item(i), null);
fVariables[i] = var;
}
int globalLength = propertyGlobal.getLength();
for (int k = 0; k < globalLength; k++) {
XDebugVariable var = new XDebugVariable(this, propertyGlobal.item(k), null);
fVariables[k + length] = var;
}
}
/**
*
*/
private XDebugVariable findVariable(Vector varList, String varname) {
XDebugVariable variable;
XDebugAbstractValue value;
int i;
for (i = 0; i < varList.size(); i++) { // For all variables
variable = (XDebugVariable) varList.get(i); // Get the variable
value = (XDebugAbstractValue) variable.getValue(); // Get the value of the variable
try {
if (value.hasVariables()) { // Does the variable/value have children
if (!hasRecursion(variable)) { // Don't follow recursive variable/values
XDebugVariable var = findVariable(value.getChildVariables(), varname);
if (var != null) {
return var;
}
}
}
if (variable.getName().equals(varname)) {
return variable;
}
} catch (DebugException e) { // That's, because of the hasVariables method
}
}
return null;
}
/**
* This method is called from the UI (e.g. from PHPDebugHover
* to find the variable the mouse is pointing to)
*
* @param s The variable name we are looking for.
* @return
*/
public IVariable findVariable(String s) throws DebugException {
if (!fUpToDate) {
getVariables();
}
return (findVariable(varList, s)); // Prefix the variable name with $
}
/*public void evaluateChange(IStackFrame OldStackFrame) throws DebugException {
IVariable[] OldVariable = ((XDebugStackFrame) OldStackFrame).getVariables();
for (int i = 0; i < fVariables.length; i++) {
((XDebugVariable) fVariables[i]).setChange(OldVariable[i]);
}
}*/
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IStackFrame#hasVariables()
*/
public boolean hasVariables() throws DebugException {
if (!fUpToDate) {
getVariables();
}
return (varList.size() > 0);
}
public int getLineNumber() {
return fLineNumber;
}
/* (non-Javadoc)
*
*/
public void setLineNumber(int line) {
fLineNumber = line;
}
/* (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#getCharEnd()
*/
public int getCharEnd() throws DebugException {
return -1;
}
/* (non-Javadoc)fName
* @see org.eclipse.debug.core.model.IStackFrame#getName()
*/
public String getName() throws DebugException {
return fName.toString() + "::" + fWhere + " line: " + fLineNumber;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IStackFrame#getRegisterGroups()
*/
public IRegisterGroup[] getRegisterGroups() throws DebugException {
return null;
}
/* (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.IStep#canStepInto()
*/
public boolean canStepInto() {
return fThread.canStepInto();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IStep#canStepOver()
*/
public boolean canStepOver() {
return fThread.canStepOver();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IStep#canStepReturn()
*/
public boolean canStepReturn() {
return fThread.canStepReturn();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IStep#isStepping()
*/
public boolean isStepping() {
return fThread.isStepping();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IStep#stepInto()
*/
public void stepInto() throws DebugException {
fThread.stepInto();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IStep#stepOver()
*/
public void stepOver() throws DebugException {
fThread.stepOver();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IStep#stepReturn()
*/
public void stepReturn() throws DebugException {
fThread.stepReturn();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ISuspendResume#canResume()
*/
public boolean canResume() {
return fThread.canResume();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ISuspendResume#canSuspend()
*/
public boolean canSuspend() {
return fThread.canSuspend();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ISuspendResume#isSuspended()
*/
public boolean isSuspended() {
return fThread.isSuspended();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ISuspendResume#resume()
*/
public void resume() throws DebugException {
fThread.resume();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ISuspendResume#suspend()
*/
public void suspend() throws DebugException {
fThread.suspend();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ITerminate#canTerminate()
*/
public boolean canTerminate() {
return fThread.canTerminate();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ITerminate#isTerminated()
*/
public boolean isTerminated() {
return fThread.isTerminated();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ITerminate#terminate()
*/
public void terminate() throws DebugException {
fThread.terminate();
}
/**
* Returns the name of the source file this stack frame is associated
* with.
*
* @return the name of the source file this stack frame is associated
* with. If the file associated with this frame does not exists, it returns null.
*/
public String getSourceName() {
if (fName == null) {
return null;
}
IPath a = new Path(fName.getFile());
return a.lastSegment();
}
public String getFullSourceName() {
if (fName == null) {
return null;
}
IPath a = new Path(fName.getFile());
return a.toString ();
}
public boolean isSameStackFrame(Object obj) {
boolean isSameStackFrame = false;
if (obj instanceof XDebugStackFrame) {
XDebugStackFrame sf = (XDebugStackFrame) obj;
isSameStackFrame = sf.getSourceName ().equals (getSourceName ()) &&
sf.getType().equals (getType ()) &&
sf.getWhere().equals (getWhere ());
}
return isSameStackFrame;
}
public URL getFullName() {
return fName;
}
public void setFullName (URL name) {
fName = name;
}
public int getLevel() {
return fLevel;
}
public void setLevel(int level) {
this.fLevel = level;
}
public String getType() {
return fType;
}
public String getWhere() {
return fWhere;
}
public boolean setVariableValue(XDebugVariable variable, String expression) throws DebugException {
return ((XDebugTarget) getDebugTarget()).setVarValue(variable.getNameFull(), expression);
}
public Node eval(String expression) throws DebugException {
return ((XDebugTarget) getDebugTarget()).eval(expression);
}
/**
*
*/
public void setAvailable(boolean available) {
fAvailable = available;
}
/**
*
*/
public boolean isAvailable() {
return fAvailable;
}
public void setUpToDate (boolean bValue) {
this.fUpToDate = bValue;
}
public void setDescription(String desc) {
this.fWhere = desc;
}
public String getDescription() {
return this.fWhere;
}
/**
* This function is needed when sorting the stackframes by their index numbers.
*
* @param obj The stackframe which this one is compared to.
* @return
*
* - -1 if the index of this stackframe is less.
*
- 0 if the index of both stackframes are equal (should no happen).
*
- 1 if the index of this stackframe is greater.
*
*/
public int compareTo(Object obj) {
if (!(obj instanceof XDebugStackFrame)) {
throw new IllegalArgumentException ("A PHPStackFrame can only be compared with another PHPStackFrame");
}
int frameLevel = ((XDebugStackFrame) obj).getLevel ();
if (fLevel < frameLevel) {
return -1;
} else if (fLevel > frameLevel) {
return 1;
}
return 0;
}
}