Chapter 20. Exception Handling

Let's say you create a marvelously successful plug-in. You have thousands—no millions—of satisfied customers. What a disaster!

Why a disaster? Even with test-driven plug-in development some percentage of users will have problems. The more users, the more problems. Pretty soon, support costs will eat you out of house and home.

In this chapter we'll talk about Eclipse features that collectively reduce support costs:

  • Handling Eclipse exceptions

  • Presenting exceptions to the user using an error dialog

  • Logging errors to the Eclipse log

An important rule to improve the serviceability of your plug-ins is a bit of a paradox:

  • RESPONSIBILITY RULEClearly identify your plug-in as the source of problems.

How can telling the user a mistake is yours reduce your cost of service?

  • If you have an open source plug-in, the community will collectively provide first level service. If you provide the necessary information in log entries and error dialogs, most problems will be solved before you ever see them.

  • If you know problems can be traced directly to your plug-in, you will be careful to write robust code. The test-driven techniques introduced in the Interlude can go far towards eliminating problems at the source if applied carefully.

IStatus, CoreException

To address the Responsibility Rule, exceptions thrown by plug-ins have to carry an IStatus object. The IStatus object identifies the plug-in and contains a severity, a status code, a message, and optionally, an exception. The contained exception typically comes from a lower layer that should not be exposed to clients. CoreException is the root of the Eclipse exception hierarchy and carries an IStatus.

When an exception occurs, you want to inform the user that it happened. If you cannot show the exception to the user, then you want to at least log it to help you service problem reports. For example, it is always safe to inform the user of an exception when it happens in your action code. Then you know the context the user is in, that is, they just invoked the action. The situation is less clear when you don't know the calling context. You have to decide whether you want to propagate the exception, inform the user, or to just log the exception.

IStatus, CoreException

Example . org.eclipse.ui.internal.actions/OpenWorkspaceFileAction

org.eclipse.ui.internal.actions/OpenWorkspaceFileAction
public void run(IAction action) {
  //...
  try {
    IWorkbenchPage page = workbenchWindow.getActivePage();
    if (page != null)
     page.openEditor(file);
  } catch (CoreException x) {
    String title = "Open File"
    String message = "An Exception occurred while "+
      "opening the resource";
    WorkbenchPlugin.log(title, x.getStatus());
    ErrorDialog.openError(workbenchWindow.getShell(), title,
      message, x.getStatus());
  }
}

When a CoreException happens, your first decision is whether you want to inform the user with a dialog or whether you want to just log the problem. If you are in a context where you can show a dialog, then you should always do so. If this is not possible, you should log the problem. If you have informed the user of the problem with a dialog, there is no need to also log the problem unless you feel it will help you service problem reports later. Never just swallow an exception. In the snippet above, the developer has decided to both inform the user and to log the problem.

The Plugin class provides access to the log. It is common to provide a static helper method returning the log. The helper takes an IStatus object and passes it on to the log as shown below:

Example . org.eclipse.ui.internal/WorkbenchPlugin

org.eclipse.ui.internal/WorkbenchPlugin
public static void log(String message, IStatus status) {
  //...
  getDefault().getLog().log(status);
}

Presenting Exceptions in an Error Dialog

To inform the user about a problem Eclipse provides an ErrorDialog that can present the IStatus to the user. It is used in the snippet above.

Here are some other error situations and hints for how to do the error reporting:

  • You don't have a CoreException or an exception carrying an IStatusInform the user with the MessageDialog (MessageDialog.openError()). To log the exception create an IStatus object and log it.

  • A single conceptual operation can have multiple errors and you do want to proceed despite a single error—In this case, collect all of the IStatus' in a MultiStatus and pass the MuliStatus to the ErrorDialog or the log.

Let's apply what we have learned to our example. A good place to use an error dialog in our code is the AutoTestPropertyPage. When the user clicks OK, our method performOk() is called. The methods we call, like JUnitPlugin.addAutoTestNature(), can throw a CoreException. Opening an error dialog makes perfect sense, since we're in the middle of a user interface operation:

Example . org.eclipse.contribution.junit/AutoTestPropertyPage

public boolean performOk() {
  try {
    JUnitPlugin plugin= JUnitPlugin.getPlugin();
    if (autoTest.getSelection())
      plugin.addAutoBuildNature(getProject());
    else
      plugin.removeAutoBuildNature(getProject());
  } catch (CoreException e) {
    ErrorDialog.openError(getShell(), "Error",
      "Cannot set auto-test property", e.getStatus());
  }
  return true;
}

We can force this dialog to appear by throwing an exception in the body of the try block.

Example . org.eclipse.contribution.junit/AutoTestPropertyPage

public boolean performOk() {
  try {
    JUnitPlugin plugin= JUnitPlugin.getPlugin();
    Exception exception= null;
    String id= plugin.getDescriptor().getUniqueIdentifier();
    int code= 42;
    IStatus status= new Status(IStatus.ERROR, id, code,
      "Status message", exception);
    throw new CoreException(status);
  } catch (CoreException e) {
    ErrorDialog.openError(getShell(), "Error",
      "Cannot set auto-test property", e.getStatus());
  }
  return true;
}

Then we see the error dialog shown in Figure 20.1.

org.eclipse.contribution.junit/AutoTestPropertyPage

This code also serves to show you how to throw a CoreException. As described above, each CoreException has associated with it an IStatus, an object that bundles information about an operation. We're using it to report a problem (IStatus.ERROR). The status also carries a human-readable error message (you can see it in the dialog above), an error code, and the original exception, if any.

We left several TODO comments about places in the code where we wanted to do a more thorough job of error handling. We'll fill in one of those places here.

Logging Errors

In MarkerCreator, we had to clear the test failure markers any time a project's tests restarted. Removing the markers potentially throws a CoreException. In this case, we cannot make any assumption about the calling context of this method, so we cannot simply show an ErrorDialog. Therefore we just log the problem:

Example . org.eclipse.contribution.junit/MarkerCreator$Listener

public void testsStarted(IJavaProject project, int testCount) {
  try {
    IResource resource= project.getUnderlyingResource();
    resource.deleteMarkers(
      "org.eclipse.contribution.junit.failure", false,
      IResource.DEPTH_INFINITE);
  } catch (CoreException e) {
    JUnitPlugin plugin= JUnitPlugin.getPlugin();
    IStatus status= new Status(IStatus.ERROR,
      plugin.getDescriptor().getUniqueIdentifier(), 0,
      "Problem deleting markers", e);
    plugin.getLog().log(status);
  }
}

Now when we force the problem to occur:

Example . org.eclipse.contribution.junit/MarkerCreator$Listener

public void testsStarted(IJavaProject project, int testCount) {
  JUnitPlugin plugin= JUnitPlugin.getPlugin();
  try {
    IStatus status= new Status(IStatus.ERROR,
      plugin().getDescriptor().getUniqueIdentifier(), 42,
      "Status message", null);
    throw new CoreException(status);
  } catch (CoreException e) {
    IStatus status= new Status(IStatus.ERROR,
      plugin.getDescriptor().getUniqueIdentifier(), 0,
      "Problem deleting markers", e);
    plugin.getLog().log(status);
  }
}

We get the error log entry shown in Figure 20.2. To see the entry we first open the Error Log view, shown in Figure 20.3.

org.eclipse.contribution.junit/MarkerCreator$Listener
org.eclipse.contribution.junit/MarkerCreator$Listener

Bringing up the properties of the entry, we see the stack trace including our error messages, shown in Figure 20.4.

org.eclipse.contribution.junit/MarkerCreator$Listener

In addition to browsing the log with the Error Log view, you can always access the log file directly. It is located in the target workspace's .metadata folder with the name .log.

Another aspect of reducing the cost of servicing your plug-ins is instrumenting your plug-in with code that can be optionally enabled. We'll cover that in the next chapter. Reviewing this chapter, we

  • Opened an error dialog when we received a CoreException during a user interface operation

  • Wrote a message to the log file when we received a CoreException in a context where we didn't want to show a dialog

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

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