Thursday, November 21, 2013

Debugger 9: Variables support

Lets continue our little debugger example by adding support to display variables.

Debug Framework Tutorials

For a list of all debug related tutorials see Debug Framework Tutorials Overview.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Implementing variable classes

Variables are attached to StackFrames and will only be queried when the debugger is in suspended state. To implement them, we need a variable description stored in TextVariable:
public class TextVariable extends TextDebugElement implements IVariable {

 private final String fName;
 private IValue fValue;

 protected TextVariable(IDebugTarget target, String name, String value) {
  super(target);
  fName = name;
  setValue(value);
 }

 @Override
 public void setValue(String expression) {
  fValue = new TextValue(getDebugTarget(), expression);
 }

 @Override
 public boolean supportsValueModification() {
  return false;
 }

 @Override
 public IValue getValue() {
  return fValue;
 }

 @Override
 public String getName() {
  return fName;
 }

 @Override
 public String getReferenceTypeName() throws DebugException {
  return "text type";
 }
}
The implementation is straight forward and does not allow to modify the variable content.

An actual value is stored in the TextValue class:
public class TextValue extends TextDebugElement implements IValue {

 private final String fValue;

 public TextValue(IDebugTarget target, String value) {
  super(target);

  fValue = value;
 }

 @Override
 public String getReferenceTypeName() throws DebugException {
  return "text type";
 }

 @Override
 public String getValueString() throws DebugException {
  return fValue;
 }

 @Override
 public IVariable[] getVariables() throws DebugException {
  return new IVariable[0];
 }

 @Override
 public boolean hasVariables() throws DebugException {
  return false;
 }
}
To display hierarchical structures a value might hold child variables (eg. fields of a class, elements of a collection, ...). In that case you need to implement hasVariables() and getVariables() too.

You might have noticed that both classes define a method getReferenceTypeName(). While TextVariable.getReferenceTypeName() denotes the declared variable type, TextValue.getReferenceTypeName() denotes the actual type. Looking at a java example
IValue value = new TextValue();
The declared type would be IValue, while the actual type would be TextValue. You may enable these columns in the debug view by opening the Variables view menu and selecting Layout / Select Columns...

Step 2: Updating variables

Now for the big question: "how and when to update variables".

First we notice that variables are queried by the debug framework when a StackFrame element is selected that is in suspended mode. As we should do all our debugger queries in asynchronous mode we have two options:
  1. populate all stack frames automatically with variables on a suspend event in case our user might query them
  2. first deliver outdated/no data and update them as fast as possible
For the demo I chose the second option: we display whatever variables we had stored on the last query and trigger an asynchronous update.
public class TextStackFrame extends TextDebugElement implements IStackFrame {

 private final List<TextVariable> fVariables = new ArrayList<TextVariable>();
 private boolean fDirtyVariables = true;

 @Override
 public synchronized IVariable[] getVariables() {
  if (fDirtyVariables) {
   fDirtyVariables = false;
   getDebugTarget().fireModelEvent(new FetchVariablesRequest());
  }

  return fVariables.toArray(new IVariable[fVariables.size()]);
 }

 public synchronized void setVariables(Map<String, String> variables) {
  for (String name : variables.keySet()) {
   boolean processed = false;
   // try to find existing variable
   for (TextVariable variable : fVariables) {
    if (variable.getName().equals(name)) {
     variable.setValue(variables.get(name));
     variable.fireChangeEvent(DebugEvent.CONTENT);

     processed = true;
     break;
    }
   }

   if (!processed) {
    // not found, create new variable
    TextVariable textVariable = new TextVariable(getDebugTarget(), name, variables.get(name));
    fVariables.add(textVariable);
    textVariable.fireCreationEvent();
   }
  }
 }

 @Override
 public synchronized void fireChangeEvent(int detail) {
  fDirtyVariables = true;

  super.fireChangeEvent(detail);
 }

 @Override
 public boolean hasVariables() {
  return getVariables().length > 0;
 }
}


The TextDebugger sends its list of variables as a new event to the TextDebugTarget, which simply delegates the call to the topmost StackFrame within its handleEvent() method. During the update we send either change events or creation events for the updated variables. Of course a real implementation should be more intelligent than the implemented version above and use intelligent comparisons and caching.

If you want changed contents to be highlighted you should implement IVariable.hasValueChanged(). An example implementation is provided in the source code on github.

 
UI problems

There is one behavior, I could not fix yet: Sometimes the Variables view remains empty when the debugger suspends. Pushing the view to back and bringing it to front again solves this issue. Not sure if this is a problem in my code or the debugger framework. See bug 528120 for details.

No comments:

Post a Comment