Manual

Content

Installation

The tool can be installed directly from an update site within Eclipse. To install the tool, the following tasks need to be performed:

  1. Open Eclipse, and open the Install New Software dialog.
  2. In this dialog, add a new software site to install plug-ins from (or choose the one which you already added, if you are updating the plug-in). In the dialog given below, enter the URL http://edvizdebugger.github.io/update, and optionally give it a name so that you can recognize it again.

  3. It can happen that no plug-ins are shown. There can be two cases: Either you already have the newest version of the tool, in which case you can skip the installation. Otherwise, make sure that the option Group items by category is not checked.
  4. Choose the plug-in called EDViz and press Next. To through the following dialogs and accept the license as well. Afterwards, a Security Warning might pop up. In that case, just ignore it and press OK. Finally, the plug-in will be installed, and Eclipse will prompt to restart. The tool has been successfully installed.

Features

The DebugTool is an Eclipse-plugin which can be used to debug Java programs. It features a main drawing area of customizable size, where the heap structure as given at breakpoints is laid out on screen. The tool is particularly suited to developing new data structures and small heaps. The following features are a part of the tool:

Usage

EDViz can be used to debug Java programs written in Eclipse. The tool is most suited to simple and small data structures such as linked lists, binary trees or hash maps, however not strictly limited to them. To use the tool to debug Java programs, the following steps are necessary:

The following is a screenshot of the tool in action, displaying a simple binary tree data structure:

Actions

While debugging a program, various actions can be taken. They are listed at the top of the debug tool. The actions which can be performed can be seen in the below screenshot and are explained here in more detail.

Configuration

The config.json file in the plugin folder allows to configure various things. The following options can be configured directly in the file. The more important options are available in the settings dialog, or through one of the different editors for ordering constraints, string formats, blacklist or abstraction rules. If changes are made directly to the file, Eclipse will need to be restarted for them to take effect. Changes made with the different dialogs are applied immediately. Editing the configuration file directly should only be performed by expert users.

Optionally, you may specify another configuration file to use through the Load configuration action. This allows you to have multiple configuration files and swap between them, for example to test certain behavior without destroying the original configuration file. The Load configuration action also shows you the default location of the configuration file.

Ordering constraints

The ordering constraints are stored in the configuration under constraints > queries in the form of an array of constraints. They can easily be edited using the edit constraints action. These constraints decide on the horizontal ordering of the values displayed in the graph on screen.

Right-clicking on values with two or more fields allows an ordering constraint to be added directly for that value's type, as an easier alternative. That option can be found in the context menu under Rules → Write order constraint for type X, where X will be replaced by the value's actual type. There is no semantic difference between an ordering constraint added that or any other way, it's just a simplification of the process.

Each of the constraints has three parameters. These parameters are the following:

  • query: Based on the query it is decided on which values to apply the ordering constraint. Generally, each query can match zero, one or multiple values. The query can have one of two forms:
    1. typeName.something: Here, typeName refers to some Java type, and something dereferences some fields from that type. something can dereference multiple fields in a row, e.g. f1.f2 or also be empty. Then the matching values will be anything dereferenced by something of all the values of the given Java type. Note that Java types which are contained in packages must have the dots in their names replaced by /. This means that e.g. java.util.String must be converted to java/util/String.
    2. variableName.something: Here, variableName refers to the name of a variable in the program, and something works the same way as given above, by being dereferenced on the variable's value.
    To refer to all fields of an array, you may use any single character. For example, array.i will let i refer to all fields of the given array. i can then be used to refer to the array elements in the order.
  • quantifier: The quantifier decides to which of the values matched by the query the ordering constraint should be applied. Currently there is only one quantifier: all, which applies the ordering constraint to all values matched by the query.
  • order: The left-to-right order in which the fields of the matched values should be drawn. The order is applied to each matched value separately. In the order, fields of the matched values can be used. Generally, all matched values should have the same type and therefore the same fields. Some examples:
    1. (f1)<(f2): For each matched value, field f1 should be ordered to the left of field f2.
    2. (f1.y)<(f2.x): For each matched value, field y of the value in field f1 should be ordered to the left of field x of the value in field f2.
    3. (f1.recurse(f1))<(f2.recurse(f2)): The recurse function is special: It recursively follows the field or fields given as arguments and therefore resolves to multiple values. For example, f1.recurse(f1) leads to f1,  f1.f1,  f1.f1.f1 and so on. Multiple fields may also be followed: f1.recurse((f1|f2)) leads to f1,  f1.f1,  f1.f2,  f1.f1.f1,  f1.f1.f2 and so on. All fields of a value may be followed with f1.recurse(*).
    4. (i)<(i+1): If you end the query part with i to refer to all children of an array, this allows to order the children of an array in their natural order.
Examples

Assume that you have the following BinaryTree class in your code:

package binary;

class BinaryTree<T> {
BinaryTree<T> left, right;
T value;
...
}

As usual, you want left to be drawn to the left of right for all binary tree elements. Then you should add the following ordering constraint. Note that the recurse(*) here ensures that the entire left subtree is drawn to the left of the entire right subtree. If you omitted the recurse(*), then each left sibling would be drawn to the left of its right sibling, but there would be no guarantees about the entire subtree. Also note that we call the type binary/BinaryTree<T>, we use the fully qualified name, but replacing dots with /.

{
"query": "binary/BinaryTree<T>",
"quantifier": "all",
"ordering": "(left.recurse(*))<(right.recurse(*))"
}

Assume now that you have two variables in your program, root1 and root2. To order root1 horizontally left of root2, add the following ordering constraint. The variables are root items, just as the types are, therefore the query is left empty.

{
"quantifier": "all",
"ordering": "(root1)<(root2)"
}

For the last example, assume that the variable root1 is of type BinaryTree<T>. To order only the children of that specific variable, and not all children for that type, add the following ordering constraint:

{
"query": "root1",
"quantifier": "all",
"ordering": "(left)<(right)"
}

Let's assume we have the ArrayList as given in the abstraction examples. To order the elements in their natural order, use the following constraints:

{
"query": "ArrayList.elements.i",
"quantifier": "all",
"order": "(i)<(i+1)"
}

String formats

The string formats are stored in the configuration under toStringFormats in the form of an array of string formats. They can easily be edited using the edit string formats action. Based on these string formats, the text to display within a value in the graph on screen is decided.

Right-clicking on a value will present some options for setting the string format of the value. Under Rules → Set format of type X to {f} you may set the format of all values of type X to show the value of their field f as a default option. If the default formats are not sufficient, you may choose Rules → Write format for type X to write your own format for all values of the given type X.

Each of the formats has three parameters. These parameters are the following:

  • query and quantifier: These are the same as in the ordering constraints.
  • format: Based on the format, it is decided how the text to display will look like. The format is a special string which allows for formatting based on field values: {f} will be replaced with the value of the field f in the values matched by the query and quantifier. Outside of {}, the string is left as-is. For example, applying {key}:{value} to a value with fields key = Hello and value = 2 will lead to the string Hello:2.

If no format is applied to a value, their value by default will be as follows:

  • For primitives, the primitive value is converted to a string.
  • For arrays, they are converted to elementTypeName[]. Arrays with a limited number of primitive values can also be converted to [v1,v2,v3] where vi are the values in the array.
  • For objects, the format {value} is applied. If the object doesn't have a field value, then it is converted to the value's type name.

Note: Cascading formats are currently not supported. Assume you have a structure A → B → C. Setting the format for type A to {b} and the format of type B to {c} will not show the value of C for the value A. To achieve such an effect, the format of A should be {b.c} instead.

Examples

Assume that you have the BinaryTree class as given in the ordering example. Then the following format will use the value field as the string to display. Note that we prefix the actual value with value:.

{
"query": "binary/BinaryTree<T>,
"quantifier": "all",
"format": "value:{value}"
}

The string to be displayed can also be hardcoded. For example, to display every left field of a tree as left, use the following format:

{
"query": "binary/BinaryTree<T>.left,
"quantifier": "all",
"format": "left"
}

Now assume that you have the following Entry class:

class Entry {
int key,value;
...
}

You can use multiple fields in the format. The following format will use both fields to generate an intuitive string describing the contents of the entry:

{
"query": "Entry",
"quantifier": "all",
"format": "{key}:{value}"
}

Instead of type names you may also use variable names for the query. Then the format will simply be only applied to the value pointed to by the variable, and not to all values of some type. Dereferences both in the query and in the formats.

Abstraction rules

The abstraction rules are stored in the configuration under abstractions in the form of an array of abstraction rules. They can easily be edited using the edit abstractions action. Based on these rules, multiple values may be abstracted to a single value, allowing to save space on screen and draw larger heaps.

Each of the rules has five parameters. These parameters are the following:

  • query and quantifier:These are the same as in the ordering constraints.
  • comparison: The comparison to compare the values matched by the query and quantifier with. Multiple comparisons may be specified by separating them with a comma and putting each comparison into normal brackets. A comparison can be of one of the following forms. Note that the query may be replaced by *, this then corresponds to all fields. The query has the same semantics as the query given above, with the all quantifier.
    1. query: This is equal to query==*.
    2. query==v: This compares the value at query to the value v. v may take some special values: primitive checks whether the value is of a primitive type and null whether it is null. Comparing to * always returns true. Instead of query, also * may be used, which refers to all fields of the value and will therefore compare all fields of the value to v and return the ones that match. Using this allows to refer to the current value instead of any field of it. If v should have spaces, it must be enclosed in "": query=="With spaces".
    3. query!=v: This is true iff query==v is false.
  • count: This denotes the minimal number of values which the query has to match in order for the abstraction rule to be applied. Setting this to 1 will ensure that the abstraction will always be applied.
  • active: If set to false, this abstraction rule will be disabled. This allows to easily deactivate abstraction rules without having to delete them.
Examples

Let's assume you have some class ArrayList as given below:

class ArrayList {
Object[] elements;
ArrayList() {
elements = new Object[100];
}
...
}

This class has an obvious problem when trying to display it with the tool. The elements array has 100 elements, which will not fit on even a very large screen. However, for small programs, it's likely that this array will be mainly filled with null elements. These elements can be abstracted to a single element without really losing any information. The following rule will abstract them to one node, as long as there are at least five of them:

{
"query": "ArrayList.elements",
"quantifier": "all",
"comparison": *==null",
"count": 5
}

Let's assume we have two somewhat strange classes as given below:

class Class1 {
Class2 next;
...
}

class Class2 {
Class1 next;
...
}

We can now for example abstract any pair of Class1 → Class2 objects in order to reduce the height of the heap. We can do this with the rule given below. Note that we don't add any equalities to the comparison, as we want to abstract them together regardless of their value. As we specify multiple comparisons, we must separate them by a comma and wrap them in brackets. If we started the query at Class2, we would be abstracting Class2 → Class1 pairs, which is slightly different.

{
"query": "Class1",
"quantifier": "all",
"comparison": "(this),(next)",
"count": 1
}

Blacklist

The blacklist rules are stored in the configuration under blackList in the form of an array of blacklist rules. They can easily be edited using the edit blacklist action. Based on these rules, values and variables may be blacklisted, meaning that they won't appear in the graph on screen.

Right-clicking on a value also shows options for blacklist rules to be added. Choose Rules → Blacklist field v of type X to blacklist all fields v of some type X. Instead, you may also blacklist by value: Rules → Blacklist value x will blacklist all values which match the given comparison x. Lastly, you may write your own pre-filled blacklist query by choosing Rules → Write blacklist for field v of type X to manually write a conditional blacklist for fields v of some type X.

Each of the rules has three parameters. These parameters are the following:

For each of these rules, the values and variables matching the rules will be resolved, and they will not be shown in the graph. This allows to remove some fields or variables which typically cause visual clutter but are not actually important to debugging, for example the String[] args parameter in Java main methods.

Examples

Assume that you have the BinaryTree class as given in the ordering examples. The value of the tree is usually not drawn as a node, but rather displayed inside the tree node, as we have done already in the string format examples. The following blacklist rule takes care of that:

{
"query": "binary/BinaryTree<T>",
"quantifier": "all",
"comparison": "value"
}

In some cases, the value might actually contain important information, for example if it is not a primitive. To blacklist it only when it is a primitive value, use the following rule:

{
"query": "binary/BinaryTree<T>",
"quantifier": "all",
"comparison": "value==primitive"
}

When debugging a Java main method, there is always a String[] args variable. In many cases, this is not needed. To blacklist that variable, the following rule can be used. The comparison here is optional. If set, the variable is only blacklisted if it contains a primitive value. If no comparison is given, then the variable is always blacklisted.

{
"query": "args",
"quantifier": "all",
<optional>("comparison": "this==primitive")
}