This portion of the document describes how to create a new refactoring, or at least the methodology that was used to create the existing refactorings.
The first step is to create a set of source files to test on. This file contains the "clean" version of the file before the refactoring was applied. Then I hand edited the file so that it was the correct result. Then applied the pretty printer to the edited file. (If more than one file is updated by a refactoring, this process is repeated for each file.)
The second step is to create a unit test. To do this, I extended org.acm.seguin.junit.DirSourceTestCase. This takes all the features of the TestCase from junit and adds in the specific directories. Root is the working directory, clean contains the unmodified files, and check contains the correct files. The unit test:
To get the unit test to compile, I create a refactoring class. I call the refactoring class XXXRefactoring, and it extends org.acm.seguin.refactor.Refactoring. The refactoring goes into the appropriate package. At the moment, the kinds of refactoring are:
I quickly create the empty methods so that the refactoring is not abstract, and then everything should compile. I compile, and run the unit tests to see that the junit test fails because the files do not match. (Other types of failures are caused by the unit test being incorrect - so now that is debugged.)
Next I create a number of TransformASTs that perform some unit transformation on the parse tree. I've used these to add or remove a node in the parse tree or replace a name everywhere it occurs in the source tree.
Sometimes a TransformAST hands it's work off to a Visitor object. The org.acm.seguin.parser.ChildrenVisitor is a good one to choose as a base class for this visitor. It already provides the ability to traverse the entire tree. Then all I have to do is overload the specific visit methods for the nodes I'm concerned about.
To learn more about what exactly the source tree looks like, look at the java1_1.jjt file that is included in the code.jar file.
Now we have a bunch of TransformAST objects which might call visitors to do their work. How do these get combined together to update a single file? I would then create a ComplexTransform and add to it each TransformAST object that is necessary to update that file. Create an instance of the ComplexTransform with the getComplexTransform() method of the Refactoring object. (This is done to support undoing the refactoring.)
If the effect of a refactoring requires that perhaps a large number of files be modified, then I use a SummaryVisitor to traverse all the source files that are loaded into memory. A good visitor to extend is the org.acm.seguin.summary.TraversalVisitor. It traverses the summary file.
To learn more about the Summary objects, look at the org.acm.seguin.summary package. The Summary objects store the metadata for all the source files that are loaded. One thing to note about the FileSummary objects is that if they have a file that is equal to null, then the FileSummary and associated types are from the JDK or a 3rd party library. These types cannot be updated by the refactoring tool.
By this point we have a Refactoring object. It applies a ComplexTransform to a single file or uses a TraversalVisitor to apply the ComplexTransform to a series of files. The ComplexTransform applies a number of TransformAST objects to update a single parse tree.
Once the unit test passes, you are done!