1) Improvements for the XDebug plugin.
[phpeclipse.git] / net.sourceforge.phpeclipse.xdebug.core / src / net / sourceforge / phpeclipse / xdebug / php / model / XDebugStackFrame.java
index 4cacdd9..ece1a54 100644 (file)
@@ -8,10 +8,14 @@ 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;
@@ -24,75 +28,262 @@ import org.w3c.dom.NodeList;
  * @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, String type, int lineNumber, String where, /*URL*/String filename) {
-               super(thread == null ? null : (XDebugTarget) thread.getDebugTarget());
-               
-               fId = id;
-               fLevel = id;
-               fThread = thread;
-               fType = type;
-               fLineNumber = lineNumber;
-               fWhere = where;
-               
+               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);
+                       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 = ((XDebugTarget) getDebugTarget()).getLocalVariables(fLevel);
-                       Node dfg = ((XDebugTarget) getDebugTarget()).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?
+                                                       valOld.setValueString(valNew.getValueString()); // Yes, set the 'static' value (variable) to 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.
+        */
 
-               return fVariables;
+       public synchronized IVariable[] getVariables() throws DebugException {
+               /* always read variables, poor performance
+                * but this fix bug #680.
+                * need to investigate on.
+                */
+
+        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()]);
        }
-       
-       private void parseVariable(Node localVariables, Node globalVariables) {
-               NodeList property = localVariables.getChildNodes();
-               
+
+       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));
@@ -105,173 +296,228 @@ public class XDebugStackFrame  extends XDebugElement implements IStackFrame {
                        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 {
-               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.
         */
@@ -279,91 +525,53 @@ public class XDebugStackFrame  extends XDebugElement implements IStackFrame {
                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) {
-                       }
+                       XDebugStackFrame sf = (XDebugStackFrame) obj;
+
+                       isSameStackFrame = sf.getSourceName ().equals (getSourceName ()) &&
+                                              sf.getType().equals (getType ()) &&
+                                              sf.getWhere().equals (getWhere ());
                }
-               return false;
-       }
-       
-       /* (non-Javadoc)
-        * @see java.lang.Object#hashCode()
-        */
-       public int hashCode() {
-               return getSourceName().hashCode() + fLevel;
+
+               return isSameStackFrame;
        }
-       
-       /**
-        * 
-        * @return this stack frame's unique identifier within its thread
-        */
-       /*protected int getIdentifier() {
-               return fId;
-       }*/
 
        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;
        }
@@ -375,4 +583,60 @@ public class XDebugStackFrame  extends XDebugElement implements IStackFrame {
        public boolean setVariableValue(XDebugVariable variable, String expression)  throws DebugException {
                return ((XDebugTarget) getDebugTarget()).setVarValue("$" + variable.getName(), expression);
        }
-}
\ No newline at end of file
+
+    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
+        * <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;
+       }
+}