Creating an Eclipse Plug-in

Gene Sally

An introduction to the Eclipse environment and a study in how to build your own Eclipse plug-in.

Sidebar: About the Common Public License

Eclipse is an open source Integrated Development Environment (IDE) flexible enough to handle just about any programming language or software development chore. The open source licensing paired with a design from the ground-up for extensibility, make Eclipse a perfect substrate for commercial or in-house software development tools.

Eclipse grew out of tools development projects at an IBM subsidiary, Object Technologies International (OTI). When IBM began work on its next generation of development tools, OTI was charged with the responsibility for creating an extensible platform that could be extended to include third-party tools and utilities. Drawing on its experience with creating extensible developer tools, OTI produced the foundation of IBM's WebSphere product line and what would eventually be known as Eclipse.

IBM decided the best way to build a a strong following among end users and extension developers was to introduce Eclipse into the open-source community, and the Eclipse Project was formally started on November 5, 2001 with the initial contribution of the Eclipse framework and Java Development Toolkit, valued at $40 million dollars. With this substantial contribution, IBM hopes to benefit by incorporating improvements from the open-source community. Eclipse users receive the benefit of a no-cost, first-rate IDE. In addition, IBM's move challenges Microsoft's.NET platform IDE. Both are extensible platforms designed to allow third-party developers to easily integrate their tools offerings; however, Eclipse is an open-source project that allows any interested party to create extensions while .NET is proprietary, and tools development requires licensing and approval from Microsoft.

Using Eclipse to create extensions, called plug-ins, provides the developer with two very powerful tools: a consistent and rich user interface and a versatile internal model. The user interface allows the plug-in author to create extensions that look and function like other extensions, so even unrelated tools from different vendors can participate in a consistent user interface experience. The internal model exposed by Eclipse means that tools by other vendors can share data with Eclipse and each other.

Working in Eclipse

You can download Eclipse 2.1 from the official Eclipse web site at http://download2.eclipse.org/downloads/index.php for users in North America. For other locations, select http://www.eclipse.org/ downloads/index.php and pick a region closer to you for the fastest download.

When you start Eclipse, you enter a desktop space called the workbench (see Figure 1). The workbench contains one or more views. Each view conveys a specific type of information to the user. The term perspective describes a collection of views and their arrangement. Although Eclipse ships with several pre-defined perspectives, the user is free to create new perspectives from any of the views in the system and save them for future use.

Figure 1 shows the Resource perspective, containing the following views

The Eclipse online help is a great source for information on getting around in the Eclipse user interface. To learn more about working in Eclipse, Select Help | Help Contents and choose Workbench Users Guide / Getting Started from the tree on the left.

The remainder of this article describes the process of creating a simple plug-in along with providing some background information about Eclipse's behavior and architecture. As you see in the article, the plug-in you'll create will blend peacefully into the Eclipse user interface and politely interact with other plug-ins. Readers interested in adapting their own tools will be able to see how easy the Eclipse environment is to work with and how polished the user interface will look and feel when using the standard tool kits. In fact, Eclipse-based software products you see advertised today use the same plug-in architecture illustrated here.

Plug-ins

At a minimum, a plug-in consists of an XML file describing the plug-in; in practice, a plug-in also contains a jar file filled with the binaries for the project along with the name of a class serving as an entry point for the plug-in. When Eclipse starts, it scans a folder called "plug-ins" for directories containing a file aptly called plugin.xml. This file contains the following meta-data:

After locating all of the necessary information, Eclipse builds a dependency graph and begins loading the plug-ins into the system. After loading the plug-ins, the workbench configures the windows in the UI and the system is ready for use.

When Eclipse needs to load something from the plug-in it will first load the plug-in class. The initialization process calls the startup method that works like a constructor, allowing you to initialize any member variables and otherwise get the class ready for use. After startup, Eclipse will then invoke loadPreferenceStore to retrieve any preferences the user may have entered in a preference page.

A plug-in may only be loaded once. In order to weakly enforce this semantic, the class for the plug-in contains a static reference to itself and a method, getDefault, that returns a reference to the class. Since the constructor is not private, the possibility still exists to create multiple instances, so you must remember not do so.

Our Project

The plug-in for this article will expose the state of the document model through a view so you can learn about creating a plug-in while seeing how the Eclipse performs basic document handling. The plug-in will list the open documents and highlight the current one in a view. From the plug-in you'll be able to save and activate documents. Granted, this functionality already exists in Eclipse, but playing with this will give you a good understanding of the underlying object model and hopefully spur you on to try something more sophisticated.

Start Eclipse with a Fresh Workspace

When starting Eclipse, you need to specify a working directory or the current directory will be used for the project. For this article, we're going to start Eclipse in a new workspace directory by doing the following
cd <directory where Eclipse was installed>
eclipse -data c:\newplugin
or for those running Linux:
./eclipse -data ~/newplugin
When the application starts, a message saying the installation is completing will appear as the program initializes the new directory and starts-up.

New Plug-in Wizard

Wizards, in general, exist to automate the drudge work of common tasks while at the same time help novice users by providing reasonable default values for the tasks. Eclipse contains wizards for most common programming chores, along with some very sophisticated wizards to assist in creating new Plug-in modules. For this example, we're going to take advantage of the Plug-in Project Wizard to generate a skeleton project that we will populate with code for our sample project.

Create the skeleton plug-in by selecting File | New | Project. and then selecting Plug-in Development | Plug-in Project, then click Next.

The wizard will prompt you for a project name, enter docview and click Next. Accept the defaults in the Plug-in Project Structure, clicking Next to move to the next panel. Since this sample project will contain a view that exposes some of the underlying state of the Workbench, select "Plug-in with a view" and click Next.

At this point, we're not going to change any of the other panels in the Wizard, so you can press Finish now, or walk through the remainder of the wizard to see the different settings and options.

What the Wizard Created

1. docviewPlugin.java This file contains a singleton class that represents the plug-in. This class will always be loaded first, before any other of controls in the plug-in. In this class, you can add public methods and data that you want other components of the plug-in to use.

2. SampleView.java This class contains a view class and all of the supporting code necessary to populate the control with data. All of the coding for this article will occur in this class.

The plug-in created by the wizard is ready to run. Create a Runtime-Workbench Debug Configuration to launch an instance of Eclipse and open the plug-in's view by selecting Window | Show View| Other... and picking Sample Category / Sample View from the list of views.

Plug-in Life Cycle

Plug-ins have a life-cycle controlled by the workbench. When Eclipse starts it records the plug-ins that can be loaded but does not actually load the plug-in until necessary. During the load process, Eclipse invokes the startup() method on the class and, before unloading, calls shutdown(); the user should think of these methods as the constructor and destructor of the plug-in. In this example, the class docviewPlugin will remain unloaded until the user tries to load the SampleView class.

Views, or their abstraction, workbench parts, follow a similar convention, Eclipse invokes the init() method at start-up and the dispose() method at shutdown. After calling the init() method, the view's controls will be constructed.

Start Writing the Plug-in (The Zen of Views)

Plug-ins typically expose their functionality through views in the workbench. A view aggregates menu bars, context menus, and controls that work together. The important part about working with views is understanding the separation between the data displayed by the view, frequently called the model in the documentation, and the view itself. In the sample application, the model for the view is the data inside of Eclipse that will be cached in a temporary data structure declared in the SampleView, as follows:
private Map mapEditors = new HashMap();
In order to populate the cache, the view will traverse the internal document model in Eclipse to find the running editors and to register listeners for events that may require the view to be updated. The code for the listeners will add or remove references to editors in the cache based on what the user is doing in the user interface. This code resides in the createPartControl method of SampleView.
IWorkbenchWindow[] windows =
                   PlatformUI.getWorkbench().getWorkbenchWindows();
for (int currWindow =
    0; currWindow < windows.length; currWindow++) {
  windows[currWindow].addPageListener(pageEvents);
  IWorkbenchPage pages[] = windows[currWindow].getPages();
  for (int currPage = 0; currPage < pages.length; currPage++) {
    pages[currPage].addPartListener(partEvents);
    IEditorReference editors[] =   
          pages[currPage].getEditorReferences();
    for (int currEditor = 0;
        currEditor < editors.length; currEditor++)
    {

IEditorPart editor = editors[currEditor].getEditor(true); mapEditors.put(editor.toString(), editor); } } }

As you can see, the code starts at the Workbench, the top level user interface widget in the model, and retrieves the list of windows. For each window, the code then gathers a list of pages and, for each page, iterates over the editors, placing them into the cache that will be used to drive the user interface.

The wizard created some stock code for populating the view via the inner class ViewContentProvider implementing the IStructuredContentProvider interface. Recall that the Eclipse designers wanted to establish clear lines between data and display; the ViewContentProvider is responsible for returning an array of objects the view will display. Since this data resides in a HashMap, this routine just does data transformation.

public Object[] getElements(Object parent) {
return mapEditors.values().toArray();
}
Once the view has a list of items to display, it defers to another inner class, ViewLabelProvider, implementing the ITableLabelProvider interface, to figure out what text and graphics to display. The code populating the view hands this object a reference of one of the items returned in the getElements method in exchange for a text string and image for the user to see.
public String getColumnText(Object obj, int index) {
  if (obj instanceof IEditorPart) {
    return ((IEditorPart) obj).getTitle();
  }
  else {
    return "Object not a reference to an editor: "
        + obj.toString();
  }
}

In order to keep the data the view synchronized with the title of the editor in the interface, this method defers to the getTitle() method in the IEditorPart interface implemented by editor objects in Eclipse. The ITableLabelProvider interface has a similar method for returning a reference for the associated label called getImage() that works similarly to the getColumnText method, except it returns a reference to an image instead of a string.

With the code in place to display data, the project is now interesting, but not that useful, as it only displays data. The next section will show how to create actions that will allow you to perform some basic operations on the list of editors.

Actions

The Eclipse way of handling actions makes creating consistent user interfaces very easy. From a conceptual level, the functionality necessary to perform some operation exists in an instance of an object implementing the IAction interface. The IAction interface specifies the visual elements for the action, like menu text, tool tip information and icon, as well as the code necessary to perform the action. If you want the action to appear in a menu and toolbar, you create one instance of the action object and register it with the toolbar manager and the right click menu. The toolbar and the right click menu will have the exact same visual appearance.

The wizard created three actions as part of its stock code, two for the toolbars and one for the double-click. Our project will have two distinct actions: switching editors and saving a file. The double-click action will just be assigned a reference to the same action used to switch editors, to illustrate how the same action can be invoked from multiple locations in the interface. This code resides in the makeActions() method of SampleView.

action2 = new Action() {
  public void run() {
    ISelection selection = viewer.getSelection();
    IEditorPart currEditor = (IEditorPart)
((IStructuredSelection)selection).getFirstElement();
currEditor.getEditorSite().getPage().activate(currEditor);
    }
};
action2.setText("Switch to");
action2.setToolTipText("Switch to this editor");
action2.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().
getImageDescriptor(ISharedImages.IMG_OBJS_TASK_TSK));
doubleClickAction = action2;
One new thing in the above code is the object implementing the IStructuredSelection interface. Selecting from a list of items yields an object that contains a list of object references passed to the control when getElements was called. Because the view control allows the user to potentially reorder the display and select non-contiguous ranges of elements, the IStructuredSelection interface provides a clean method for iterating over the user's selection. In this case, we know that items returned by getElements can be cast to EditorPart since getElements returned an array of that type, so activating the editor means we just need to cast the Object reference to the correct type an invoke the method to activate the editor.

The action for saving the editor's contents is nearly identical to the action for activating the editor. The differences between the two are the method invoked on the IEditorPart reference and the fact this action operates on all items selected by the user, not just the first.

action1 = new Action() {
  public void run() {
    ISelection selection = 
        viewer.getSelection();
    for (Iterator iter = 
        ((IStructuredSelection)
        selection).iterator();
        iter.hasNext();) {
      IEditorPart currEditor = 
          (IEditorPart) iter.next();
if (currEditor.isDirty()) {
  currEditor.doSave(null);
}
    }
  }
};
(The full source for this code can be downloaded from <www.cuj.com/code/>.)

Now our sample application is complete!

What's Next

As you can tell, Eclipse contains powerful tools to help you get started down the path of creating Plug-in extensions. You can customize Eclipse to suit your own or your company's needs. Most plug-ins can be generated in skeleton form by a wizard and extensive on-line help describes the environment and API. And when you're really stuck, you can always find similar functionality in Eclipse and read the source to determine how the experts did it.

Have fun!

About the Author

Gene Sally is a Senior Software Engineer with TimeSys Corporation and was the technical lead for TimeSys' Eclipse-based IDE for C/C++/Java application development, TimeStorm.