From Code to AST and Back
This is the last post on creating a refactoring for eclipse. In this post I'll cover the process of creating the changes needed to transform the code once all the conditions have been validated.
The first post introduced my Refactoring named ToFinal which adds the final modifier to the private fields that are only assigned at initialization time. I gave a quick overview of the refactoring API provided by the LTK. In the second installment I covered the topic of contributing a new refactoring using a command. And the last time around I covered the process of validating that the selected field can be made final using the checkInintialConditions method. Now I'm going to finish the series showing the way to transform the ast and taking the changes made to the actual source code.
For this post I used a newer version of the code. You can get the source code here.
Following the same standard I've used previously I'll start showing the code in a top-down fashion. I'll start explaining the createChange method and then I'll dig deeper.
As you can tell from the code, once it has been determined that the transformation can be performed there are two scenarios to take into account. The first one occurs when the field is declared alone. In the eclipse ast jargon that's a field declaration with a single fragment. The second case is a field declaration with multiple fragments. The method responsible of detecting that is isThereOnlyOneDeclarationInTheLine. In the first case the only thing needed to do is adding the final modifier to the declaration. The second case is a little more complex because the field declaration needs to be split and then the final modifier needs to be added to the new declaration. Before moving on, I want to describe the problem the guys behind the IDEs have to deal with when transforming/modifying source code.
As you have seen in my previous posts we've done all the processing using a convenient AST. We are ready to transform the code. And of course we are going to do is transform the AST. Here comes the problem. We are transforming the AST not the original source code. We need to translate the changes to the AST into changes to the text file. One solution is "printing" the whole AST back into the text file. But IDE users like you and me don't want the IDE to modify the whole file when what is needed is a simple change. Like in this case we need to add just the final modifier to a declaration, how hard can that be?. For starters the AST needs to remember to which parts of the file the nodes correspond to. And a mechanism for tracking changes in the AST to convert them to changes in the text file is also required. It's a good thing eclipse provides all that for us. We only need to learn how to use the API for that purpose. Here's where the ASTRewrite class comes handy. ASTRewrite does exactly what I said before, keeps track of the changes made to the AST, and, here comes the catch, without modifying the AST. It's like a list of potential changes.
From the createChange you can see that astRewrite is passed as a parameter to addFinalModifierToDeclaration and
splitMultipleDeclaration. Those methods are the ones in charge of adding changes to the astRewrite. Once the transformation is ready there's one last step left. Create a Change using astRewrite that has all the recorded modifications. That's the purpose of the createChangeFromAstRewrite method. Let's drill down into the methods mentioned above.
This method exists just for making the condition easier to understand, obviously, if there's only one element in the list of fragments it implies that this particular declaration only is declaring one field, duhh.
For reutilization sake I created the template class FieldDeclarationChanger which takes care of modifying one declaration. In this case the template method adds the final modifier to the declaration. Please note that the new nodes are created by the AST.
After siting with a cup of coffee looking at this code I realized that splitMultipleDeclaration is a terrible name for what this method does. Let me explain. When a declaration has multiple fragments I can't add the final modifier to all the declared fields. So I need to create a new declaration for the field being refactored, obviously add the final modifier and finally I need to remove it from the previous place it was being declared. Maybe createNewFinalField is more descriptive then splitMultipleDeclaration. In order to perform the task at hand I created a new field declaration using the createNewFieldDeclaration and again using the template class FieldDeclarationChanger but this time I used it to remove fragment of the field being refactored from the list of fragments contained in the declaration.
In createNewFieldDeclaration I create a new field declaration with a single fragment, what the method does is quite simple: Copy the parts that I need from the original declaration to define the new one.
I want you to put special attention to the last two statements in the method. That's how new nodes are added using the astRewrite. The method getListRewrite returns a ListRewrite instance that you can use to move around,insert,replace and remove child nodes of a give parent node. In this case I'm using it to insert the new field declaration after the existing one. I know what you are thinking now. Why the author didn't do this to add the final modifier in addFinalModifierToDeclaration ?. Good question. I could have done that. I just thought it was simpler the way I did it. And I needed an excuse to reuse my beloved template FieldDeclarationChanger :).
Existing nodes of the AST cannot be modified, in order to edit a node, a copy must be obtained, edited , and then record the modification by replacing the original with new one using the astRewrite. There are more fined grained ways of editing nodes but I found this method quite simple. The method applyEdition clones the node to be edited, invokes the abstract method editFieldDeclaration which is the one that will perform the edition on the cloned node and finally then whole declaration is replaced by the astRewrite instance. I've debated with my self if the FieldDeclarationChanger template is an overkill solution or not. At the end of the day I'm just trying to reuse 3 lines of code and wrote 20 to do that. But let's assume that is needed ...
So far we have been working with the AST, all the editions are ready and it's time to create a Change, a CompilationUnitChange is what we need. Which is a text based Change, And requires a TextEdit. To translate the changes to the AST recorded in the astRewrite to changes to the original text the method to use is rewriteAST. Then it's just a matter of set the edit to the the change. And that's it.
I hope these posts helps you get started with the JDT/LTK api. See you later.
UpdateI didn't feel comfortable with the FieldDeclarationChanger class. Because it was replacing a whole declaration when I was just trying to modify just parts of it, as I mentioned above. So I did my homework and looked for ways to make the modifications more granular.
Below I'll show you new versions of the methods addFinalModifierToDeclaration and splitMultipleDeclaration. They do the same but with less changes to the file.
With this changes the class FieldDeclarationChanger can be let go :(