*/
package net.sourceforge.phpeclipse.xdebug.php.model;
+import java.net.MalformedURLException;
import java.net.URL;
-
-//import net.sourceforge.phpeclipse.xdebug.core.Base64;
-//import net.sourceforge.phpeclipse.xdebug.core.PHPDebugUtils;
-//import net.sourceforge.phpeclipse.xdebug.core.xdebug.ResponseListener.DebugResponse;
-//import net.sourceforge.phpeclipse.xdebug.core.xdebug.ResponseListener.XDebugResponse;
+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;
* @author Axel
*
*/
-public class XDebugStackFrame extends XDebugElement implements IStackFrame {
- private XDebugThread fThread;
-
- private int fId;
-
- private URL fName;
-
- private int fLineNumber;
- private int fLevel;
- private String fType;
- private String fWhere;
-
- private IVariable[] fVariables;
-
- private int fStepCount = 0;
-
+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) {
- super(thread == null ? null : (XDebugTarget) thread.getDebugTarget());
- fId = id;
- fThread = thread;
+ 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;
}
-
- public IVariable[] getVariables() throws DebugException {
- if (fVariables == null) {
- Node dfl = fTarget.getLocalVariables(fLevel);
- Node dfg = fTarget.getGlobalVariables(fLevel);
- parseVariable(dfl, dfg);
+
+ /**
+ * @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
+ * <ul>
+ * <li> false if the PHPValue is not a child of itself
+ * <li> true if the PHPValue is
+ * </ul>
+ */
+ 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:
+ * <ul>
+ * <li> It looks for new variables within the DBG variables list and
+ * adds them to the 'static' list.
+ * <li> 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).
+ * <li> 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.
+ * </ul>
+ *
+ * @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
+ }
}
- return fVariables;
+ // 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
+ }
+ }
}
-
- private void parseVariable(Node localVariables, Node globalVariables) {
- NodeList property = localVariables.getChildNodes();
-
+
+ /**
+ *
+ * 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));
+ 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));
+ 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 {
- /*return fVariables.length > 0;*/
- return true;
+ if (!fUpToDate) {
+ getVariables();
+ }
+
+ return (varList.size() > 0);
}
-
+
+ public int getLineNumber() {
+ return fLineNumber;
+ }
+
/* (non-Javadoc)
- * @see org.eclipse.debug.core.model.IStackFrame#getLineNumber()
+ *
*/
- public int getLineNumber() throws DebugException {
- return fLineNumber;
+ 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 {
- //String a = fName.getFile();
- //return fName.lastSegment().toString()+"::"+fWhere+ " line: "+ fLineNumber;
- return fName.toString()+"::"+fWhere+ " line: "+ fLineNumber;
+ 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.
*/
if (fName == null) {
return null;
}
+
IPath a = new Path(fName.getFile());
+
return a.lastSegment();
}
- public boolean isSameStackFrame(Object obj) {
- boolean isSameStackFrame = false;
-
- if (obj instanceof XDebugStackFrame) {
- XDebugStackFrame sf = (XDebugStackFrame)obj;
- //try {
- isSameStackFrame = sf.getSourceName().equals(getSourceName()) &&
- /*sf.getLineNumber() == getLineNumber() &&*/
- /*sf.getLevel() == getLevel() &&*/
- sf.getType().equals(getType()) &&
- sf.getWhere().equals(getWhere()); //&&
- /*sf.fId == fId;*/
- /*} catch (DebugException e) {
- }*/
+ public String getFullSourceName() {
+ if (fName == null) {
+ return null;
}
- return isSameStackFrame;
- }
-
- /* (non-Javadoc)
- * @see java.lang.Object#equals(java.lang.Object)
- */
- public boolean equals(Object obj) {
- if (obj instanceof XDebugStackFrame) {
- XDebugStackFrame sf = (XDebugStackFrame)obj;
- try {
- return sf.getSourceName().equals(getSourceName()) &&
- sf.getLineNumber() == getLineNumber() &&
- sf.getLevel() == getLevel() &&
- sf.getType().equals(getType()) &&
- sf.getWhere().equals(getWhere());
-/* sf.getType() == getType() &&
- sf.getWhere() == getWhere() &&*/
- /*sf.fId == fId;*/
- } catch (DebugException e) {
- }
- }
+ IPath a = new Path(fName.getFile());
- return false;
+ return a.toString ();
}
-
- /* (non-Javadoc)
- * @see java.lang.Object#equals(java.lang.Object)
- */
- public boolean equalsOld(Object obj) {
+
+ public boolean isSameStackFrame(Object obj) {
+ boolean isSameStackFrame = false;
+
if (obj instanceof XDebugStackFrame) {
- XDebugStackFrame sf = (XDebugStackFrame)obj;
- try {
- return sf.getSourceName().equals(getSourceName()) &&
- sf.getLineNumber() == getLineNumber() &&
- sf.fId == fId;
- } catch (DebugException e) {
- }
- }
- return false;
- }
-
- /* (non-Javadoc)
- * @see java.lang.Object#hashCode()
- */
- public int hashCode() {
-// return getSourceName().hashCode() + fId;
- return getSourceName().hashCode() + fLevel;
- }
-
- /**
- *
- * @return this stack frame's unique identifier within its thread
- */
- protected int getIdentifier() {
- return fId;
- }
+ XDebugStackFrame sf = (XDebugStackFrame) obj;
+ isSameStackFrame = sf.getSourceName ().equals (getSourceName ()) &&
+ sf.getType().equals (getType ()) &&
+ sf.getWhere().equals (getWhere ());
+ }
- public void setFullName(URL name) {
- fName = name;
+ return isSameStackFrame;
}
-
public URL getFullName() {
return fName;
}
-
+
+ public void setFullName (URL name) {
+ fName = name;
+ }
public int getLevel() {
return fLevel;
}
public void setLevel(int level) {
- fLevel = level;
- fId = level;
+ this.fLevel = level;
}
+
public String getType() {
return fType;
}
- public void setType(String type) {
- fType = type;
- }
-
public String getWhere() {
return fWhere;
}
- public void setWhere(String where) {
- fWhere = where;
+ 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 void setLineNumber(int newlineNumber) {
- fLineNumber = newlineNumber;
+ 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
+ * <ul>
+ * <li> -1 if the index of this stackframe is less.
+ * <li> 0 if the index of both stackframes are equal (should no happen).
+ * <li> 1 if the index of this stackframe is greater.
+ * </ul>
+ */
+ 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;
}
-}
\ No newline at end of file
+}