Wednesday, February 23, 2011

The experience of creating an Eclipse refactoring (part 3)

In my previous post I took a tour through the configuration needed to get a new Refactoring going. I promised that I would start showing the code that processed the refactoring. I intend to do exactly that now. I honestly wanted to cover the whole refactoring in this post but the condition checking code is big enough for one post. So I decided to leave the transformation code aside in this post. I really hope that this series can be useful for someone trying to build tools for the JDT. Not from an expert's point of view, but from the perspective of someone that is learning the eclipse framework and has some experience on processing source code. I want to share my thoughts and the things I found tricky. If someone runs into one of those issues he can have a reference.
I will assume you're somewhat familiar to the AST concept and JDT's AST in particular. If you are not, I recommend you the following articles

Here's a link to the source code of the project used for this post.

From Text to AST


If you read my previous installment you'll recall that I got to a point where we could get the refactoring wizard running without knowing much about what happens next. Let's build from there by taking a look at the whole ToFinalCmdHandler class. And I'll comment on the methods on a top-down fashion from the execute method. Then we'll do the same for the remaining classes.

ToFinalCmdHandler


execute
When the handler is executed it tries to update the fields fCompilationUnit and fField using the current editor and the current selection. The method responsible for that operation is updateDataFromSelection. Once the data is ready, the refactoring wizard is started by the method startWizard.

updateDataFromSelection
The first thing updated is the field fCompilationUnit using the method CompilationUnitForCurrentEditor. The next step is updating the selected field using the current selection. The current selection can be an instance of the following two interfaces:

ITextSelection: The current selection is an instance of an ITextSelection when there's text selected in the current Editor.

IStructuredSelection: The current selection is an IStructuredSelection when almost anything else is selected, like a node in the package explorer or an error in the problems view. I'm interested in the case when there's a field selected in the outline view.

Two different approaches are used to obtain the parsed field. When the selection is structured. The code is already parsed and the corresponding node from the AST can be obtained directly from the selection. When the selection is text we use the method codeResolve from the class SelectionConverter. The method codeResolve obtains the AST node that is located in the coordinates of the text selection. I have to warn you here. SelectionConverter is part of the JDT internal API. So there's no warranty that it will be there in future versions of JDT or if it will ever be public. But is handy nonetheless. The only thing left here is to validate if the obtained node (if any) is in fact a field. Then it is stored in fField. Let's take a closer look to CompilationUnitForCurrentEditor


CompilationUnitForCurrentEditor
Sorry about the violation to the coding standard. I found out my mistake after I published the code. The first thing you notice (after noticing the c#'ish name) when you see this method is the comment. I really don't like the idea of parsing the current editor. When I explored the code of the existing refactorings I found that the compilation unit was being passed as a parameter to them by a third party. But I have to admit that I didn't try to dig deeper to find out how to integrate with that mechanism. However I needed a compilation unit and this mechanism works. What this method does is simple but needs several steps to get the content of the current Editor as an IResource instance. By the way the getAdapter method is a perfect sample of an implementation of the Adapter pattern.


startWizard
There isn't much about this method. It receives the wizard and launches it using a RefactoringWizardOpenOperation. So we had already covered the RefactoringWizard in the previous post. Let's move to the Refactoring itself.

ToFinalRefactoring

Ok so we get to the interesting part.  In order to implement the refactoring my class must inherit from the Refactoring abstract class.  The official javadoc explains very well the Refactoring class:
Abstract super class for all refactorings. Refactorings are used to perform behavior-preserving workspace transformations. A refactoring offers two different kind of methods:

1. methods to check conditions to determine if the refactoring can be carried out in general and if transformation will be behavior-preserving.
2. a method to create a Change object that represents the actual work space modifications.

The life cycle of a refactoring is as follows:

1. the refactoring gets created
2. the refactoring is initialized with the elements to be refactored. It is up to a concrete refactoring implementation to provide corresponding API.
3. checkInitialConditions(IProgressMonitor) is called. The method can be called more than once.
4. additional arguments are provided to perform the refactoring (for example the new name of a element in the case of a rename refactoring). It is up to a concrete implementation to provide corresponding API.
5. checkFinalConditions(IProgressMonitor) is called. The method can be called more than once. The method must not be called if checkInitialConditions(IProgressMonitor) returns a refactoring status of severity RefactoringStatus.FATAL.
6. createChange(IProgressMonitor) is called. The method must only be called once after each call to checkFinalConditions(IProgressMonitor) and should not be called if one of the condition checking methods returns a refactoring status of severity RefactoringStatus.FATAL.
7. steps 4 to 6 can be executed repeatedly (for example when the user goes back from the preview page).

A refactoring can not assume that all resources are saved before any methods are called on it. Therefore a refactoring must be able to deal with unsaved resources.

The class should be subclassed by clients wishing to implement new refactorings.

If someone asks me how the refactorings for the JDT were designed I'd say they used a Behavior Driven Approach because the Refactoring class is clearly modeled in function of the User Interface workflow. And looks like the RefactoringDescriptor is a tradeoff of this design decision. When a refactoring needs to be run without user interaction the steps needed to make it work don't appear as clean as when it needs user interaction.

Now back to business. Let's see how I implemented the abstract methods to get my refactoring going. Let's start with checkInitialConditions.

checkInitialConditions
Half of the job is done by this method. What this method does basically is to determine if the refactoring can be applied on the selected field. These are the things that are validated:
  • The selection is actually a field
  • The class that defines the field is not an annotation
  • The field is private
  • The field is not already final
  • The field is initialized at declaration time and is not assigned anywhere else
The first 3 conditions are checked by the method doesFieldMatchesInitialConditions. If all of them are met then I proceed to validate the fifth condition that takes some work to find out.  In order to do that we need to need the help of a visitor and a fully parsed AST in order to create a class whose responsibility is to look for assignments to a variable all over the place (Inside the class methods that is). Now is time to take a look into the AssignmentsFinder class.


checkFinalConditions
After the method checkInitialConditions is invoked an input screen is shown to the user. Like the window that shows when moving code into a new method and the method name must be introduced. Once the user presses OK on that screen the next method invoked is checkFinalConditions. Now that there's more information available further validations can be performed. In this case there's no user input, rendering the method useless.


doesFieldMatchesInitialConditions
It's easy to find out all the things this method is looking for just by querying the field's properties and using the Flags class.

AssignmentsFinder


The responsibility of AssignmentsFinder is to traverse the code of the current class, and after doing so it must be able to respond to the question "Is the field initialized at declaration time and only is assigned there?"
If you take a look at the code you'll find that the code is rather simple. It overrides the visit method for the nodes in the AST that correspond with assignments:

ClassExample
VariableDeclarationFragment int variable=1;
PostfixExpression variable++
PrefixExpression ++variable
Assignmentvariable = value; variable += value

When visiting the VariableDeclarationFragment instances the intention is to find the declaration of the selected field. Once found the next step is to verify that it has an initialization expression. As a side note on the code I'm checking if the initialization value is a literal. And at this point I honestly don't remember why I set that constraint. The rest of the visited nodes are forms of assignments that need to be inspected in order to validate that the selected field is not being assigned.

The first thing that comes to mind when implementing this code is how to know that the being visited is actually the node that corresponds to the field. That can be accomplished by the use of bindings. Every time the parser finds a reference to an entity the same binding is assigned to that reference. Making it easy to know exactly what a symbol is 'bound' to.

After the visitor is finished traversing it is ready to answer the question I mentioned above. The method used for that purpose is canVariableBeFinal. If the method returns true the refactoring can proceed otherwise an error must be reported and the refactoring should not continue. As you can see in the checkInitialConditions method.

From AST back to Text

Next time I'll cover the code that transforms the AST and adds the final modifier to the field. Stay tuned. I expect to have the next post much sooner than what it took to get this one out of the door...

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.

Sunday, July 25, 2010

The experience of creating an Eclipse refactoring

A long ago I started investigating on developing new Refactorings for Eclipse. I started with a lot enthusiasm, a cup of coffee and my Linux box. First things first, I downloaded the SDK version of Eclipse. Then I started browsing the net looking for resources beside the official documentation and the code itself. I was shocked when I found out that not only there wasn't a lot of resources, they barely exist!. I can list all the resources I found right here:
That was like 2 years ago but the outlook is not very different now.  At the beginning I thought that there must be a big community of developers creating language processing tools for automating some of their stuff with JDT.  I guess I was wrong or I'm not that good at googling.

In this post I'll talk  about how I got started and I'll introduce the main components of a refactoring in eclipse in a very concise way. I want this post to be more like an entry point for someone who wants to start exploring the JDT/LTK SDK and is a little lost like I was at the time.

Originally my intention was to create a set of refactorings to promote functional programming practices in java.  First I would try to add a Quick Fix to add the final modifier to private fields that are not changed inside the class in an attemp to promote unmutable objects. I hadn't developed in java for a while and I forgot that the final modifier in java works differently then C#'s readonly which was what I had in mind when I thought of the Quick Fix. But I proceeded anyway.



Because most of the resources I had found were related to refactoring I decided to start by implementing a refactoring named 'To Final" and after that, move it to a Quick fix.

 This was my plan of action:
  1. Create the project using the wizard
  2. Invoke a dummy refactoring
  3. From that point start building the core functionality.

Creating The Project.

This is an easy one. Eclipse has a bunch template projects to get started with the SDK. I started with the good old Hello World.

The Dummy Refactoring

Turns out a Dummy Refactoring is not so Dummy after all. There are two approaches on how to build refactorings.

The old-school way
It can be done using set of classes that work together to get the job done:

RefactoringWizard:
This class is responsible for providing all the pages (forms) for user input, also provides the preview and an error page.

RefactoringContribution:
This class provides a way to create refactorings for scripting and history. I didn't use it. Because of that my plugin cannot be used in scripts.
 
Refactoring:
 This is the meaty part. This class is responsible of validating that the refactoring can be performed and return an object that describes the set of changes required to complete the refactoring. The aforementioned object is an instance of the class Change that is used to generate the preview and if the changes are accepted is used to perform the actual changes in the source code.

RefactoringDescriptor:
As far as I know the RefactoringDescriptor is used with the RefactoringContribution to automate refactoring executions. In my case I ignored this one too.

Refactoring the new way
The other way to include refactorings is by The use of processors and participants. This model of adding refactorings adds the flexibility of binding partincipants to the original refactoring. It can be seen as additional tasks that the refactoring needs to execute that can exist outside of the scope of the JDT. In fact is not a new way of creating refactorings. It's an extension to the original API.

What I did in the end
I based my refactoring on the resources from the article Unleashing the Power of Refactoring. Which is based in the old-school approach.  I tried to code everything myself but I have to confess I had to go to look in the resources more times than I'd like to admit.

The core functionality

I want to leave this part to my next instalment. I'll talk about the road blocks I found during the implementation of my plugin and how I implemented it.