Chapter 25. ResultView Revisited — Observing Changes

Our old ResultView will keep displaying red or green even if the underlying Java code changes. We would like the color to be accurate—red if a test fails, green if all tests succeed, and neutral if we don't know. In this chapter we revisit the ResultView to clear the red/green result indication as soon as a Java resource has changed and we no longer know whether the tests will succeed or not. This will allow us to illustrate how to listen to changes to Java elements.

In this chapter, we'll see how to

  • Listen to changes to Java elements

  • Process Java element deltas

Testing Color

Let's start with the corresponding test case. We add the following test to ViewColorTest:

Example . org.eclipse.contribution.junit.test/ViewColorTest

org.eclipse.contribution.junit.test/ViewColorTest
public void testResultViewColorReset() throws Exception {
  view.getListener().testsStarted(null, 0);
  view.getListener().testFailed(null, "class", "method", "trace");
  view.getListener().testsFinished(null);
  Display display= view.getControl().getDisplay();
  Color red= display.getSystemColor(SWT.COLOR_RED);
  assertEquals(red, view.getControl().getBackground());
  Color background= display.getSystemColor(
    SWT.COLOR_WIDGET_BACKGROUND);
  changeWorkspace();
  assertEquals(background, view.getControl().getBackground());
}

We simulate the messages broadcast when a test fails. The view should be red. Then we change the workspace and assert that the view color is reset. We implement changeWorkspace() by adding and removing a project using the TestProject fixture.

Example . org.eclipse.contribution.junit.test/ViewColorTest

org.eclipse.contribution.junit.test/ViewColorTest
public void changeWorkspace() throws Exception {
  TestProject project= new TestProject();
  project.createPackage("pack1");
  project.dispose();
}

Observing Changes

How do we observe changes in Eclipse? The Navigator and Package Explorer are good examples for how views handle change. They both present the workspace and update themselves as the workspace changes. As we have seen in Chapter 23, one responsibility of a JFace content provider is to observe the changes in their domain model. Figure 25.1 shows the content providers underlying these Eclipse views.

ContentProvider Observes Changes

Figure 25.1. ContentProvider Observes Changes

When analyzing the code we find that the WorkbenchContentProvider listens to resource changes:

Example . org.eclipse.ui.model/WorkbenchContentProvider

org.eclipse.ui.model/WorkbenchContentProvider
public class WorkbenchContentProvider
  implements IResourceChangeListener {
...
  newWorkspace.addResourceChangeListener(this,
    ResourceChangeEvent.POST_CHANGE);

The PackageExploreContentProvider listens to Java element changes:

Example . org.eclipse.jdt.ui/PackageExplorerContentProvider

org.eclipse.jdt.ui/PackageExplorerContentProvider
class PackageExplorerContentProvider
  implements IElementChangedListener {
...
  JavaCore.addElementChangedListener(this);

Both mechanisms are very similar and are listener-based. A listener is informed of changes by a change event. The change event carries a hierarchical delta describing the changes. A resource-change listener receives the change notification in terms of resources and a Java-element-change listener in terms of Java elements. The event object a listener receives is only valid for the duration of the event method. See Table 25.1 for a comparison of resource and Java element-based notifications.

Table 25.1. Observing Changes to Resources vs. Java Elements

Observing changes in

Resources

JavaElements

Listener interface

IResourceChangeListener

IElementChangedListener

Change event

IResourceChangeEvent

ElementChangedEvent

Delta

IResourceDelta

IJavaElementDelta

Register listener with

IWorkspace

JavaCore

Java elements provide a Java-specific view of the underlying resources as defined by a project's build class path. This means folders on the build class path become packages, and so on. We have to choose between general resource changes and Java-specific changes. Since we are only interested in Java changes, we implement a listener for Java element deltas. Let's first understand Java element deltas better.

Observing Changes to Resources vs. Java Elements
Observing Changes to Resources vs. Java Elements

In the toString() representation we see that the delta starts at the JavaModel and then includes a path to the changed node FailTest.java. This delta describes a single change only, but deltas can include more than one change. The kind of change is encoded as changed = *, added = +, removed = -.

First we define a DirtyListener inner class in the ResultView. It implements IElementChangedListener and its event handling method elementChanged(). We register a DirtyListener with the JavaModel in createPartControl() and deregister it in dispose(). Since dispose() can be called without a matching call to createPartControl(), we have to check that the listener got registered.

Example . org.eclipse.contribution.junit/ResultView$DirtyListener

private DirtyListener dirtyListener;
private class DirtyListener implements IElementChangedListener {
  public void elementChanged(ElementChangedEvent event) {
    processDelta(event.getDelta());
  }
}

Example . org.eclipse.contribution.junit.ResultView

public void createPartControl(Composite parent) {
  listener= new Listener();
  JUnitPlugin.getPlugin().addTestListener(listener);
  dirtyListener= new DirtyListener();
  JavaCore.addElementChangedListener(
    dirtyListener,
    ElementChangedEvent.PRE_AUTO_BUILD);
  control= new Label(parent, SWT.NONE);
}

public void dispose() {
  if (listener != null)
    JUnitPlugin.getPlugin().removeTestListener(listener);
  if (dirtyListener != null)
    JavaCore.removeElementChangedListener(dirtyListener);
  listener= null;
  dirtyListener= null;
}

The method addElementChangedListener() supports an event mask as its second argument. The event mask allows us to specify the events we are interested in. We use ElementChangeEvent.PRE_AUTO_BUILD as the event mask to indicate that we want to receive change notifications before the auto-build starts. With auto-testing enabled, tests are run as part of an auto-build and we want to reset the notification before the tests start. Otherwise our listener would not correctly reset the color indication for auto-test builds.

Reacting to Changes

We want to reset the color in the view when one of the following changes occurs:

  • Adding/removing a Java element inside the workspace, a project, a source folder, or package

  • Changing the contents of a compilation unit

We process a delta in the method processDelta(). It recursively traverses the delta and returns true when the traversal should continue:

Example . org.eclipse.contribution.junit/ResultView$DirtyListener

private boolean processDelta(IJavaElementDelta delta) {

  // analyze delta see below

  IJavaElementDelta[] affectedChildren=
    delta.getAffectedChildren();
  for (int i= 0; i < affectedChildren.length; i++)
    if (!processDelta(affectedChildren[i]))
      return false;
    return true;
}

Each delta node provides access to its affected children. These are the deltas for added, removed, or changed children. Next let's look into the delta analysis. Each delta node has a change kind—added, removed, changed—and a corresponding Java element. If the user adds or removes Java elements above compilation units, we reset the view. Similarly, any change to a compilation unit invalidates the view color. This is the corresponding delta analysis code:

Example . org.eclipse.contribution.junit/ResultView$DirtyListener.processDelta()

int kind= delta.getKind();
int type= delta.getElement().getElementType();
switch (type) {
  case IJavaElement.JAVA_MODEL:
  case IJavaElement.JAVA_PROJECT:
  case IJavaElement.PACKAGE_FRAGMENT_ROOT:
  case IJavaElement.PACKAGE_FRAGMENT:
    if (kind == IJavaElementDelta.ADDED ||
        kind == IJavaElementDelta.REMOVED)
      return testResultDirty();
    break;
  case IJavaElement.COMPILATION_UNIT:
    return testResultDirty();
  }

Finally, here is the implementation of ResultView.testResultDirty(). We have to post the color change to the main thread since the delta can be sent out from another thread.

Example . org.eclipse.contribution.junit/ResultView

private boolean testResultDirty() {
  final Display display= getSite().getShell().getDisplay();
  display.syncExec(new Runnable() {
    public void run() {
      if (!control.isDisposed())  {
       Color background= display.
         getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
       control.setBackground(background);
      }
    }
  });
  return false;
}

With these additions the test passes. Unfortunately the color resets when we open or close a compilation unit in an editor. To understand what is going on, let's enable the tracing option for Java element deltas. To do so, we switch to the trace option tab of the launch configuration for our workbench and enable the javadelta tracing option, as shown in Figure 25.3.

Working Copies

When this tracing option is set, all delta notifications are logged to the console. In addition, you can see how much time each listener takes. When opening the compilation unit FailTest.java, we get the following trace:

FIRING POST_CHANGE Delta [Thread[main,5,main]]:
Java Model[*]: {CHILDREN}
  TestProject1[*]: {CHILDREN}
    src[*]: {CHILDREN}
      pack1[*]: {CHILDREN}
        [Working copy] FailTest.java[+]: {}
Listener#1=...PackageExplorerContentProvider@139491b->0ms
Listener#2=...ResultView$DirtyListener@16db492 -> 0ms

The trace indicates that a working copy was added in pack1. We don't want to reset the result color when merely opening a compilation unit, so we filter out working copy changes in the processDelta() method:

Example . org.eclipse.contribution.junit/ResultView#DirtyListener.processDelta()

//...
case IJavaElement.COMPILATION_UNIT:
  ICompilationUnit unit= (ICompilationUnit)delta.getElement();
  if (unit.isWorkingCopy())
    return true;
  return testResultDirty();

The ResultView now more accurately reflects the current state of the tests. Reviewing this chapter

  • We have used JavaElementChangedListeners to observe changes to Java elements.

  • We have shown how to analyze a JavaElementDelta.

  • We have seen how to trace the broadcast of Java element deltas.

Forward Pointers

  • When registering the listener you can specify an event mask in both the resource and Java element worlds. In the event mask you can specify which events you are interested in, for example, POST_CHANGE, PRE_CLOSE, PRE_AUTOBUILD, etc.

  • Resource deltas not only carry information about the changed resources, but also about the changed markers.

  • For more information about resource deltas, refer to: www.eclipse.org/articles/Article-Resource-deltas/resource-deltas.html.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset