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.
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.
1. Identity: The plug-in's name, unique identifier version, and author.
2. Dependencies: A list of what plug-ins, along with the corresponding versions,
that need to be loaded for this plug-in to work correctly.
3. Extention points: Interfaces offered or used by the plug-in.
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.
cd <directory where Eclipse was installed> eclipse -data c:\newpluginor for those running Linux:
./eclipse -data ~/newpluginWhen the application starts, a message saying the installation is completing will appear as the program initializes the new directory and starts-up.
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.
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.
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.
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.
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!
Have fun!