blob: 69383e767c216341e62884307900417ffe8482af [file] [log] [blame]
% Sample Refactoring
%{\scriptsize Last modified }
Contributing a new refactoring to Photran is best done by following a working
This paragraph describes the general approach: First, an action must be added to
both the editor popup menu \textbf{and} the Refactor menu in the menu bar by
modifying the plugin.xml file. Then, the action delegate and its accompanying
refactoring wizard have to be coded; these two classes are responsible for
populating the user interface of the refactoring wizard dialog. Finally, the
actual Fortran refactoring itself has to be coded.
The remaining sections go into the details of each of those steps based on a
simple (but not useful) refactoring example: obfuscating Fortran by removing the
comments and adding redundant comments to the header. The source code is
available from
(UIUC personnel can find it in the Subversion repository described in the appendix).
\section{Modifying the plugin.xml}
There are \textbf{four} extensions points (from the Eclipse core) that our plug-in needs to extend:
\item [\texttt{org.eclipse.ui.commands}] Creates a new command \emph{category}
to represent our refactoring. This category will be referenced by the other
extensions in the plugin.xml file.
\item [\texttt{org.eclipse.ui.actionSets}] This extension point is used to add
menus and menu items to the Fortran perspective.
\item [\texttt{org.eclipse.ui.actionSetPartAssociations}] Allows our
refactoring to be visible/enabled in the context of the Fortran editor.
\item [\texttt{org.eclipse.ui.popupMenus}] Displays our refactoring in the
pop-up menu that appears during a right-click.
\item [\texttt{org.eclipse.ui.bindings}] (Optional) Allows our refactoring to
be invoked via keyboard shortcuts. For instance the Fortran Rename Refactoring
is bound to the Alt + Shift + R keyboard shortcut, which is the same as the one
for the Java Rename Refactoring.
Please refer to the documentation and schema description for each extension point; the documentation is available from Help $>$ Help Contents in Eclipse.
Fortran currently does \textbf{not} use the newer \texttt{org.eclipse.ui.menus}
extension points (introduced in Eclipse 3.3) for adding menus, menu items and
pop-up menus.
It is possible to use the newer \texttt{org.eclipse.ui.menus} extension point if
desired, but this chapter uses the older extension points to remain consistent
with how Photran is doing it.
For more information, see the plugin.xml file of our refactoring example.
\section{Creating an Action Delegate and a Refactoring Wizard}
The \texttt{org.eclipse.ui.actionSets} and \texttt{org.eclipse.ui.popupMenus}
extension points that were extended in our plugin.xml file require a reference
to action delegate class that we need to provide.
For a Fortran refactoring, our action delegate should extend the
\\\texttt{AbstractFortranRefactoringActionDelegate} class \textbf{and} implement
the \\\texttt{IWorkbenchWindowActionDelegate} and \texttt{IEditorActionDelegate}
The most important method in our action delegate class is the
\textbf{constructor}. The constructor has to be done in a particular way so that
everything is setup correctly. Listing~\ref{lst:obfuscateaction} shows how the constructor needs to be setup.
public class ObfuscateAction extends AbstractFortranRefactoringActionDelegate
implements IWorkbenchWindowActionDelegate, IEditorActionDelegate {
public ObfuscateAction() {
super(ObfuscateRefactoring.class, ObfuscateRefactoringWizard.class);
\caption{\texttt{ObfuscateAction} for our simple refactoring example}
Inside our constructor, we need to call the parent constructor that takes
\textbf{two} parameters: the class of the actual refactoring object (e.g.
ObfuscateRefactoring) and the class of the actual refactoring wizard (e.g.
ObfuscateRefactoringWizard). The parent class will dynamically create the
refactoring object and refactoring wizard using Java reflection.
Our refactoring wizard needs to be a subclass of
\texttt{AbstractFortranRefactoringWizard}. The only method that we are required
to implement is the \texttt{doAddUserInputPages} method. This page is
responsible for creating a page for the wizard. For instance, a refactoring such
as rename refactoring requires the user to provide a new name. So the
\texttt{doAddUserInputPages} is responsible for creating the interface for that.
Ideally, if our refactoring does not require the user to provide any input, it
should just have an empty \texttt{doAddUserInputPages} method. However, because
of a bug in the Mac OS X version of Eclipse, it is necessary to add a
\emph{dummy} page. Without this dummy page the refactoring will cause the entire
Eclipse UI to lock up on Mac OS X. Listing~\ref{lst:dummy_wizard_page} shows how
to add a dummy input page. \\
protected void doAddUserInputPages() {
addPage(new UserInputWizardPage(refactoring.getName()) {
public void createControl(Composite parent) {
Composite top = new Composite(parent, SWT.NONE);
top.setLayout(new GridLayout(1, false));
Label lbl = new Label(top, SWT.NONE);
lbl.setText("Click OK to obfuscate the current Fortran file.
To see what changes will be made, click Preview.");
\caption{Adding a \texttt{dummy} wizard input page}
\section{Creating the Actual Refactoring}
Section~\ref{sec:structure_of_a_fortran_refactoring} gives a good overview of
the \textbf{four} methods that a Fortran refactoring needs to implement. And
Section~\ref{sec:refactoring_caveats} gives an overview of things to avoid while
performing a refactoring. Our example refactoring conforms to the lessons in
both those sections.
Here we briefly describe the four methods in our example:
\item [getName] This just returns the text ``Obfuscate Fortran Code''
describing our refactoring. This text will be used as the title of the
refactoring wizard dialog.
\item [doCheckInitialConditions] Our simple refactoring does not have any
\emph{real} initial conditions. Our refactoring can proceed as long as the
current file can be parsed as valid Fortran source code. This is automatically
checked by the \texttt{FortranRefactoring} parent class.
Instead we use this method as a hook to perform some simple program analysis --
acquiring the names of all the functions and subroutines in the current file. We
will print these names later as part of the header comment.
\item [doCheckFinalConditions] Since we do not require the user to provide any
additional input, there are no final conditions to check.
\item [doCreateChange] The actual refactoring changes are constructed in this
We iterate through every token in the current file to check if it has a comment
string. Comment strings are acquired by calling \texttt{Token\#getWhiteBefore()}
and \texttt{Token\#getWhiteAfter()}. Following the advice of
Section~\ref{sec:refactoring_caveats}, we store a list of all the tokens (call
this list TokensWithComments) that contain comment strings. Once we have
iterated through all the tokens, we proceed to remove the comments for tokens in
our TokensWithComments list. Removing comments is done by calling
\texttt{Token\#setWhiteBefore()} and \texttt{Token\#setWhiteAfter()} with blank
strings as parameters.
Finally, we create a header comment that just lists all the functions and
subroutines in the current source file and add that to the preamble of the main
For more information, please consult the source code for our example.