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