Heavy Refactoring with Eclipse Monkey
Refactoring code in Eclipse has really changed the way I maintain code.
I often extract code into a local variable or inline the values of local
variables just to see which is easier to read and understand… for code
that is easier to understand is easier to maintain.
But what about refactoring code that can't be automatically converted?
At work, we've code a collection of classes which begin by logging a message
after crafting a long string of the parameters. Something along the lines of:
logStart ( "host="+host +" port="+port +
" user='"+user +"' pass='"+pass+"'" );
However, each developer who wrote each of these classes would usually write
them slightly differently. Now, for a logging message, normally this wouldn't
be a big deal, but for this product, it is customer facing, and we decided that
it would be better if did something like:
logStart ( new Object[] { host, port, user, pass } );
Or, with the new variable argument feature in Java 5, this would end up as:
logStart ( host, port, user, pass );
But how to convert all of those classes? This is where my old friend, Emacs,
often comes into play. For I could write a little bit of code to programmatically
convert these kinds of lines, bind it to a key, and kick it off.
Unfortunately, macros and this sort of thing is not standard on Eclipse.
But a new project, Eclipse Monkey, while a bit difficult to figure out,
works quite well.
First, you need to install Eclipse Monkey, and the online instructions
are quite easy to get started. Once it is installed and Eclipse has restarted,
you'll have a Scripts menu. Select the Examples and it will create a
new project (Eclipse Monkey Examples) full of examples.
Clicking again on the Scripts menu will now display a larger collection
of example scripts that exist in the scripts
directory in that project.
In fact, any project that contains a directory called scripts
will be searched
and scripts found there will be added to that directory (see this document).
The scripts (while eventually can be written in just about any scripting language)
are written in Javascript, but these scripts have access to both the Eclipse RCP
as well as to Java. The rub is knowing where you are and what you can do.
First, create a comment header of the metadata containing information like
the name used on the menu and a keystroke we can use to bring it up without
needing to use the menu:
/*
* Menu: Convert showExec() Methods
* Key: M3+9
* DOM: http://download.eclipse.org/technology/dash/update/org.eclipse.eclipsemonkey.lang.javascript
*/
A critical line is the DOM entry. This specifies a… uhm, library for
our script to use, and in this case, this DOM creates a good interface to the
Eclipse RCP to get us started.
Note: The biggest problem with Monkey right now is the lack of generated
documentation of this and any DOM. I would hope that eventually, putting the
URL of the DOM in your browser would at least bring up a Javadoc-like listing
of the objects available.
Next, let's create our main() method, which will be called when this script
is executed, and the first thing we will do, is use the DOM we just included to
get the active editor:
function main() {
var editor = editors.activeEditor;
What can we do with it? In my case, I deprecated the first logStart
method
above in favor with the second form, so when I load up a class with the first
form, I can simply hit Control-.
to jump to that line, and have the deprecated
method highlight. So, in my script, I want to grab the highlighted text:
var range = editor.selectionRange;
var offset = range.startingOffset;
var deleteLength = range.endingOffset - offset;
var text = editor.source.substring( offset, range.endingOffset )
We get the range
(and assign some variables we will be using), and then get
some of the source
from the editor based on the highlighted range
.
At this point, we are in the happy place of Javascriptland, and there are plenty
of books and online documentation on this aspect. In this example, I wanted to
use the text substitution and regular expressions to convert this text:
text = text.replace( /"[^"]+"\s*\+\s*/, "new Object[] { ")
text = text.replace( /\s*\+\s*"[^"]+"\s*\+\s*/g, ", ")
text = text.replace( /\s*\+\s*"[^"]+"\s*\)/, ")")
text = text.substring ( 0, text.lastIndexOf(")") ) + " } )"
While you are developing and debugging your script, I would recommend using
the following line to see what you are variable text
contains:
// Packages.org.eclipse.jface.dialogs.MessageDialog.openInformation(
// window.getShell(), "Monkey Debugging", text )
This will open up a dialog window with the contents of the text
variable as
the contents of the dialog. I can then highlight the bad code, hit Alt-9
,
and see what my script was going to do with my code. Once I was happy with the
results, I commented out the dialog code above and added the applyEdit
code:
editor.applyEdit(offset, deleteLength, text);
}
With this script bound to a keystroke, I could load up a file and easily convert
it, and then spend less of my time with tedious text-smithing.
Hopefully this project will expand, for its a great idea, and its biggest
problem is the lack of documentation. As I play and search the 'net for examples,
I'll start documenting them here.
Tell others about this article: