1) Made getVariables and evalBlock methods in PHPDBGInterface synchronized (At least...
[phpeclipse.git] / net.sourceforge.phpeclipse.debug.core / src / net / sourceforge / phpdt / internal / debug / core / model / PHPStackFrame.java
index a819140..dcd372e 100644 (file)
@@ -6,11 +6,12 @@ which accompanies this distribution, and is available at
 http://www.eclipse.org/legal/cpl-v10.html
 
 Contributors:
-    IBM Corporation - Initial implementation
-    Vicente Fernando - www.alfersoft.com.ar
+       IBM Corporation - Initial implementation
+       Vicente Fernando - www.alfersoft.com.ar
 **********************************************************************/
 package net.sourceforge.phpdt.internal.debug.core.model;
 
+import java.util.Arrays;
 import java.util.Vector;
 
 import net.sourceforge.phpdt.internal.debug.core.PHPDBGProxy;
@@ -39,7 +40,11 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
        private int                     modno;              //
        private PHPVariable[]   variables;          // The array of variables TODO: better introduce a vector?
        private Vector          varList  = new Vector ();
-       private String                  description;        //
+       private String                  description;        // The source file name with the full path on target/remote system
+       private boolean         fUpToDate;          // Indicates whether the variable list within this stackframe is
+                                                                                               // up-to-date
+       private boolean         fAvailable;         // Needed when updating the stackframe list, shows whether the stackframe
+                                                                                               // is within the list which was received from dbg
 
        /**
         *
@@ -59,6 +64,7 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
                this.thread      = thread;
                this.description = desc;
                this.modno               = modno;
+               this.fUpToDate   = false;
        }
 
        /**
@@ -75,6 +81,7 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
                this.index              = index;
                this.file               = file;
                this.thread     = thread;
+               this.fUpToDate  = false;
        }
 
        /**
@@ -93,6 +100,203 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
 
        /**
         *
+        */
+       private void setUpToDate (boolean upToDate) {
+               fUpToDate = upToDate;
+       }
+
+       /**
+        *
+        */
+       private boolean isUpToDate () {
+               return fUpToDate;
+       }
+
+       /**
+        *
+        */
+       public void setAvailable (boolean available) {
+               fAvailable = available;
+       }
+
+       /**
+        *
+        */
+       public boolean isAvailable () {
+               return fAvailable;
+       }
+
+
+       /**
+        * @see IAdaptable#getAdapter(Class)
+        */
+       public Object getAdapter(Class adapter) {
+               if (adapter == PHPStackFrame.class) {
+                       return this;
+               }
+
+               return super.getAdapter(adapter);
+       }
+
+       /**
+        *
+        *
+        */
+       private void resetHasChangedInfo (Vector varList) {
+               int             n;
+               PHPVariable var;
+               PHPValue    val;
+
+               for (n = 0; n < varList.size (); n++) {                  // For every variable in 'DBG list'
+                       var = (PHPVariable) varList.get (n);                    // Get the variable
+                       val = (PHPValue) 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.
+        *
+        * @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 (PHPVariable var) {
+               PHPVariable parentVar;
+               PHPValue    val;
+
+               val = (PHPValue) 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)
+       {
+               PHPVariable     varOld;                                                                                 // The variable from the 'static' list
+               PHPVariable varNew;                                                                                     // The variable from the DBG list
+               PHPValue    valOld;                                                                                     // The value of the current variable from 'static' list
+               PHPValue    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 = (PHPVariable) varListNew.get (n);                              // Get the DBG variable
+
+                       for (o = 0; o < varListOld.size (); o++) {                              // For every variable in static list
+                               varOld = (PHPVariable) varListOld.get (o);                      // Get the static variable
+
+                               if (varNew.getName ().equals (varOld.getName ())) { // Did we found the variable within the 'static' list?
+                                       valOld = (PHPValue) varOld.getValue ();         // Get the value from 'static'
+                                       valNew = (PHPValue) 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 ());
+                                                       }
+                                               }
+                                               else 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
+                                               }
+                                               //else {
+                                               //      varOld.setValueChanged (false);                                     // Reset the 'has changed' flag
+                                               //}
+                                       }
+                                       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 = (PHPVariable) varListOld.get (o);                                                      // Get the static variable
+
+                       for (n = 0; n < varListNew.size (); n++) {                                                      // For all variables in 'DBG' list
+                               varNew = (PHPVariable) 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
+                                o -= 1;                                                        // Adjust the 'static' list index
+                       }
+               }
+       }
+
+
+       /**
+        *
         * This function returns the array of PHPVariables for this stackframe
         * The PHPVariables should not change (newly build up) between two steps
         * (or breaks).
@@ -104,13 +308,14 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
         * @return The array of PHPVariables for this stackframe.
         */
        public IVariable[] getVariables() throws DebugException {
-               //PHPVariable[] variablesNew;                                 // The intermediate storage of the variable array we get from DBG proxy
-
-               //variablesNew = this.getPHPDBGProxy ().readVariables (this); // Get the variable array from DBG proxy
-               //variables    = variablesNew;                                // Store the array the stackframes member variable
-               varList   = this.getPHPDBGProxy ().readVariables (this);
+               if (!isUpToDate ()) {
+                       resetHasChangedInfo (varList);
+                       updateVariableList (varList, this.getPHPDBGProxy ().readVariables (this));
+                       setUpToDate (true);
 
-               variables = (PHPVariable[]) varList.toArray (new PHPVariable[varList.size ()]);
+                       variables = (PHPVariable[]) varList.toArray (new PHPVariable[varList.size ()]);
+                       Arrays.sort(variables, new PHPVariableComparator());
+               }
 
                return variables;                                           // Give the array back to user interface
        }
@@ -129,10 +334,12 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
 
                        try {
                                if (value.hasVariables ()) {                                            // Does the variable/value have children
-                                       variable = findVariable (value.getChildVariables (), varname);
+                                       if (!hasRecursion (variable)) {                                 // Don't follow recursive variable/values
+                                               variable = findVariable (value.getChildVariables (), varname);
 
-                                       if (variable != null) {
-                                               return variable;
+                                               if (variable != null) {
+                                                       return variable;
+                                               }
                                        }
                                }
                                else if ((variable.getName ()).equals (varname)) {      //
@@ -154,6 +361,14 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
         * @return
         */
        public IVariable findVariable (String s) throws DebugException {
+               if (!isUpToDate ()) {
+                       resetHasChangedInfo (varList);
+                       updateVariableList (varList, this.getPHPDBGProxy ().readVariables (this));
+                       setUpToDate (true);
+
+                       variables = (PHPVariable[]) varList.toArray (new PHPVariable[varList.size ()]);
+               }
+
                return (findVariable (varList, s));                                                     // Prefix the variable name with $
        }
 
@@ -161,11 +376,8 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
         *
         */
        public boolean hasVariables () throws DebugException {
-               if (variables == null) {                                                                        // Do we have a variables array?
-                       return false;                                                                                   // No
-               }
-
-               return variables.length > 0;                                // Is there something within the array?
+               return true;
+               // return (varList.size () > 0);
        }
 
        public int getLineNumber() {
@@ -187,10 +399,20 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
        }
 
        public String getName() {
-               if(!this.getDescription().equals(""))
-                       return this.getDescription() + " [line: " + this.getLineNumber() + "]";
-               else
-                       return this.getFileName() + " [line: " + this.getLineNumber() + "]";
+               StringBuffer name = new StringBuffer();
+
+               if (!this.getDescription().equals ("")) {
+                       name.append (this.getDescription ());
+               }
+               else {
+                       name.append (this.getFileName ());
+               }
+
+               name.append (" [line ");
+               name.append (this.getLineNumber ());
+               name.append ("]");
+
+               return name.toString();
        }
 
        public String getFileName() {
@@ -247,11 +469,20 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
        public void stepInto () throws DebugException {
                DebugEvent      ev;
 
-        thread.prepareForResume (DebugEvent.STEP_INTO);             // Don't know why, but this is necessary
-               this.getPHPDBGProxy ().readStepIntoEnd (PHPStackFrame.this);
+               setUpToDate (false);
 
-               ev = new DebugEvent (this.getThread (), DebugEvent.RESUME, DebugEvent.STEP_INTO);
-               DebugPlugin.getDefault().fireDebugEventSet (new DebugEvent[] { ev });
+               thread.prepareForResume (DebugEvent.STEP_INTO);             // Don't know why, but this is necessary
+               this.getPHPDBGProxy ().readStepIntoEnd (PHPStackFrame.this);
+               
+        // Commented out sending the RESUME event because it was already sent by prepareForResume.
+        // The second RESUME event leads only to a little flickering within the variables view.
+        // It is also not clear why this event was necessary in eclipse < 3.2
+        // Also sending a SUSPEND event here leads to a total rebuild of the variables view.
+        // (eclipse 3.2 has a build in timeout of 500 ms which leads to a auto suspend, with
+        // no flickering... but why???)
+        // 
+               //ev = new DebugEvent (this.getThread (), DebugEvent.RESUME, DebugEvent.STEP_INTO);
+               //DebugPlugin.getDefault().fireDebugEventSet (new DebugEvent[] { ev });
        }
 
        /**
@@ -260,11 +491,15 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
        public void stepOver () throws DebugException {
                DebugEvent      ev;
 
-        thread.prepareForResume (DebugEvent.STEP_OVER);
+               setUpToDate (false);
+
+               thread.prepareForResume (DebugEvent.STEP_OVER);
                this.getPHPDBGProxy ().readStepOverEnd (PHPStackFrame.this) ;
 
-               ev = new DebugEvent (this.getThread (), DebugEvent.RESUME, DebugEvent.STEP_OVER);
-               DebugPlugin.getDefault ().fireDebugEventSet (new DebugEvent[] { ev });
+        // See comment within the previous stepInto method.
+        // 
+               //ev = new DebugEvent (this.getThread (), DebugEvent.RESUME, DebugEvent.STEP_OVER);
+               //DebugPlugin.getDefault ().fireDebugEventSet (new DebugEvent[] { ev });
        }
 
        /**
@@ -273,7 +508,9 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
        public void stepReturn () throws DebugException {
                DebugEvent      ev;
 
-        thread.prepareForResume (DebugEvent.STEP_RETURN);
+               setUpToDate (false);
+
+               thread.prepareForResume (DebugEvent.STEP_RETURN);
                this.getPHPDBGProxy ().readStepReturnEnd (PHPStackFrame.this) ;
 
                ev = new DebugEvent (this.getThread (), DebugEvent.RESUME, DebugEvent.STEP_RETURN);
@@ -294,6 +531,7 @@ public class PHPStackFrame extends PHPDebugElement implements IStackFrame, Compa
        }
 
        public void resume() throws DebugException {
+               setUpToDate (false);
                this.getThread().resume();
        }