Monday, September 13, 2010

The experience of creating an Eclipse refactoring (part 2)

The Last time around I commented on my motivation of learning about Eclipse plugins and specially the JDT and gave a small overview of the classes involved in a refactoring. In this post a I'll talk about the plugin's metadata and the classes that eventually launch the refactoring. I'll leave the implementation of the refactoring itself for the next post.

One thing that I forgot to mention in my previous post is that I'm working with Eclipse Helios (3.6).

The version of the code I used for this post can be downloaded here.

The refactoring that I'm presenting is called 'To Final' and adds the final modifier to private fields that are initialized inline and are not modified in the scope of the class. As I already mentioned my Refactoring is based on the article Unleashing The Power of Refactoring. The article is a little old now (4 years) but It's a comprehensive guide that will take you step by step. Just a word of caution before you get started, The refactoring explained there is 'Introduce Indirection' and is already present in recent eclipse versions. You might want to change its name to avoid confusions and second it uses Actions instead of the newer Commands framework to get the refactoring code started.


I'm going to walk through the metadata of my plugin and some code while commenting about the things I found important to remember. Let's get started

Manifest


One thing that I found a little frustrating was the fact that I couldn't find a place where you can search which class is provided by which bundle. In other words if somehow I ran into a class that didn't belong to the dependencies that I already had in place I had to google for the package to which that class belongs to find the bundle that exported the given package.

In the previous version of the Manifest I had an import package for org.eclipse.jdt.core.refactoring. That happened when I answered yes to a Quick fix that recommended adding the import package line in the Manifest. Which leads me to ask the following question:
When should I use require-bundle and when import-package?.

Plugin.xml


In this plugin several extension points are being used let's go one by one.

org.eclipse.ltk.core.refactoring.refactoringContributions
Although is technically true that I'm adding a refactoring Contribution, I haven't done anything yet other then adding a class with a default implementation of the methods needed to compile. So nothing special here. I need to dig deeper in the History/Scripting deal and make sense of the Refactoring Contribution and Descriptor use. That's left for a future post.

org.eclipse.ui.commands
I replaced the Action based implementation of the original plugin with the newer commands api. The command declares a sort of abstract action that may be invoked by the UI. In this particular case the command defined corresponds to the execution of the refactoring. As you can see there's no reference to a class because the command itself is only a placeholder for a set of behaviors. Each behavior is implemented by a Handler.

org.eclipse.ui.handlers
Each handler is responsible of the execution of a Command's behavior. Only one handler is active at a time and that is achieved by the use of conditions. For example in my plugin that sole handler that I have defined only is active if the current selection is an instance of whether an ITextSelection or a StructuredSelection. One thing I had a hard time understanding is the fact that the variables used by the with tag are predefined by the workbench.

org.eclipse.ui.menus
Here's where the menu that starts the refactoring is defined. It took me a lot of time to get the menu to show up. I'm not sure why it didn't work at the beginning because I ended up taking this configuration from a working sample. I don't if it was me or in fact there's a problem reporting issues when a menu cannot be shown because while I was struggling with the menu not showing up I didn't see anything useful in the log.
I little thing you should know about is the locationURI attribute that defines where your menu is going to be placed.

The Classes

If you want to write your own refactoring. One that's started from the menu. You'll need an implementation of the RefactoringWizard. Let me try to explain how to get the refactoring code running :
  1. The menu 'My Refactorings/To Final...' dispatches the commmand
  2. The command itself is a placeholder for a set of handlers. The active handler is executed. In this particular case there's only one: 'arz.refactorings.string.to.final'
  3. The handler starts the Refactoring Wizard
  4. The Wizard executes the different steps that comprise the refactoring


ToFinalCmdHandler

ToFinalCmdHandler inherits from AbstractHandler.
When the 'To Final...' option under the 'My Refactorings' menu is clicked, the handler ToFinalCmdHandler is run and its entry point is the execute method.
In this method I update the data needed to create the refactoring instance using the current selection. Because the conditions that enable the handler require an active selection I can safely assume that there's one. Once the data is updated I launch the wizard with my newly created refactoring instance. I'll leave the implementation details for a future post where I'll talk about manipulating and transforming code using the JDT.


ToFinalWizard

As I mentioned in the previous post in this series my Wizard is really dumb because it doesn't add any input page. Remember this refactoring eventually will become a QuickFix. The only thing to note here is that it receives an instance of ToFinalRefactoring (my refactoring). This is the glue that binds my refactoring to Eclipse's refactoring engine.


When I first started working with Eclipse the configuration part was the hardest for me. Maybe because I didn't take the time to read how the plugin model works or because I'm new to OSGI. Or is just the learning curve. Once I got to the point where everything showed up and got started the implementation of the refactoring was relatively easy. But will talk about that in a later post.