Chapter 8. Defining an Extension Point

One early reviewer commented, “I just want to know how to write plug-ins. I don't really want to learn how to declare new extension points. There are hundreds of plug-ins out there and almost none of them enable new extension points.” Eclipse is great fun for writing little plug-ins, but if you aren't declaring new extension points, you're missing out on much of the power of the platform. When children are taught to ice skate in Switzerland, they begin skating forwards and backwards the first day. Skating backwards is just never scary if that's how you learn. We believe creating extension points doesn't need to be scary, but you need to start creating extension points early. Once you get good at creating extension points, building upon your work will be easier for you and for others.

Our goal for this chapter is to invite others (and ourselves) to present test results. In this chapter, you'll see

  • Designing and defining an extension point

  • Defining a contribution interface

  • Defining an extension

We have a simple, dialog-based interface, but we can't be sure it is the one and only best way to present results.

  • INVITATION RULEWhenever possible, let others contribute to your contributions.

How can we open up our system so others can also display results? If we didn't want to change our code, we could implement an extension point, but continue to use dynamic listener protocol. If the rules are different for different clients, though, we miss the chance for valuable feedback.

  • FAIR PLAY RULEAll clients play by the same rules, even me.

Eclipse has few exceptions to the Fair Play Rule. Even the workbench itself is loaded as an extension. If you want to run a completely different application on top of the platform core runtime, you can define your own extension to the application extension point (org.eclipse.core.runtime.applications). If we're going to play fair, we should convert what we have already to use our new extension point.

Eclipse enables people solving a problem many different ways. When contributing to Eclipse, you don't build monolithic functionality. Instead, you create opportunities for extension, and you provide some extensions yourself. Invariably, you also use your own extensions.

Now that we have the basic test-running functionality, we want to open it up. To define and use an extension point, we have to do the following:

  • Design the extension point.

  • Define the extension point in the manifest.

  • Write code to load the extensions to that point.

  • Invoke the extensions (safely) at the appropriate time.

Object-oriented frameworks are designed, as is Eclipse, to be used by being extended. One problem with object-based frameworks is that every public method is a potential point for extension. If you're successful and people use your framework, you quickly find that you have no freedom to grow the framework further. Every method has potentially been extended, so you can't change anything. As Martin Fowler says, we should distinguish between public and published methods.[1] Published methods are intended for use by others, public methods are merely visible. Eclipse institutionalizes this distinction.

  • EXPLICIT EXTENSION RULEDeclare explicitly where a platform can be extended.

Extension points are declared in the plug-in manifest. Being good little monkeys, we'd like an example from which to copy. Where can we find a similar extension point? We'd like to browse all extension points. We often use the on-line help (see Figure 8.1).

EXPLICIT EXTENSION RULE
EXPLICIT EXTENSION RULE

Looking around we find org.eclipse.ui.views. The example given in the help looks like this:

Example . <extension point="org.eclipse.ui.views">

<extension point="org.eclipse.ui.views">
 ...
 <view
   id="com.xyz.views.XYZView"
   name="XYZ View"
   category="com.xyz.views.XYZviews"
   class="com.xyz.views.XYZView"
   icon="icons/XYZ.gif">
 </view>
</extension>

In general, an extension point should accept many extensions. The rule is:

  • DIVERSITY RULEExtension points accept multiple extensions.

The Diversity Rule is a consequence of the Sharing Rule. If someone adds an extension to an extension point, it should not affect your ability to add another extension to the same point. It's when designing and implementing an extension point that you have to take the Diversity Rule into account. The extensions themselves should never care about the presence or absence of other extensions.

Why isn't a run-time listener good enough? When we popped up the dialog to display the results, we added and removed the listener dynamically. However, we'd also like to create plug-ins that would respond to the running of all tests. For example, imagine a plug-in that counts all tests being run and sends a message to a central server to count all the tests run by everyone in a community. Such a plug-in would not be activated and deactivated dynamically. It would register as an extension and then be activated on demand, the first time a test was run (Lazy Loading Rule).

Mimicking the structure of the views extension point, we should have an overall extension point for test listeners with elements, each representing a single class of listener. What should the name of the extension point be? Everywhere the extension point name is used it will be fully qualified, so we don't need to make the name itself unambiguous. We can just call it listener. The Diversity Rule implies that the name should be plural, so we get listeners. So, in use, the extension point should look something like the following:

<extension point="org.eclipse.contribution.junit.listeners">
  <listener
    class="org.eclipse.contribution.junit.RunTestAction$Listener">
  </listener>
</extension>

This extension declaration says that when an extension point named listeners is invoked, an instance of RunTestAction$Listener should be created (by convention, this attribute is always called class). In our case we have a static inner class, so we have to qualify the name with “$”. Class names in this form are common but not standard. If we wanted to be perfectly portable, we would promote our inner class to a top-level class. Our listeners needs to follow the Conformance Rule and implement ITestRunListener.

One thing to consider when designing extension points is how to help Eclipse be even lazier loading implementors of the extension point. We can imagine implementing a special extension point that is only notified when a test fails. Extenders would not have to be loaded as long as all the tests succeed. The tradeoff is you don't want tons of extension points that don't do much by themselves. In this case, we can't imagine how we would use an extension point that is notified on test failure but doesn't care about success, so we lump the notifications together in a single extension point. We would only make this kind of refinement if it was possible to typically not load a plug-in at all. Here that's not the case—tests will fail.

PDE tells us that the extension point org.eclipse.contribution.junit.listeners is not defined, as shown in Figure 8.2.

Diagnosing Plug-Ins

How do we declare an extension point? First, we need to find the declaration of our example, as shown in Figure 8.3.

Diagnosing Plug-Ins
Diagnosing Plug-Ins

This leads us to the following declaration (in the Source tab):

Example . org.eclipse.ui/plugin.xml

org.eclipse.ui/plugin.xml
<extension-point id="views" name="%ExtPoint.views" schema="schema/views.mxsd"/>

A point that often trips us is that the only difference between the declaration of an extension point and a use of that point is the dash “-”. Be careful when defining new extension points, or use the PDE tools to edit the manifest file. Notice that the id in the declaration has no unique prefix. Eclipse prepends the plug-in id as a prefix for all extension points. Extensions, however, must use the fully qualified id. You always declare an extension point within a particular plug-in. When you use it, however, you could be in the same plug-in, or in a completely different plug-in.

The name of an extension point is used by PDE to display the extension point to a user. The name above is internationalized, a topic we'll cover in Circle Two.

Mimicking the views extension point declaration, we need to declare our listeners extension point as:

Example . org.eclipse.contribution.junit/plugin.xml

<extension-point id="listeners" name="Test Listeners"/>

Say we want our result dialog to pop up whenever tests are run. (This is a horrible user-interface long-term, but it serves to demonstrate the topic of this chapter.) The Fair Play Rule suggests we should use the same mechanism for our code that we offer to clients. We should notify all extensions the same way.

When should we load the extensions? One simple answer is to load them when Eclipse starts up. However, if all plug-ins loaded all extensions at startup, startup would be proportional to the number of installed plug-ins. The better solution is to lazily load the extensions the first time they need to be notified.

The getter method lazily computes the listeners as follows:

Example . org.eclipse.contribution.junit/JUnitPlugin

private List listeners; // Don't initialize
private List getListeners() {
  if (listeners == null)
    listeners= computeListeners();
  return listeners;
}

How do you load extensions

How do you load extensions?
How do you load extensions?

We don't want to start looking with ExtensionPoint, because it lives in an internal package. IExtensionPoint is in a published package (no “internal” in the package name), so we can begin looking at its clients.

When following the Monkey See/Monkey Do Rule, you'll often spend five or ten minutes at a time reading code. It's easy to interpret this as wasted time. You're learning all the time you're reading. In a year, your fluidity with Eclipse will increase to the point that you only occasionally have to spend extended stretches reading. In the meantime, you are getting functionality working and integrated into Eclipse.

The example we came to that is close to what we want to do is in ToolFactory (we've removed some extraneous detail):

Example . org.eclipse.jdt.core/ToolFactory

org.eclipse.jdt.core/ToolFactory
public static ICodeFormatter createCodeFormatter(){
  Plugin jdtCorePlugin = JavaCore.getPlugin();
  IExtensionPoint extension =
     jdtCorePlugin.getDescriptor().getExtensionPoint(
       JavaModelManager.FORMATTER_EXTPOINT_ID);
  IExtension[] extensions =  extension.getExtensions();
  for(int i = 0; i < extensions.length; i++){
    IConfigurationElement [] configElements =
      extensions[i].getConfigurationElements();
    for(int j = 0; j < configElements.length; j++){
      try {
        Object execExt= configElements[j].
          createExecutableExtension("class");
        if (execExt instanceof ICodeFormatter){
          return (ICodeFormatter)execExt;
        }
      } catch(CoreException e){
      }
    }
  }
}

The code shown above only loads the first extension, violating the Diversity Rule. We want to load all extensions.

The process of loading extensions is as follows:

  1. Get the extension point from the platform. (Remember that the platform reads all the manifest files on start-up, so our extension point will be there.)

  2. Get the registered extensions (IExtension). These are also created by reading the manifest files, looking for:

    <extension point=
      "org.eclipse.contribution.junit.listeners">.
    
  3. For each extension, get the XML elements inside it (IConfigurationElement):

    <listener class=
      "org.eclipse.contribute.junit.TestRunAction$Listener"/>.
    
  4. For each configuration element, create an object whose class will be based on the contents of the class attribute. Validate that the defined attributes are complete.

  5. Save the newly created extensions in a collection (instead of just returning the first one as in the ToolFactory example).

The plug-in registry (the root of the shadow world described above) gives us descriptions of all the extensions of our extension point discovered at load time. Starting there, we will get the extensions and the elements inside the extensions, and create our listeners.

Example . org.eclipse.contribution.junit/JUnitPlugin

private static final String listenerId=
   "org.eclipse.contribution.junit.listeners";

private List computeListeners() {
  IPluginRegistry registry= Platform.getPluginRegistry();
  IExtensionPoint extensionPoint=
     registry.getExtensionPoint(listenerId);
  IExtension[] extensions= extensionPoint.getExtensions();
  ArrayList results= new ArrayList();
  for (int i= 0; i < extensions.length; i++) {
    IConfigurationElement[] elements=
       extensions[i].getConfigurationElements();
    for (int j= 0; j < elements.length; j++) {
      try {
        Object listener=
          elements[j].createExecutableExtension("class");
        if (listener instanceof ITestRunListener)
          results.add(listener);
      } catch (CoreException e) {
        e.printStackTrace();
      }
    }
  }
  return results;
}

After we create the listener, we explicitly check to make sure it conforms to our expected interface (Conformance Rule). We are also careful to protect the creation of our listeners. We call the listener class's constructor, which could have problems (Safe Platform Rule) inside a try-catch block.

If we eliminate the dynamic listener and declare the same listener class as an extension, we get the same dialog. Now we are playing fair. We are using the same mechanism for behavior inside the plug-in as we intend to publish for external use.

We haven't yet looked at the Eclipse way to invoke extensions. Once we've done that, the functionality for Circle One is complete. We have a simple UI for running tests. Before doing so, we'll review:

  • We decided to open up our plug-in for further contribution and to use the same mechanism ourselves instead of cheating.

  • We declared an extension point, listeners, that would allow multiple objects to listen to test progress.

  • We loaded the extensions lazily, creating an ITestRunListener for each element in the extensions.

Forward Pointers

  • Defining an extension point schema file (.exsd)—PDE offers a guided way to define an extension in the manifest editor. This requires us to tell PDE more about our extension and its structure. This information is captured in an XML schema file with the suffix .exsd.

  • IExecutableExtension—. If you need additional arguments for the extensions created by the factory, define them in the XML and declare your extension to implement IExecutableExtension, and the parameters will be passed to your newly created object with a call to setInitializationData().

  • Activating a plug-in early—There are some rare situations when a plug-in needs to be loaded early (when Eclipse starts). Consider a plug-in that starts a server to which external clients need to connect. When a plug-in hosting such a server is loaded lazily, clients will never be able to connect to the server, because it isn't running. For these rare situations, Eclipse can activate a plug-in as soon as the workbench is up and running. To force early plug-in activation, use the org.eclipse.ui.startup extension point. Because this is an extension point with a potential for abuse, Eclipse lets users disable the early start-up of a plug-in with the preferences: Preferences > Workbench > Startup.



[1] See Martin Fowler, “Public versus Published Interfaces”, IEEE Software, March/April 2002.

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

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