1) Improvements for the XDebug plugin.
[phpeclipse.git] / net.sourceforge.phpeclipse.xdebug.core / src / net / sourceforge / phpeclipse / xdebug / php / model / XDebugStackFrame.java
1 /*
2  * Created on 23.11.2004
3  *
4  * TODO To change the template for this generated file go to
5  * Window - Preferences - Java - Code Style - Code Templates
6  */
7 package net.sourceforge.phpeclipse.xdebug.php.model;
8
9 import java.net.MalformedURLException;
10 import java.net.URL;
11 import java.util.Arrays;
12 import java.util.Collections;
13 import java.util.Vector;
14
15 import org.eclipse.core.runtime.IPath;
16 import org.eclipse.core.runtime.Path;
17 import org.eclipse.debug.core.DebugException;
18 import org.eclipse.debug.core.model.IDebugTarget;
19 import org.eclipse.debug.core.model.IRegisterGroup;
20 import org.eclipse.debug.core.model.IStackFrame;
21 import org.eclipse.debug.core.model.IThread;
22 import org.eclipse.debug.core.model.IVariable;
23 import org.w3c.dom.Node;
24 import org.w3c.dom.NodeList;
25
26 /**
27  * @author PHPeclipse team
28  * @author Axel
29  *
30  */
31 public class XDebugStackFrame  extends XDebugElement implements IStackFrame, Comparable {
32         private XDebugThread    fThread;
33         private URL                     fName;
34         private int                     fLineNumber;
35         private int                     fLevel;
36         private String                  fType;
37         private String                  fWhere;
38         private IVariable[]     fVariables;
39         private int                     fStepCount = 0;
40         private boolean         fUpToDate = false;
41         private Vector                  varList;                                // Variables list
42         private boolean                 fAvailable;                     // Needed when updating the stackframe list, shows whether the stackframe
43                                                                                                         // is within the list which was received from XDebug
44
45         /**
46          * Constructs a stack frame in the given thread with the given
47          * frame data.
48          *
49          * @param thread
50          * @param data frame data
51          * @param id stack frame id (0 is the bottom of the stack)
52          */
53         public XDebugStackFrame(XDebugThread thread, int id, String type, int lineNumber, String where, /*URL*/String filename) {
54                 super(/*thread == null ? null : */(XDebugTarget) thread.getDebugTarget());
55
56                 this.fLevel       = id;
57                 this.fThread      = thread;
58                 this.fType        = type;
59                 this.fLineNumber  = lineNumber;
60                 this.fWhere       = where;
61                 this.varList      = new Vector();
62
63                 try {
64                         fName = new URL(filename);
65                 } catch (MalformedURLException e) {
66                         e.printStackTrace();
67                 }
68         }
69
70         public void incrementStepCounter() {
71                 fStepCount++;
72         }
73
74         /* (non-Javadoc)
75          * @see org.eclipse.debug.core.model.IStackFrame#getThread()
76          */
77         public IThread getThread() {
78                 return fThread;
79         }
80
81     /**
82      * @see IAdaptable#getAdapter(Class)
83      */
84     public Object getAdapter(Class adapter) {
85         if (adapter == XDebugStackFrame.class) {
86             return this;
87         }
88
89         return super.getAdapter(adapter);
90     }
91
92     public IDebugTarget getDebugTarget() {
93         return this.getThread().getDebugTarget();
94     }
95
96         /**
97          *
98          */
99         private void resetHasChangedInfo(Vector varList) {
100                 int                     n;
101                 XDebugVariable          var;
102                 XDebugAbstractValue     val;
103
104                 for (n = 0; n < varList.size (); n++) {                                         // For every variable in 'DBG list'
105                         var = (XDebugVariable) varList.get(n);                                  // Get the variable
106                         val = (XDebugAbstractValue) var.getValue();                     // Get the variable's value
107
108                         try {
109                                 if (val.hasVariables()) {                                                       // Do we have other variables within the value
110                                         if (!hasRecursion(var)) {                                               // Is this variable (value) branch recursive?
111                                                 resetHasChangedInfo(val.getChildVariables()); //  No, go into branch
112                                         }
113                                 }
114                         } catch (DebugException e) {                                                    // That's, because of the hasVariables method
115                         }
116
117                         var.setValueChanged(false);                                                     // Reset the 'has changed' flag
118                 }
119         }
120
121         /**
122          * Go up the tree of PHPVariables
123          * look whether the PHPValue is a reference to a parent PHPValue
124          *
125          * TODO Check where this recursion can come from.
126          * Whether this back reference is legal or a bug.
127          *
128          * Typically $GLOBALS contains $GLOBALS
129          *
130          * @param var
131          * @return
132          * <ul>
133          * <li> false if the PHPValue is not a child of itself
134          * <li> true if the PHPValue is
135          * </ul>
136          */
137         private boolean hasRecursion(XDebugVariable var) {
138                 XDebugVariable                  parentVar;
139                 XDebugAbstractValue     val;
140
141                 val = (XDebugAbstractValue) var.getValue();                                             // Get the PHPValue from the current PHPVariable
142
143                 while (var != null) {                                                                           // As long as we have PHPVariable
144                         parentVar = var.getParent();                                                    // Get the parent PHPVariable
145
146                         if (parentVar != null) {                                                                // Is there a parent?
147                                 if (parentVar.getValue().equals(val)) {                         // Get the PHPValue for the parent PHPVariable and check
148                                                                                                                                         // whether it is the same
149                                         return true;                                                                    // Return, if we have recursion
150                                 }
151                         }
152
153                         var = parentVar;
154                 }
155
156                 return false;                                                                                           // No recursion found
157         }
158
159         /**
160          * This method updates the 'static' variables list.
161          * It does a replication between the 'static' list (the variable list which
162          * is a member of this DBG interface object) and the DBG variable list
163          * (the list of variables which is received from PHP via DBG with the current suspend)
164          * Replication is done in the following way:
165          * <ul>
166          * <li> It looks for new variables within the DBG variables list and
167          *      adds them to the 'static' list.
168          * <li> It looks for changed variables copies the current value to the variable within
169          *              the 'static list' and mark these variables as 'hasChanged' (which uses the UI
170          *              for showing the variable with a different color).
171          * <li> It looks for variables within the 'static' list, and removes them
172          *              from the 'static' list in case the do not appear within the DBG list.
173          * </ul>
174          *
175          * @param varListOld The 'static' list of variables which are to be updated.
176          * @param varListNew The new list of (current) variables from DBG.
177          */
178         private void updateVariableList(Vector varListOld, Vector varListNew) {
179                 XDebugVariable          varOld;                                                                         // The variable from the 'static' list
180                 XDebugVariable          varNew;                                                                         // The variable from the DBG list
181                 XDebugAbstractValue valOld;                                                                             // The value of the current variable from 'static' list
182                 XDebugAbstractValue valNew;                                                                             // The value of the current variable from DBG list
183                 int                             n;                                                                                      // Index for the DBG list
184                 int                             o;                                                                                      // Index for the static list
185
186                 // Add the variables (and childs) to the static list if they are new
187                 //  and update the values of variables which are already existend within
188                 //  the 'static' list.
189
190                 for (n = 0; n < varListNew.size(); n++) {                                       // For every variable in 'DBG list'
191                         varNew = (XDebugVariable) varListNew.get(n);                    // Get the DBG variable
192
193                         for (o = 0; o < varListOld.size(); o++) {                               // For every variable in static list
194                                 varOld = (XDebugVariable) varListOld.get(o);            // Get the static variable
195
196                                 if (varNew.getName().equals(varOld.getName())) {                        // Did we found the variable within the 'static' list?
197                                         valOld = (XDebugAbstractValue) varOld.getValue();               // Get the value from 'static'
198                                         valNew = (XDebugAbstractValue) varNew.getValue();               // Get the value from DBG
199
200                                         try {
201                                                 if (valOld.hasVariables() ||                                                            // If the 'static' value has child variables
202                                                         valNew.hasVariables()) {                                                                //  or if the DBG value has child variables
203                                                         if (!hasRecursion(varOld) &&
204                                                             !hasRecursion(varNew)) {                                                    // Both branches should not have a recursion
205                                                                 updateVariableList(
206                                                                         valOld.getChildVariables(),             // Update the variable list for the child variables
207                                                                         valNew.getChildVariables());
208                                                         }
209                                                 }
210
211                                                 if (!valOld.getValueString().equals(
212                                                                 valNew.getValueString())) {             // Has the value changed?
213                                                         valOld.setValueString(valNew.getValueString()); // Yes, set the 'static' value (variable) to the new value
214                                                         varOld.setValueChanged(true);                   // and set the 'has changed' flag, so that the variable view
215                                                                                                                                         // could show the user the changed status with a different
216                                                                                                                                         // color
217                                                 }
218                                         } catch (DebugException e) {                                    // That's, because of the hasVariables method
219                                         }
220
221                                         break;                                                                                  // Found the variable,
222                                 }
223                         }
224
225                         if (o == varListOld.size()) {                                                   // Did we found the variable within the static list?
226                                 varListOld.add(varNew);                                                         //  No, then add the DBG variable to the static list
227                         }
228                 }
229
230                 // Look for the variables we can remove from the 'static' list
231
232                 for (o = 0; o < varListOld.size(); o++) {                                       // For every variable in 'static' list
233                         varOld = (XDebugVariable) varListOld.get(o);                    // Get the static variable
234
235                         for (n = 0; n < varListNew.size(); n++) {                               // For all variables in 'DBG' list
236                                 varNew = (XDebugVariable) varListNew.get(n);            // Get the variable from the 'DBG' list
237
238                                 if (varNew.getName().equals(varOld.getName())) {        // Did we found the 'static' list variable within the 'DBG' list?
239                                         break;                                                                                  // Yes we found the variable, then leave the loop
240                                 }
241                         }
242
243                         if (n == varListNew.size()) {                                                   // Did not find the 'static' list variable within the 'DBG' list?
244                                 varListOld.remove(o--);                                                         // then remove the 'static' list variable from list
245                         }
246                 }
247         }
248
249         /**
250          *
251          * This function returns the array of PHPVariables for this stackframe
252          * The PHPVariables should not change (newly build up) between two steps
253          * (or breaks).
254          * A PHPVariable with the same name but with different object ID is
255          * handled as a new variable.
256          *
257          * @return The array of PHPVariables for this stackframe.
258          */
259
260         public synchronized IVariable[] getVariables() throws DebugException {
261                 /* always read variables, poor performance
262                  * but this fix bug #680.
263                  * need to investigate on.
264                  */
265
266         if (!fUpToDate) {
267             Node dfl = ((XDebugTarget) getDebugTarget()).getLocalVariables(fLevel);
268             Node dfg = ((XDebugTarget) getDebugTarget()).getGlobalVariables(fLevel);
269             parseVariable(dfl, dfg);
270
271                     Vector newVariables = new Vector (Arrays.asList(fVariables));
272                         resetHasChangedInfo (varList);
273                         updateVariableList (varList, newVariables);
274                         fUpToDate = true;
275                         Collections.sort (varList, new XDebugVariableComparator ());
276         }
277
278                 return (IVariable[]) varList.toArray (new IVariable[varList.size()]);
279         }
280
281         private void parseVariable(Node localVariables, Node globalVariables) throws DebugException {
282                 NodeList property       = localVariables.getChildNodes();
283                 NodeList propertyGlobal = globalVariables.getChildNodes();
284
285                 fVariables = new IVariable[property.getLength() + propertyGlobal.getLength()];
286
287                 int length = property.getLength();
288                 for (int i = 0; i < length; i++) {
289                         XDebugVariable var = new XDebugVariable(this, property.item(i));
290                         fVariables[i] = var;
291                 }
292
293                 int globalLength = propertyGlobal.getLength();
294                 for (int k = 0; k < globalLength; k++) {
295                         XDebugVariable var = new XDebugVariable(this, propertyGlobal.item(k));
296                         fVariables[k + length] = var;
297                 }
298         }
299
300         /**
301          *
302          */
303         private XDebugVariable findVariable(Vector varList, String varname) {
304                 XDebugVariable                  variable;
305                 XDebugAbstractValue     value;
306                 int                                     i;
307
308                 for (i = 0; i < varList.size(); i++) {                                          // For all variables
309                         variable = (XDebugVariable) varList.get(i);                     // Get the variable
310                         value = (XDebugAbstractValue) variable.getValue();              // Get the value of the variable
311
312                         try {
313                                 if (value.hasVariables()) {                                             // Does the variable/value have children
314                                         if (!hasRecursion(variable)) {                                  // Don't follow recursive variable/values
315                                                 XDebugVariable var = findVariable(value.getChildVariables(), varname);
316
317                                                 if (var != null) {
318                                                         return var;
319                                                 }
320                                         }
321                                 }
322
323                                 if (variable.getName().equals(varname)) {
324                                         return variable;
325                                 }
326                         } catch (DebugException e) {                                                    // That's, because of the hasVariables method
327                         }
328                 }
329
330                 return null;
331         }
332
333         /**
334          * This method is called from the UI (e.g. from PHPDebugHover
335          * to find the variable the mouse is pointing to)
336          *
337          * @param s The variable name we are looking for.
338          * @return
339          */
340         public IVariable findVariable(String s) throws DebugException {
341                 if (!fUpToDate) {
342                         getVariables();
343                 }
344
345                 return (findVariable(varList, s));                                                      // Prefix the variable name with $
346         }
347
348         /*public void evaluateChange(IStackFrame OldStackFrame) throws DebugException {
349                 IVariable[] OldVariable = ((XDebugStackFrame) OldStackFrame).getVariables();
350                 for (int i = 0; i < fVariables.length; i++) {
351                         ((XDebugVariable) fVariables[i]).setChange(OldVariable[i]);
352                 }
353         }*/
354
355         /* (non-Javadoc)
356          * @see org.eclipse.debug.core.model.IStackFrame#hasVariables()
357          */
358         public boolean hasVariables() throws DebugException {
359                 if (!fUpToDate) {
360                         getVariables();
361                 }
362
363                 return (varList.size() > 0);
364         }
365
366         public int getLineNumber() {
367                 return fLineNumber;
368         }
369
370         /* (non-Javadoc)
371          *
372          */
373         public void setLineNumber(int line) {
374                 fLineNumber = line;
375         }
376
377         /* (non-Javadoc)
378          * @see org.eclipse.debug.core.model.IStackFrame#getCharStart()
379          */
380         public int getCharStart() throws DebugException {
381                 return -1;
382         }
383
384         /* (non-Javadoc)
385          * @see org.eclipse.debug.core.model.IStackFrame#getCharEnd()
386          */
387         public int getCharEnd() throws DebugException {
388                 return -1;
389         }
390
391         /* (non-Javadoc)fName
392          * @see org.eclipse.debug.core.model.IStackFrame#getName()
393          */
394         public String getName() throws DebugException {
395                 return fName.toString() + "::" + fWhere + " line: " + fLineNumber;
396         }
397
398         /* (non-Javadoc)
399          * @see org.eclipse.debug.core.model.IStackFrame#getRegisterGroups()
400          */
401         public IRegisterGroup[] getRegisterGroups() throws DebugException {
402                 return null;
403         }
404
405         /* (non-Javadoc)
406          * @see org.eclipse.debug.core.model.IStackFrame#hasRegisterGroups()
407          */
408         public boolean hasRegisterGroups() throws DebugException {
409                 return false;
410         }
411
412         /* (non-Javadoc)
413          * @see org.eclipse.debug.core.model.IStep#canStepInto()
414          */
415         public boolean canStepInto() {
416                 return fThread.canStepInto();
417         }
418
419         /* (non-Javadoc)
420          * @see org.eclipse.debug.core.model.IStep#canStepOver()
421          */
422         public boolean canStepOver() {
423                 return fThread.canStepOver();
424         }
425
426         /* (non-Javadoc)
427          * @see org.eclipse.debug.core.model.IStep#canStepReturn()
428          */
429         public boolean canStepReturn() {
430                 return fThread.canStepReturn();
431         }
432
433         /* (non-Javadoc)
434          * @see org.eclipse.debug.core.model.IStep#isStepping()
435          */
436         public boolean isStepping() {
437                 return fThread.isStepping();
438         }
439
440         /* (non-Javadoc)
441          * @see org.eclipse.debug.core.model.IStep#stepInto()
442          */
443         public void stepInto() throws DebugException {
444                 fThread.stepInto();
445         }
446
447         /* (non-Javadoc)
448          * @see org.eclipse.debug.core.model.IStep#stepOver()
449          */
450         public void stepOver() throws DebugException {
451                 fThread.stepOver();
452         }
453
454         /* (non-Javadoc)
455          * @see org.eclipse.debug.core.model.IStep#stepReturn()
456          */
457         public void stepReturn() throws DebugException {
458                 fThread.stepReturn();
459         }
460
461         /* (non-Javadoc)
462          * @see org.eclipse.debug.core.model.ISuspendResume#canResume()
463          */
464         public boolean canResume() {
465                 return fThread.canResume();
466         }
467
468         /* (non-Javadoc)
469          * @see org.eclipse.debug.core.model.ISuspendResume#canSuspend()
470          */
471         public boolean canSuspend() {
472                 return fThread.canSuspend();
473         }
474
475         /* (non-Javadoc)
476          * @see org.eclipse.debug.core.model.ISuspendResume#isSuspended()
477          */
478         public boolean isSuspended() {
479                 return fThread.isSuspended();
480         }
481
482         /* (non-Javadoc)
483          * @see org.eclipse.debug.core.model.ISuspendResume#resume()
484          */
485         public void resume() throws DebugException {
486                 fThread.resume();
487         }
488
489         /* (non-Javadoc)
490          * @see org.eclipse.debug.core.model.ISuspendResume#suspend()
491          */
492         public void suspend() throws DebugException {
493                 fThread.suspend();
494         }
495
496         /* (non-Javadoc)
497          * @see org.eclipse.debug.core.model.ITerminate#canTerminate()
498          */
499         public boolean canTerminate() {
500                 return fThread.canTerminate();
501         }
502
503         /* (non-Javadoc)
504          * @see org.eclipse.debug.core.model.ITerminate#isTerminated()
505          */
506         public boolean isTerminated() {
507                 return fThread.isTerminated();
508         }
509
510         /* (non-Javadoc)
511          * @see org.eclipse.debug.core.model.ITerminate#terminate()
512          */
513         public void terminate() throws DebugException {
514                 fThread.terminate();
515         }
516
517         /**
518          * Returns the name of the source file this stack frame is associated
519          * with.
520          *
521          * @return the name of the source file this stack frame is associated
522          * with. If the file associated with this frame does not exists, it returns null.
523          */
524         public String getSourceName() {
525                 if (fName == null) {
526                         return null;
527                 }
528
529                 IPath a = new Path(fName.getFile());
530
531                 return a.lastSegment();
532         }
533
534         public String getFullSourceName() {
535                 if (fName == null) {
536                         return null;
537                 }
538
539                 IPath a = new Path(fName.getFile());
540
541                 return a.toString ();
542         }
543
544         public boolean isSameStackFrame(Object obj) {
545                 boolean isSameStackFrame = false;
546
547                 if (obj instanceof XDebugStackFrame) {
548                         XDebugStackFrame sf = (XDebugStackFrame) obj;
549
550                         isSameStackFrame = sf.getSourceName ().equals (getSourceName ()) &&
551                                                sf.getType().equals (getType ()) &&
552                                                sf.getWhere().equals (getWhere ());
553                 }
554
555                 return isSameStackFrame;
556         }
557
558         public URL getFullName() {
559                 return fName;
560         }
561
562         public void setFullName (URL name) {
563             fName = name;
564         }
565
566         public int getLevel() {
567                 return fLevel;
568         }
569
570         public void setLevel(int level) {
571                 this.fLevel = level;
572         }
573
574
575         public String getType() {
576                 return fType;
577         }
578
579         public String getWhere() {
580                 return fWhere;
581         }
582
583         public boolean setVariableValue(XDebugVariable variable, String expression)  throws DebugException {
584                 return ((XDebugTarget) getDebugTarget()).setVarValue("$" + variable.getName(), expression);
585         }
586
587     public Node eval(String expression) throws DebugException {
588                 return ((XDebugTarget) getDebugTarget()).eval(expression);
589     }
590
591         /**
592          *
593          */
594         public void setAvailable(boolean available) {
595                 fAvailable = available;
596         }
597
598         /**
599          *
600          */
601         public boolean isAvailable() {
602                 return fAvailable;
603         }
604
605         public void setUpToDate (boolean bValue) {
606                 this.fUpToDate = bValue;
607         }
608
609         public void setDescription(String desc) {
610                 this.fWhere = desc;
611         }
612
613         public String getDescription() {
614                 return this.fWhere;
615         }
616
617         /**
618          * This function is needed when sorting the stackframes by their index numbers.
619          *
620          * @param obj The stackframe which this one is compared to.
621          * @return
622          * <ul>
623          * <li> -1 if the index of this stackframe is less.
624          * <li> 0 if the index of both stackframes are equal (should no happen).
625          * <li> 1 if the index of this stackframe is greater.
626          * </ul>
627          */
628         public int compareTo(Object obj) {
629                 if (!(obj instanceof XDebugStackFrame)) {
630                         throw new IllegalArgumentException ("A PHPStackFrame can only be compared with another PHPStackFrame");
631                 }
632
633                 int frameLevel = ((XDebugStackFrame) obj).getLevel ();
634
635                 if (fLevel < frameLevel) {
636                         return -1;
637                 } else if (fLevel > frameLevel) {
638                         return 1;
639                 }
640                 return 0;
641         }
642 }