Sunday, April 1, 2012

Writing Eclipse Plugins Tutorial - Part 1

Eclipse is one of three most popular java development IDEs. One reason for its success is its extensibility. Writing eclipse plugins can be very fast and easy for anyone who knows what to do and already done it.

Unfortunately, doing something in Eclipse for the first time can be very time consuming and frustrating. Eclipse framework is huge, powerful and sometimes complicated. It may be difficult to figure out which features are available and how to use them.

This tutorial explains basics of all eclipse features needed to automate a simple java refactoring task. It shows how to add new items into the menu and how to analyze, modify and format java source code. It also shows how to use dialogs to communicate with users.

It is split into two parts. This post explains all needed theory. By the end of this part, you will already know enough about Eclipse framework to be able to finish the plugin with just a little searching. Practically speaking, we will create a plugin that adds new items into the menu and collects all information needed to perform the refactoring.

Next part of this tutorial will show how to create dialogs and how to modify java sources from the plugin. It was not released yet.

Table of Contents

Sample Plugin

We create sample plugin with two features:
  1. check non-null parameters - modifies selected method to check whether its parameters are not null,
  2. custom generate toString - adds toString method to selected class.

Both features will add a new item into the source menu. Check non-null parameters item will be enabled only if user selects a java method. The feature shows a dialog which allows user to choose a subset of method arguments. Selected method is then modified to check whether chosen arguments are null:
if (arg1==null || arg2==null || ... || argn==null)
  throw new IllegalStateException("The parameter may not be null.");

Custom generate toString item will be enabled only if user selects a java class. It will show a dialog with list of all its properties. The user selects which properties belongs to toString method. If user selected less than four properties, the feature adds following code to the class:
@Override
public String toString() {
  StringBuilder builder = new StringBuilder(this.getClass()
    .getSimpleName());
  builder.append(" [ ");
  builder.append(b).append(", ").append(c).append(" ]");
  return builder.toString();
}

If user selected less four of more properties, the feature adds following code to the class:
public String toString() {
  StringBuilder builder = new StringBuilder(this.getClass()
    .getSimpleName());
  builder.append(" [ \n");
  builder.append("    a" + ": ").append(a).append("\n");
  builder.append("    b" + ": ").append(b).append("\n");
  builder.append("    c" + ": ").append(c).append("\n");
  builder.append("    d" + ": ").append(d).append("\n");
  builder.append(" ]");
  return builder.toString();
}

That is all. The plugin is available on Github.

IDE

Each Eclipse release comes in multiple flavors. The version best suitable for plugin writers is called 'Eclipse for RCP and RAP Developers'. The RCP stands for 'rich client platform' which is just another name for Eclipse platform.

Download and install 'Eclipse for RCP and RAP Developers' from the download page.

Target Platform

First thing to do is to configure target platform. Target platform is another instance of eclipse. It represents the minimal configuration your plugin will work with. Your plugin will be compiled against the target platform. It will also be installed into it and will run inside it whenever you will want to test it.

The whole workspace can have only one active target platform. It is not project specific, although it would make more sense.

Easiest Target Platform
The easiest is to develop for the same eclipse version as you are running on. This is the default option. If this is the case, all you have to do is to add eclipse SDK into it.

Install Eclipse SDK:
  • Go to 'Help' -> 'Install New Software...'.
  • Choose your eclipse update site, in our case the 'The Eclipse Project Updates' http://download.eclipse.org/eclipse/updates/3. update site.
  • Check both Eclipse SDK and Eclipse Platform SDK.
  • Click next, accept license and finish the installation.

That is it. Nothing else is necessary, you are ready to create first plugin project.

Advanced Target Platform
It is possible to download and maintain the target platform separately. Use this option if you want to be compatible with an older release or if you want to have greater control over the target platform configuration.

If you are not interested, skip to the next chapter.

Create Target Platform
Find and download SDK of whatever you assume your users will have. You may use also a 'regular' version if you can not find the SDK. However, if you download SDK, you will have source code and javadocs available.

For example, our plugin requires Eclipse Indigo release. The version number of Eclipse Indigo is 3.7, therefore we have to download Eclipse 3.7 SDK. The complete list of even older Eclipse SDKs versions is available on archived releases page.

Our plugin will depend only on the eclipse itself, so all we have to do is to unpack downloaded SDK somewhere. If it would require additional plugins, we would have to hunt and download their SDKs too. We would also unpack them and copy into the same directory as eclipse SDK.

Configure Target Platform
Now, we have to configure our RCP eclipse to use prepared target platform.

Define and activate the target platform:
  • Go to 'Window' -> 'Preferences' -> 'Plug-in development' -> 'Target Platform'.
  • Click Add.
  • Choose 'Nothing: Start with an empty target definition' and click next.
  • Fill in target name.
  • Click Add, choose directory, browse to the unpacked eclipse SDK and finish.
  • Check new target platform as 'Active'.

Finally, go to 'Plug-in Development' preference page and check 'Include all plug-ins from target in Java search'.

Simple Plugin Project

This chapter shows how to create a simple plugin project and how to debug it. The simple plugin does nothing useful. It is only able to show a message to prove that it exist.

In the end of this chapter, we will remove the sample message and end up with an empty plugin skeleton.

Create Plugin Project
We will use Eclipse wizard to generate the plugin. Invoke the wizard from the package explorer:
  • right click in the package explorer,
  • choose 'New' -> 'Other..'.,
  • select 'Plug-in Project' and click Next.

The wizard has multiple pages. Configure the project name and target platform on the first one. You can use any project name as you want, but the convention is to name the project after the root java package. For example, as we want to put all classes into the org.meri.eclipse.defensiveapitools package, our project name will be org.meri.eclipse.defensiveapitools

The target platform field contains a version of Eclipse you are developing for. If someone wants to use your plugin, he will be required to download Eclipse with equal or bigger number. An old incompatible Eclipse will refuse to load it. We are fine with developing for current Eclipse, so we will choose 3.7.

Click 'Next'.

Second page contains basic project information. Fill in the id, plugin version, name and provider however you like. The first important parameter is the execution environment. The plugin will run only on the specified or never java. An eclipse running on older JVM will simply ignore it. We have chosen J2SE-1.6.

Verify that:
  • check-box 'Generate an activator, a Java ...' is checked,
  • check-box 'This plugin will make contributions to the UI' is checked,
  • the answer to 'Would you like to create a rich client platform application' is no.

Click 'Next'.

Choose 'Hello, World Command' template and click 'Finish'. This will create new plugin with sample menu item.

Debug The Plugin
The plugin works only inside a running Eclipse. Eclipse supports both manual and automatic JUnit testing. In both cases, the plugin is installed and run inside the target platform configured in the first chapter.

This chapter shows only how to do the manual testing. Refer to other resources to learn how to write junit tests for eclipse plugins.

Install and run the plugin:
  • right click on the plugin project,
  • click 'Debug As' -> 'Eclipse Application'.

The system will start new eclipse instance. Its main menu has a new entry called 'Sample Menu'.

Now, you have two running eclipse instances. One is for development and another is for testing. The testing eclipse runs inside the development one. All debugging tools are available. You can place breakpoints, inspect variables and so on:
  • open generated SampleHandler class in the development eclipse,
  • put breakpoint inside the execute method,
  • go back to the test eclipse,
  • choose 'Sample Menu' and 'Sample Command'

The execution will stop on the new breakpoint.

Clean the Plugin
We have seen that the plugin works, so we can remove generated sample menu from it.

Remove sample menu item and its handler:
  • Open plugin.xml file, go to extensions tab and remove all extensions.
  • Locate and delete generated SampleHandler class.

The plugin tag inside the plugin.xml should be empty now. Open plugin.xml file and go to the plugin.xml tab:
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
</plugin>

Adapter Pattern

Eclipse is a huge framework and there is an infinite amount of plugins you can install into it. This creates a huge system of features and plugins that have to cooperate with each other. To keep this system as decoupled and as extensible as possible, Eclipse framework uses adapter design pattern. This pattern is so common that you will probably run into it no matter what plugin you will write.

This design pattern converts a class or interface into another class or interface. The class that performs the conversion is called an adapter. The pattern has two different flavors and eclipse framework supports both. The difference between those two flavors is in who creates the adapter. It can be done either directly by the object to be converted or by an independent adapter factory.

First sub-chapter shows how to write an adapter. Second sub-chapter is about objects able to create their own adapters and third is about independent adapter factories. Last sub-chapter puts everything together and shows how to convert an object of unknown type.

Adapter
Adapter is an object that converts one type into another type. It must represent the other type e.g., if it converts objects to a class, then it must extend that class. If it converts them to an interface, then it must implement that interface.

Typically, an adapter converts object that has all needed functionality, but does not have the right API. Otherwise said, typical adapter contains very little logic. It only wraps the original object and delegates all work to it.

Following adapter is able to convert an implementation of Minus interface into the Plus interface:
public class MinusToPlusAdapter implements Plus {

  private final Minus adaptee;
  
  public MinusToPlusAdapter(Minus minus) {
    super();
    this.adaptee = minus;
  }

  @Override
  public int plus(int x, int y) {
    return adaptee.minus(x, -y);
  }

}

Adaptable Objects
In a simpler version of this design pattern, the object to be converted creates its own adapters.

We will show how to create adaptable objects compatible with eclipse framework. The rest of this sub-chapter lists advantages and disadvantages of this adapter pattern flavor.

Eclipse Implementation
An adaptable object must implement IAdaptable interface. The interface has only one method getAdapter(Class type). It returns either an adapter to the requested type or a null.

An adaptable object:
public class MinusImpl implements Minus, IAdaptable {

  public int minus(int x, int y) {
    return x - y;
  }

  public Object getAdapter(Class type) {
    if (Plus.class.equals(type))
      return new MinusToPlusAdapter(this);

    return null;
  }

}

The getAdapter method may or may not call the global adapter manager to create an adapter. Some Eclipse objects call it, some do not.

When To Use It
Adaptable objects are easy to use and debug. The pattern helps to keep class and interface hierarchies clean. It also provides some decoupling between converted type and required type.

Use it when:
  • Converted type already implements too many interfaces.
  • Converted type have to be compatible with two different features and each requires it to extend another class.
  • You want to keep the converted type and required interface/class separated.
  • The adapter needs access to private fields of methods.

Limitations
Plugin that relies only on this version of the pattern is not extensible. Third party plugins are not going to be able to add new adapters into it.

Third Party Adapter
Second version of this pattern uses an independent factory to create adapters.

We will show how to call adapter factories within eclipse framework. The rest of this sub-chapter lists advantages and disadvantages of this adapter pattern flavor.

Eclipse Implementation
Adapters are created by adapter factories. To achieve their independence, adapter factories are hidden behind global adapter manager. Client code never communicates directly with an adapter factory.

Adapter manager exposes a getAdapter(Object obj, Class type) method which delegates calls to installed adapter factories. This method returns either an adapter or a null.

Use adapter manager to create an adapter:
(Plus) Platform.getAdapterManager().getAdapter(minus, Plus.class);

Adapter factories are registered into adapter manager either programmatically or in plugin.xml. As our plugin does not need to do this, we will omit that information. Read either eclipse corner article or Eclipse Adapters tutorial for further information.

Note: the adapter factory is not allowed to call the getAdapter(Class type) method of converted object. It would cause an infinite loop, because the getAdapter method of an adaptable object may call adapter manager.

When To Use It
Adapter factories lead to high decoupling and extensibility. The decoupling of the original object and required type is absolute. There is no dependency between them. Instead, an independent adapter factory depends on both.

Asking an adapter manager for adapter makes your feature extensible. Anyone can contribute adapters to integrate his objects with your plugin.

Use it to:
  • make your plugin extensible by third party plugin writers,
  • integrate two plugins.

Limitations
The decoupling comes with a higher complexity. If something goes wrong, it may be difficult to find out where the faulty adapter came from. Similarly, finding out which adapters are available is more time consuming than in the 'adaptable objects' version of this pattern.

If you expect others to extend your plugin, document which adapters are expected. Document also adapter factories you are adding into the system. Hunting down all that information in xml files can be very time consuming.

Adapt an Object
The most correct way of adapting an object to the desired interface makes no assumptions about the adapter pattern version used by the object.

Follow three steps to adapt the object to the needed interface or class:
  • If the object implements or extends the desired interface, use the object.
  • If the object can adapt itself, use the adapter provided by the object.
  • Use the global adapter manager to adapt the object.

Adapt an object to a type, full implementation:
public static Object getAdapter(Object obj, Class type) {
  // if the object implements or extends the desired interface, use it
  if (type.isInstance(obj))
    return obj;

  // if the object is able to adapt itself, let it do it
  if (obj instanceof IAdaptable) {
    IAdaptable adaptable = (IAdaptable) obj;
    Object adapter = adaptable.getAdapter(type);
    if (adapter != null)
      return adapter;
  }

  // delegate to the global adapter manager
  return Platform.getAdapterManager().getAdapter(obj, type);
}

Abstract Syntax Tree vs Java Model

Eclipse framework uses two different hierarchies to represent java source code. First is called java model and second is called abstract syntax tree. These hierarchies are mostly independent and have different purposes and usage.

Java model hierarchy is lightweight, fault tolerant, fast to re-create and its abilities are limited. Abstract syntax tree provides full control over java source code, but is much slower to recreate. For these reasons, Eclipse framework uses java model hierarchy wherever possible. Abstract syntax tree is used only if necessary.

First sub-chapter shows how to enable these hierarchies in the plugin. Second sub-chapter contains overview of java model and last contains overview of abstract syntax tree.

Plugin Dependency
Both hierarchies belong to the org.eclipse.jdt.core plugin. We have to add jdt core plugin to plugin dependencies if we want to use them:
  • Open plugin.xml and go to the 'Dependencies' tab.
  • Click Add button in the Required Plug-ins section.
  • Select the org.eclipse.jdt.core plugin.

Eclipse will automatically add plugin dependency into Require-Bundle section of MANIFEST.MF file.

Java Model
Java model is a set of interfaces that represent java methods, classes, interfaces and other elements. It is lightweight, fault tolerant and fast to re-create.

Java model hierarchy provides basic information about java code structure. It is also able to make simple changes on it. For example, it is possible to use it to rename or add new methods, classes or interfaces.

The main disadvantage of the java model hierarchy is that it does not contain full information about underlying source code. For example, it does not contain methods bodies. As a result, it is not possible to perform more complicated source code changes.

The root of the java model hierarchy is IJavaElement interface. All interfaces that extend it belong to this hierarchy.

Abstract Syntax Tree
Abstract syntax tree is a set of classes that represent java methods, classes, interfaces and other elements. It provides full information about java code structure and is able to make any changes on it.

The main disadvantage of the abstract syntax tree hierarchy is that it is slower to re-create than java model. It is also less fault tolerant.

The root of the abstract syntax hierarchy is ASTNode class. All classes that extend it belong to this hierarchy.

User Interface

This chapter contains quick overview of eclipse user interface. We will explain only absolute basics of those parts of the UI that are going to be needed later in this post.

First sub-chapter is about the most prominent part of Eclipse UI: views and editors. Second sub-chapter deals with the selection service.

Parts, Views and Editors
Two most important visual components of Eclipse user interface are views and editors. Both editors and views can show any content and present it in any form. Both can be editable or read-only, but only editor is able to keep the content in an unsaved dirty state.

Common Properties
Both editors and views are called parts. Eclipse documentation uses the word part as a shortcut for 'an editor or a view'.

Each part must have unique id. Technically, the id is an arbitrary string. However, the convention followed by all official plugins is to prefix the id with the plugin name. As the plugin name is usually equal to plugin root package name, the convention guarantees id uniqueness.

Each part must implement IWorkbenchPart interface. The interface was extended twice, leading to IWorkbenchPart2 and IWorkbenchPart3 interfaces. You may either implement them directly, or extend the default implementation called WorkbenchPart.

All parts are adaptable. The interface IWorkbenchPart extends the IAdaptable interface. The default implementation delegates the getAdapter method to the global adapter manager.

Editors
An editor is typically used to edit or browse a document or input object. Changes made in editor are not stored immediately. An editor with changed content is in dirty state until the save operation is invoked. If the editor is closed without saving, all unsaved changes are lost. Its default implementation is called EditorPart.

All editors appear in the same region of the page and can not be minimized. There can be several instances of the same type of editor.

Editors toolbar is shown together with the global toolbar and their menu appears to be a part of the main menu.

A document or input object edited by the editor is identified using an instance of IEditorInput interface.

Views
A view is typically used to navigate a hierarchy of information, open an editor, or display additional information for the thing opened in an active editor. Modifications made in a view are saved immediately. Its default implementation is called ViewPart.

Views can be moved to any part of the page and can be minimized. There is generally only one instance of a given view per workbench page.

Each view has its own local toolbar and menu. They are also allowed to contribute buttons to the global toolbar and menu items to the main menu.

Selection
Eclipse selection system is fairly standard. Each view and editor generates its own selection. The framework has also a global selection. It usually contains selection of the active part.

If the plugin needs to know selected objects no matter where they come from, it has to use the global selection. If the plugin wants to integrate itself only with few views and editors, it may listen to their selections only.

This chapter shows how to interpret the selection once you get it. How to get the current selection is described in an Eclipse corner article.

Selected Objects
Current selection always implements the ISelection interface. That interface is extremely minimalistic. Its only method is able to tell whether the selection is empty of not.

To get more detailed information, you have to cast it to one of its subtypes. Two sub-types are interesting for our plugin:
  • ITextSelection - information about selected text. If no text is selected, contains the cursor position.
  • IStructuredSelection - contains a list of selected objects.

Structured selection can contain any types of objects. The best way to convert them to required classes or interfaces is to use the adapter pattern. Simple instanceof and cast may not be enough. Objects flying in eclipse are often adaptable to many types, but they implement or extend only some of them.

Eclipse Menus

Eclipse menu system is surprisingly rich and complicated. There are at least five menu types. The same or similarly looking menu can be invoked in multiple ways. To make it even more interesting, Eclipse has two different frameworks able to contribute new items into all those menus.

Each eclipse menu framework has its own chapter and those two chapters follow this one. This chapter contains only an overview of various menu types, an overview of the menu where our plugin adds its items and overview of those two menu frameworks.

Menus Types
Eclipse has five menu types:
  • Context menu - menu invoked by a mouse right click. Sometimes called also popup menu.
  • Main menu - menu always visible on the top of GUI.
  • Main toolbar - always visible toolbar available under the main menu.
  • View menu - menu available in a view. Click on a white down arrow in a view to invoke it.
  • View toolbar - a little toolbar available in most views.

Unlike views, editors do not have their own menu or toolbar. They always contribute to the main menu or to the main toolbar.

Source Menu
Source menu contains items like 'Organize Imports' or 'Generate Delegate methods...' and is a logical place for our two features. It was contributed by java development tools plugin (JDT).

This menu is located either in the main menu or in the context menu. Both locations show it only under some circumstances.

Main menu contains source menu item only if the active view or editor supports it. Following list contains examples of views and editors that causes the source menu to appear. Activate any of them to see it in main menu:
  • Java Editor,
  • Type Hierarchy View,
  • Package Explorer View,
  • Project Explorer View.

Context menu contains source menu item if at least one selected item represents java file, class, package method or other java item. Of course, that includes text selection inside java source editor. Activate context menu with source sub-menu:
  • open java source file in an editor and right click inside,
  • select java file or class in package explorer and right click.

The source menu can be invoked also with shortcut 'Alt+Shift+S'.

Menu Frameworks
Eclipse has two different frameworks able to contribute new items into menus:
  • the actions framework,
  • the command framework.

The actions framework is older and deprecated. The command framework is newer, flexible and little bit more complicated. It is superior and should be used for any new functionality.

The command framework is compatible with most, but not all menus. Some menus have not been rewritten yet, so you have to use the actions framework to contribute to them. For example, the source sub-menu in the main menu is compatible only with the actions framework.

Some menus are incompatible with both frameworks. The source menu invoked by the shortcut CTRLS + ALT + S is such menu. It is impossible to contribute to it.

Note: Impossible to contribute to menus are rare.

The Command Framework

The command framework is able to add items into most eclipse menus. It was designed as mini model view controller and separates UI from actions to be performed.

This chapter contains command framework basics. We will explain command framework components and use them to add new items into the source menu. Then, we will show how to make menu items enabled and disabled. New menu items are going to be enabled only if they would be useful on the current selection.

Overview
New addition to the menu is called a menu contribution. Each menu contribution needs to know where to draw new items, which items to draw and what should happen when someone clicks them.

Most Eclipse menus have unique id assigned. If you want to contribute to the menu, you have to find that id, compose so-called location URI from it and assign it to the menu contribution.

Among other things, each menu contribution can add separators and commands into the menu. Separators are those grey lines between different menu parts. Commands placed inside a menu represent clickable menu items. Each has a label, may have an icon and can be either enabled or disabled.

However, the command is not able to perform an action. It is only an abstract thing meant to separate GUI from the real action. Real work is done inside command handlers.

Menu Id
Menu id is an arbitrary string assigned to the menu or sub-menu. Each menu may have multiple ids. Finding menu id of an Eclipse menu can get very frustrating and difficult.

If you are lucky, the id will be revealed with the Plugin Spy available in Eclipse RCP. It is able to show information about menu items, UI elements and running Eclipse plugins. If you are unlucky, you will have to find the id on your own.

Top Level
Things are easy if you wish to add an item to the menu or toolbar top level.

List of general application menus:
  • The main Eclipse menu uses org.eclipse.ui.main.menu. The id refers only to the menu top level, e.g. the item will be placed together with 'File', 'Edit' and 'Help'.
  • The Eclipse toolbar uses org.eclipse.ui.main.toolbar.
  • The context menu uses org.eclipse.ui.popup.any id.

If you wish to contribute to menu or toolbar inside some view, use the view id. Luckily, the Plugin Spy can help to find the view id. Open the view in an RCP Eclipse and press 'Alt + Shift + F1'. Plugin Spy will open a popup with view information:

Sub-menu With Plugin Spy
Finding an id of a sub-menu can be more complicated because the Plugin Spy may not be able to find it.

Use 'Alt + Shift + F2' shortcut to start the Plugin Spy and open menu you want to contribute to. Click on any menu item and Plugin Spy will show various information about it.

Use Plugin Spy to get the source menu location URI:
  • open any java file in the development eclipse,
  • press 'Alt + Shift + F2',
  • right click inside the java file
  • hover over the 'Source' menu item,
  • click on the 'Clean Up...' item.

Eclipse will show following pop-up:

If the popup contains location URI, then we are almost there. Copy and paste it somewhere. Warning: Plugin Spy popup adds new line before and after copied text, so it may look like nothing was copied.

The part between : and ? is the menu id. For example, if the plugin spy shows
menu:org.eclipse.jdt.ui.source.menu?after=CleanUp
, the menu id is
org.eclipse.jdt.ui.source.menu

The Other Way
If the Plugin Spy does not show the location URI, it means that menu item or the menu itself was not rewritten into command framework yet. This is how Plugin Spy popup of an old menu item looks like:

Your best bet is to google around and look into plugin.xml of plugins that contribute to that menu. Chances are, that someone had the same problem and managed to solve it.

You can start with an old but still good list of context menus available on jdg2e. If the menu existed already in Eclipse 3.0, it is listed there along with its menu id. The source menu is not available in that table.

However, the id of its parent menu, e.g. the menu available if you right click in any java file, is #CompilationUnitEditorContext. This would be an acceptable second prize.

Plan B
While the contribution to top level menus with the command framework is easy, unknown menu ids make contributions to various sub-menus difficult or even impossible. If you have no luck finding it, you have to use the old actions framework.

Location URI
Location URI string has three parts: scheme, menu id and placement modifier:
<scheme>:<menu-id>[?<placement-modifier>]

Eclipse has three schemes:
  • menu - either main application menu or a view menu,
  • toolbar - either main application toolbar or a view toolbar,
  • popup - a context menu, e.g. any menu invoked by a right click.

The placement modifier is optional. It has the form <placement>=<id>. The placement part is either after or before. The id is either separator name, menu ID, or item ID. Be careful, if the menu does not contain specified id, it will ignore your contribution.

By convention, each menu should declare a special item with id 'additions'. This is where menu authors through that you should place your items. You do not have to respect their wish and some menus do not have such item. You can place your items wherever you like, even if the special additions item is in the menu.

Few examples:
  • menu:org.eclipse.ui.main.menu?after=file - add item to the main menu, between File and Edit sub-menus.
  • toolbar:org.eclipse.ui.main.toolbar - add item to the main toolbar.
  • menu:org.eclipse.ui.navigator.ProjectExplorer - add item to the menu available in the project explorer view.
  • toolbar:org.eclipse.ui.navigator.ProjectExplorer - add item to the toolbar available in the project explorer view.
  • popup:org.eclipse.ui.popup.any - add item to the context menu.

Place the item into the source menu without being picky about the exact location:
popup:org.eclipse.jdt.ui.source.menu

Note: We omitted the optional exact location in our location URI, because it can be tricky. If you wish to specify also the exact location, you have to be sure that selected place exists every time the menu is invoked. Unfortunately, just because the menu item looks the same, it does not mean that it really is the same menu item.

For example, if we would add ?after=CleanUp in the end of the previous location, our menu item would be placed right after the 'Clean Up...' item in the sources sub-menu invoked by right click in java editor. However, it would be invisible in the source sub-menu invoked by right click in package explorer view.

Command
A command is an abstract thing that represents an action and has an unique id. It can be placed into the menu and you can assign a shortcut to it.

Commands are configured in plugin.xml file. Open it and go to extensions tab. Use the left part named 'All Extensions':
  • Click 'Add' and choose org.eclipse.ui.commands extension point.
  • Right click on the new extension point and choose 'New' -> 'command'.
  • Fill in the id. The convention is to use plugin name as prefix of the command id.
  • Fill in the name.

We created two commands. One named 'Generate Custom toString' and another is named 'Check Non-Null Parameter'. Their ids are:
  • org.meri.eclipse.defensiveapitools.generatetostring
  • org.meri.eclipse.defensiveapitools.checknonnullparameter

Eclipse automatically generates corresponding xml and places it to the plugin.xml file. Go to the plugin.xml tab to see it:
<extension 
      point="org.eclipse.ui.commands">
   <command
      id="org.meri.eclipse.defensiveapitools.generatetostring"
      name="Generate Custom toString">
   </command>
   <command
      id="org.meri.eclipse.defensiveapitools.checknonnullparameter"
      name="Check Non-Null Parameter">
   </command>
</extension>

Menu Contribution
Menu contribution is an addition to menu. It knows where to draw itself and contains list of items that should be drawn. All items placed into the menu contribution are shown together, in the order defined by their position in that list.

We will configure a new menu contribution and add two menu items and two separators in it. Adding other element types into it is analogical.

Menu Contribution
Menu items are configured in plugin.xml file. Open it and go to extensions tab. Use the left part named 'All Extensions':
  • Click 'Add' and choose org.eclipse.ui.menus extension point.
  • Right click on the new extension point and choose 'New' -> 'menuContribution'.
  • Fill in the locationURI.
  • Set allPopups to true.

Recall, that the source context menu location uri is:
  • popup:org.eclipse.jdt.ui.source.menu

Command
Add commands to the menu contribution:
  • Right click on the menu contribution and choose 'New' -> 'command'.
  • Click Browse and find previously created command.
  • Fill in label.

Eclipse automatically generates corresponding xml and places it to the plugin.xml file. Go to the plugin.xml tab to see it:
<extension
      point="org.eclipse.ui.menus">
   <menuContribution
         allPopups="true"
         locationURI="popup:org.eclipse.jdt.ui.source.menu">
      <command
            commandId="org.meri.eclipse.defensiveapitools.generatetostring"
            label="Generate Custom toString"
            style="push">
      </command>
      <command
            commandId="org.meri.eclipse.defensiveapitools.checknonnullparameter"
            label="Check Non-Null Parameter"
            style="push">
      </command>
   </menuContribution>
</extension>

Test the menu. Run or debug the plugin and open any java file in the test Eclipse. Right click and choose the Source sub-menu. The menu contains two new items. As our commands have no handlers assigned, both items are disabled.

Separator
Add separator to the menu contribution:
  • Right click on the menu contribution and choose 'New' -> 'separator'.
  • Set visible to true.
  • Fill in name.

The order of elements in the menu contribution defines the order of corresponding items in the menu. Drag and drop new separator to the place where you would like to see it.

Eclipse automatically generates corresponding xml and places it to the plugin.xml file. Go to the plugin.xml tab to see it:
<menuContribution
      allPopups="true"
      locationURI="popup:org.eclipse.jdt.ui.source.menu">
   <separator
         name="org.meri.eclipse.defensiveapitools.begin"
         visible="true">
   </separator>
   ... commands ...
   <separator
        name="org.meri.eclipse.defensiveapitools.end"
        visible="true">
   </separator>
</menuContribution>

Test the menu again. Run or debug the plugin and open any java file in the test Eclipse. Right click and choose the Source sub-menu. Our menu items are surrounded by menu separators.

Command Handler
The command handler is the class that executes an action whenever users clicks on the menu item. Once we assign it to the command, the menu item created in previous chapter will be enabled.

This section is divided into three part. First two sections show how to create and configure a dummy handler. Third section explains where the handler can get the information about currently selected items, active editor and other UI state.

Simplest Command Handler
A command handler must implement the IHandler2 interface. The easiest way to implement it is to extend the abstract AbstractHandler class. This class provides standard implementation of all necessary methods except the execute method.

The execute method is called whenever user invoked the command. It has one parameter which contains information about the current state of the application. The execute method must return null.

Our first command handler is very simple:
public class GenerateToStringHandler extends AbstractHandler {

 @Override
 public Object execute(ExecutionEvent event) throws ExecutionException {
  System.out.println("GenerateToStringHandler");
  // must return null
  return null;
 }

}

Basic Configuration
The only thing we have to configure is which commands should be handled by our handler.

Note: It is also possible to assign multiple handlers to one command, but we will not do it. It is an advanced topic discussed in additional resources.

As usually, command handlers are configured in plugin.xml file. Open it and go to extensions tab. Use the left part named 'All Extensions':
  • Click 'Add' and choose org.eclipse.ui.handlers extension point.
  • Right click on the new extension point and choose 'New' -> 'handler'.
  • Browse for the command id.
  • Browse for the class implementing the command handler.

Eclipse automatically generates corresponding xml and places it to the plugin.xml file. Go to the plugin.xml tab to see it:
<extension
      point="org.eclipse.ui.handlers">
   <handler
         class="org.meri.eclipse.defensiveapitools.generatetostring.GenerateToStringHandler"
         commandId="org.meri.eclipse.defensiveapitools.generatetostring">
   </handler>
   <handler
         class="org.meri.eclipse.defensiveapitools.checknonnullparameter.CheckNonNullParameterHandler"
         commandId="org.meri.eclipse.defensiveapitools.checknonnullparameter">
   </handler>
</extension>

Run or debug the plugin and open any java file in the test Eclipse. Right click and choose the Source sub-menu. The 'Generate Custom toString' menu item is enabled. If you click on it, the command handler will print 'GenerateToStringHandler' into console in RCP Eclipse.

Implementation
As we are going to invoke our feature from two different places, our command handler will delegate all real functionality to yet another class.

That another class will need some information about the context surrounding the command. Namely, 'Generate Custom toString' needs to know which class was selected and 'Check Non-Null Parameters' needs to know which method was selected.

Note: Eclipse generates mini-selection every time you place the cursor somewhere. You do not have to highlight the text inside editor.

The execute method obtains an instance of ExecutionEvent as a parameter. The execution event has reference to application context which contains various information about the state of the eclipse.

Use the HandlerUtil to get that information out of the execution event. Handler util is a static class and has a lot of getWHATEVER methods. We will need four of them:
  • getCurrentSelection - returns current selection,
  • getActivePartId - returns id of active view or editor,
  • getActiveEditorInput - returns object edited by the opened editor,
  • getActiveShell - returned object will be needed for our plugin dialog.

private static final String JAVA_EDITOR_ID = 
"org.eclipse.jdt.ui.CompilationUnitEditor";

public Object execute(ExecutionEvent event) throws ExecutionException {
  //this object is needed to render wizards, messages and so on 
  Shell activeShell = HandlerUtil.getActiveShell(event);
  //get selected items or text 
  ISelection currentSelection = HandlerUtil.getCurrentSelection(event);

  //identify active GUI part
  String activePartId = HandlerUtil.getActivePartId(event);
  if (JAVA_EDITOR_ID.equals(activePartId)) {
    //get edited file
    IEditorInput input = HandlerUtil.getActiveEditorInput(event);
    //currentSelection contains text selection inside input file
    //... locate class selected in that file ...
  } else {
    //currentSelection contains all selected classes 
    //... collect all selected classes ...
  }

  return null;
}

Enabling, Disabling and Visibility
The menu item does not have to be visible and enabled all the time. Most commands are not universally usable.

Both our features are going to be visible all the time. However, we will have them enabled only under some conditions. 'Check Non-Null Parameters' will be enabled:
  • inside java editor,
  • if the current selection contains only modifiable java methods.

'Generate Custom toString' will be enabled:
  • inside java editor,
  • if the current selection contains only modifiable java classes or source files.

First, shortest section shows where to configure visibility and enablement. Next one describes xml language used to define visibility and enable conditions. Third section uses that language to test active editor and fourth tests selected objects. Final two sections put everything together and show conditions needed for our plugin.

Visible vs Hidden
If you want to make the menu item invisible, use the visibleWhen tag on a command reference inside a menu contribution. If you want to make the item disabled, use the enabledWhen tag on the command handler. Both tags works exactly the same way.

Open plugin.xml in an editor and go to the extensions tab. Right click either on the reference inside a menu contribution or command handler and choose either visibleWhen or enabledWhen.

Expression Language
If you right click on visibleWhen or enabledWhen tag, Eclipse will show a list of possible sub-tags. All those tags are part of xml boolean expression language and we will use them to define a condition. If the condition is satisfied, the menu item will be visible or enabled. If the condition is not satisfied, the menu item is either disabled or invisible.

One warning: not each tag shown on the list is directly usable. In this case, Eclipse simply shows all tags that defines a condition.

Usage of all listed tags is described on Eclipse help page. We will explain only five tags needed for our conditions:
  • or - logical or,
  • with - specify the object under condition,
  • iterate - iterator over a collection,
  • equals - compares object under condition with a value,
  • adapt - adapts object under condition to the specified one.

Or
The element or does logical or. Its child tags must represent conditions. If at least one of them returns true, the result is true.

With
The element with specifies the object under condition. This tag can access various variables that describe Eclipse state. Each variable has a name. All childs of the with tag will test their condition against the value of the specified variable.

This tag has one mandatory property variable. Use it to specify the variable name. We will use two variables:
  • activeMenuSelection - collection of all objects selected by the user,
  • activePartId - the id of currently active GUI part (view, editor, preference page ...).

As any plugin can add its own variable, the complete list of all variables is impossible to find. The list of default variables is available on Eclipse wiki page.

Iterate
The element iterate is usable only inside the with tag and only if this tag specified a collection as the object under the test. Its child tags must represent conditions. Iterate iterates through all objects inside the collection and runs all child conditions on those objects.

The iterate tag has two arguments: operator and ifempty. The first argument value can be either and or or. Selected operator will be applied to evaluated conditions results. If no operator is specified, the iterator uses and.

The ifempty can be either true or false. This value will be returned if the collection under test is empty. If not specified then true is returned when the operator equals and and false is return if the operator equals or.

Equals
The element equals compares the object under test with its value argument. As the object under test may not be a string, the value argument is converted into the object. The exact conversion algorithm is described in Eclipse manual.

Adapt
The element adapt adapts the object under test to the interface or class specified in its type argument. It may have child tags that may process it further, but we will use it solely to check whether the object under test is adaptable to the desired interface.

Test Current Editor
If the menu was invoked from java editor, then id of the active part is the same as id of java editor. Therefore, we have to get java editor id and compare it to the value of activePartId expression variable.

To get the id, open any java file in RCP eclipse and press ALT + SHIFT + F1. Plugin Spy will show popup with various information about active GUI part. According to this popup, the java editor id is org.eclipse.jdt.ui.CompilationUnitEditor.

Combine the with and equals tags to compare the id:
<with variable="activePartId">
  <equals value="org.eclipse.jdt.ui.CompilationUnitEditor" />
</with>

Test Selected Objects
Current selection can contain multiple items and all of them should represent something we can work with. The adapter pattern and java model hierarchy have been designed exactly for these kind of situation.

Three interfaces from java model hierarchy are relevant to our features:
  • org.eclipse.jdt.core.IMethod - represents java methods,
  • org.eclipse.jdt.core.IType - represents java classes and interfaces,
  • org.eclipse.jdt.core.ICompilationUnit - represents java source file.

Therefore, we will iterate over the activeMenuSelection variable and check whether each selected object is adaptable to one of needed types. If no item is selected, the condition should return false.

A method is selected if it is possible to adapt all selected objects into the IMethod interface:
<with variable="activeMenuSelection">
  <iterate ifEmpty="false" operator="and">
    <adapt type="org.eclipse.jdt.core.IMethod" />
  </iterate>
</with>

A java source file or a class is selected if it is possible to adapt all selected objects into either ICompilationUnit or ITypeinterface:
<with variable="activeMenuSelection">
  <iterate ifEmpty="false" operator="and">
    <or>
      <adapt type="org.eclipse.jdt.core.IType" />
      <adapt type="org.eclipse.jdt.core.ICompilationUnit" />
    </or>
  </iterate>
</with>

Note 1: Unfortunately, the xml expression language is not expressive enough to distinguish between classes and interfaces. An instance of IType interface represents class if its isClass method returns true, but xml language does not support method calls.

Note 2: We are cheating little bit here. It is not possible to modify compiled java methods and classes inside jar packages, but they are adaptable to java model interfaces too. Those objects are modifiable only if the method getCompilationUnit does not return null. As with the previous note, this is not possible to check from the xml expression language. Fortunately, we are contributing to the source sub-menu which is available only on modifiable java elements, so we do not have to solve this problem.

Note 3: In both cases, the correct solution would be to create own with variable and handle both problems in java. This is possible and easy, but out of scope of this article. If you wish to know how to do it, read the post on Lars Vogel blog.

Check Non-Null Parameters Enablement
To reiterate, the menu item 'Check Non-Null Parameters' will be enabled:
  • inside java editor,
  • if the current selection contains only modifiable java methods.

Use or to combine conditions created in previous chapters. This is the final condition:
<enabledWhen>
  <or>
    <with variable="activePartId">
      <equals value="org.eclipse.jdt.ui.CompilationUnitEditor" />
    </with>
    <with variable="activeMenuSelection">
      <iterate ifEmpty="false" operator="and">
        <adapt type="org.eclipse.jdt.core.IMethod" />
      </iterate>
    </with>
  </or>
</enabledWhen>

Generate Custom toString Enablement
Recall, that the menu item 'Generate Custom toString' will be enabled:
  • inside java editor,
  • whenever java class is selected,
  • whenever java file is selected.

Use or to combine conditions created in previous chapters. This is the final condition:
<enabledWhen>
  <or>
    <with variable="activePartId">
      <equals value="org.eclipse.jdt.ui.CompilationUnitEditor" />
    </with>
    <with variable="activeMenuSelection">
      <iterate ifEmpty="false" operator="and">
        <or>
          <adapt type="org.eclipse.jdt.core.IType" />
          <adapt type="org.eclipse.jdt.core.ICompilationUnit" />
        </or>
      </iterate>
    </with>
  </or>
</enabledWhen>

Expression Language - Additional Resources
The complete explanation on how those conditions work is out of scope of this post. If you wish to read more, the basic and short article is on eclipse-tips blog and a detailed one is on Lars Vogel blog.

The detailed post explains also how to reuse conditions and how to create new with variable. It is definitely worth reading, especially if you plan to do something complicated.

Command Framework - Additional Resources
We simplified things a little in this chapter. The full functionality and possibilities of the command framework are out of scope of this article.

If you wish to know more, good tutorials are available on Lars Vogel blog or in IBM library.

The Actions Framework

Theoretically speaking, the actions framework is old and has been deprecated. The newer commands framework should be used for most purposes. Unfortunately, not all menus have been rewritten yet.

If the menu you are interested in has not been rewritten yet, you have to use the old actions framework to contribute to it.

As most menus are already compatible with the new command framework, we will show only the part of actions framework that is able to contribute to the source sub-menu in main menu. If you wish to read more about the actions framework, the last sub-chapter links to more detailed articles.

Overview
Two most important elements in the actions framework are action and action delegate. An action is assigned to menu items and knows the menu where it should be drawn. Action delegate is a class that does all the work whenever a menu item is invoked.

Each action delegate can be assigned to any number of actions. However, each action can have only one action delegate.

Items and menus definitions are kept either inside an action set or inside something called a contribution. There is not much difference between them, both action set and contribution contain list of actions and sub-menus to be drawn within menu system.

Eclipse framework has three types of actions contributions and each is able to specify a simple condition that must be satisfied to have a visible action:
  • viewerContribution - an action is available only inside specified view,
  • objectContribution - an action is available only if specified object type has been selected,
  • editorContribution - an action is available only inside editor menu or toolbar.

Note: it is also possible to assign a condition to an action. Action conditions are powerful, but out of scope of this article.

Both contribution and action set are placed directly inside an extension point. Each menu type has its own extension point:
  • org.eclipse.ui.actionSets - main menu or main toolbar,
  • org.eclipse.ui.popupMenus - context menu,
  • org.eclipse.ui.viewActions - local menu or toolbar in a view,
  • org.eclipse.ui.editorActions - local menu or toolbar in an editor (recall that the editors menu is shown inside the main menu/toolbar),
  • org.eclipse.ui.perspectiveExtensions - we will ignore that.

So, we have to create action delegate class, identify the menu we want to contribute to and configure an action inside a action set.

Implementing Action Delegate
Action delegate implementation depends on where you want to place the associated action. Menus supply the action delegate with additional information that depends on the menu type. Therefore, each menu type requires it to implement different interface.

Common
Each action delegate must implement IActionDelegate interface. That interface was later extended with some lifecycle methods, so if you need to initialize your action delegate or know when it is disposed, implement also IActionDelegate2 interface.

Which method is invoked when a menu item is clicked depends on implemented interfaces. An action delegate that does NOT implement IActionDelegate2 interface must place all the work inside the run method. However, an action delegate that implements the IActionDelegate2 interface must place all the work inside the runWithEvent. In this case, the run method is never called.

Menu Types and Sub-Interfaces
Which sub-interfaces of IActionDelegate interface you should use depends on where do you want to place the action. Some parent menus send additional information about active parts and the action delegate must be able to acquire it.

List of eclipse menus and expected action delegate interfaces:
  • main menu or toolbar - IWorkbenchWindowActionDelegate,
  • view menu or toolbar - IViewActionDelegate,
  • view context menu - IViewActionDelegate or IObjectActionDelegate,
  • editor menu or toolbar - IEditorActionDelegate,
  • editor context menu - IObjectActionDelegate.

Selection
Parent menu uses the method selectionChanged to inform the action delegate about current selection changes. However, selection changes are sent to the action delegate only after it was invoked for the first time. All prior selection changes are ignored.

The selectionChanged method is called for the first time right before the first call of run or runWithEvent method.

Implementation
We are ready to implement our action delegates. As it was with the command handler in command framework, our action delegate will only collect information about current editor and selected objects or text. The real work is delegated to yet another class.

As we want to put our action to the main menu, we have to implement the IWorkbenchWindowActionDelegate interface. Main menu sends changes of current selection it to its action delegates, so all we have to do is to store it:
private ISelection selection;

public void selectionChanged(IAction action, ISelection selection) {
  this.selection = selection;
}

Getting current editor is little bit more complicated. Main menu does not inform its items about active UI parts. However, it sends them an instance of IWorkbenchWindow upon initialization. Fortunately, the workbench window object is aware of almost everything that is going on in the eclipse.

The explanation of the workbench window object would take too much space and it is not that important. The important is, that it provides access to all kind of information about eclipse user interface.

Use workbench window to get active editor and its id:
private IWorkbenchWindow window;

public void init(IWorkbenchWindow window) {
  this.window = window;
}

private String getActivePartId() {
  return window.getPartService().getActivePartReference().getId();
}

private IEditorPart getActiveEditor() {
  return window.getActivePage().getActiveEditor();
}

Finally, we are ready to implement the run method:
public class GenerateToStringActionDelegate implements IWorkbenchWindowActionDelegate {

  private static final String JAVA_EDITOR_ID = 
"org.eclipse.jdt.ui.CompilationUnitEditor";

  public void run(IAction action) {
    //this object is needed to render wizards, messages and so on 
    Shell activeShell = window.getShell();
    //get selected items or text 
    ISelection currentSelection = selection;
    //identify active GUI part
    String activePartId = getActivePartId();
    //java editor must be handled differently than view selection 
    if (JAVA_EDITOR_ID.equals(activePartId)) {
      //get edited file
      IEditorInput input = getActiveEditor().getEditorInput();
      //currentSelection now contains text selection inside input file
      //... locate class selected in that file ...
    } else {
      //currentSelection now contains all classes inside 
      //... collect all selected classes ...
    }

    System.out.println("GenerateToStringActionDelegate");
  }

}

MenubarPath and ToolbarPath
Before you configure the action, you have to find the menu or toolbar path. It identifies a menu or toolbar where your action will be shown.

Each menu or sub-menu has its name and menu or toolbar path navigates those names. It starts with the top level menu name and continues with all sub-menu names. The final part is optional and specifies a location in the final menu. If it is missing, the item is placed to the end of the specified menu.

For example, the menu path #menuName/subMenuName/additions should be read as "place the item in the end of the additions group which is located inside the sub-menu subMenuName of the #menuName menu".

Or, the menu path #menuName/subMenuName/ should be read as "place the item in the end of the sub-menu subMenuName of the #menuName menu".

Important: Menu ids are the same as described in the command framework chapter. The id of the source menu id is org.eclipse.jdt.ui.source.menu. As the main source menu is a top level menu, menubarPath is:
org.eclipse.jdt.ui.source.menu/

Configuring an Action
Action configuration is quite simple. Add an extension point, put either action set or contribution into it and place the action.

Which one of five extension points should be used depends on the menu. We want to add an action to the main menu, so we have to use the action sets extension point.

Open plugin.xml file and go to extensions tab. Use the left part named 'All Extensions'. First, configure an action set:
  • Click 'Add' and choose org.eclipse.ui.actionSets extension point.
  • Right click on the new extension point and choose 'New' -> 'actionSet'.
  • Fill in the action set id. The convention is to use plugin name as its prefix. Important: This field has some strange limitations. We could not really figure them out, so all we can say is that:
    • org.meri.eclipse.defensiveapitools.mainmenucontrib works
    • org.meri.eclipse.defensiveapitools.sourcemenu works,
    • org.meri.eclipse.defensiveapitools.mainsource does not work,
    • org.meri.eclipse.defensiveapitools.actionset does not work.
    In any case, if nothing shows up in the menu, try to change this id.
  • Fill in label. It can be anything.
  • Set visible to true.

Second, add actions to the action set:
  • Right click on the action set and choose 'New' -> 'action'.
  • Fill in the action id. The convention is to use plugin name as id prefix.
  • Fill in label. It can be anything.
  • Fill in either menubarPath or toolbarPath.
  • Scroll down and browse for the action delegate in class field.

Eclipse automatically generates corresponding xml and places it to the plugin.xml file. Go to the plugin.xml tab to see it:
<extension
      point="org.eclipse.ui.actionSets">
   <actionSet
         id="org.meri.eclipse.defensiveapitools.sourcemenu"
         label="Defensive API Tools "
         visible="true">
      <action
            class="org.meri.eclipse.defensiveapitools.generatetostring.GenerateToStringActionDelegate"
            id="org.meri.eclipse.defensiveapitools.generatecustomtostring"
            label="Generate Custom toString"
            menubarPath="org.eclipse.jdt.ui.source.menu/"
            style="push">
      </action>
      <action
            class="org.meri.eclipse.defensiveapitools.checknonnullparameter.CheckNonNullParameterActionDelegate"
            id="org.meri.eclipse.defensiveapitools.checknonnullparameters"
            label="Check Non-Null Parameters"
            menubarPath="org.eclipse.jdt.ui.source.menu/"
            style="push">
      </action>
   </actionSet>
</extension>

Run or debug the plugin and open any java file in the test Eclipse. Click on the Source main menu item. The menu contains two new items. Click on the Generate Custom toString' menu item, the action delegate will print 'GenerateToStringActionDelegate' into console in RCP Eclipse.

Additional Resources
Walkthrough of all types of actions contributions is available in an eclipse corner article. Easy to read FAQ article explains actions in the main menu.

Actions framework supports also complicated conditions and filters to show actions only under some circumstances. If you have a reason to use that, the best starting point is in eclipse documentation.

Useful Resources

Everything directly related to chapters in this post was already linked. We will add only a link on a very good series with various eclipse development tips.

If you have a problem and can not find an answer, you can also ask questions on official freenode #eclipse IRC channel.

To Be Continued

Although we added only some new items into the menu so far, almost everything needed to finish the plugin is already there. If you do not insist on having the generated code formatted, you should be able to finish the generate toString feature just by browsing through java model hierarchy API.

Next part of this tutorial will show how to finish both features, including the formatting of the generated code. It will also explain how to create and work with abstract syntax tree and how to create a dialog to communicate with user.

106 comments:

Post a Comment