Monday, February 15, 2010

A rose by any other name...

Some years ago when I wrote about Eclipse's APIs for supporting automated refactorings, it wasn't all too convenient to drive an existing JDT refactoring from your own plug-in. For instance, if you had an IField or an IMethod in your hands (one of JDT's Java language model objects), you couldn't simply call some API methods to rename that thing (field or method). You had to write quite a bit of code, and it was still difficult to avoid Eclipse presenting your refactoring in the standard wizard dialog. A few days ago I was playing around with a new fun feature in Usus (I'm not telling what it is :-) — and I found it much more convenient to do such a thing nowadays.

How you do it

The central object that represents a refactoring procedure is the Language Toolkit's Refactoring (unsurprisingly). If you want to use an existing automated refactoring, such as Rename field or Extract method from the JDT pool, your first step is to retrieve an object that conforms to the Refactoring contract.

You get one of these guys from the refactoring core plugin: first, add a plug-in dependency to org.eclipse.ltk.core.refactoring. You can then call the static factory method RefactoringCore.getRefactoringContribution( String id )in order to retrieve a refactoring contribution, i.e. an automated refactoring that was contributed from some plug-in via an extension in the plugin.xml. For this you need to know the ID of that refactoring contribution. In the case of the JDT refactorings, use the constants in IJavaRefactorings (add a dependency to org.eclipse.jdt.core.manipulation). You can now feed the refactoring contribution with the parameters it needs; in the case of a Rename Method refactoring, for instance, that would be the IMethod object that represents the method we want to rename, and the new name we to give to it. Having done that, we can ask the contribution to create the Refactoring object for us. In sum, you'd do something like this:


private Refactoring mkRefactoring( RefactoringStatus status, IMethod methodToRename, String newName ) throws CoreException {
RenameJavaElementDescriptor desc = loadDescriptor();
desc.setJavaElement( methodToRename );
desc.setNewName( newName );
return desc.createRefactoring( status );
}

private RenameJavaElementDescriptor loadDescriptor() {
String id = IJavaRefactorings.RENAME_METHOD;
RefactoringContribution contrib = RefactoringCore.getRefactoringContribution( id );
return (RenameJavaElementDescriptor)contrib.createDescriptor();
}



As you can see, I'm passing a RefactoringStatus object into the creation operation. That object will contain the result of the creation (which might be an OK or some error status).

Once you are in possession of a refactoring object, you might run in it the standard refactoring dialog (in order to be nice to the user and present them with a preview and the cancel option), or you could extract the Change objects from it and just ask them to perform their work. All in all, a quick test drive of a Rename Method refactoring might be something along these lines:


RefactoringStatus status = new RefactoringStatus();
Refactoring refactoring = mkRefactoring( status, methodToRename, newName );
if( status.isOK() ) {
if( refactoring.checkAllConditions( nullMonitor() ).isOK() ) {
Change change = refactoring.createChange( nullMonitor() );
change.perform( nullMonitor() );
}
}

// ...

private IProgressMonitor nullMonitor() {
return new NullProgressMonitor();
}



A word to the wise

Obviously, there are many aspects you want to consider if you try this in production code (are there editors open which you'll want to save? what about undo operations? do you want to support the local history? ...). You might want to look at the JDT code more in depth in order to find out how to handle these things. (That's something I didn't because I merely wanted to play around with them.) Have fun!

Monday, February 8, 2010

Elegantification is possible even in Java

Sometimes it's the small things that make you smile. (That's so. Sometimes at least ;-)

The unfavorable situation

In the Usus UI, we have several tables displaying code proportions information, and in order to simplify the code that configures such a table, we have a common TreeViewer that reads column information from an enum, where each of the enum's elements represents a table column and its meta data, such as the header string, the column weight and the text alignment in the column's cells. Thus, the table description for the Usus Cockpit view looked like this:
enum CockpitColumnDesc implements IColumnDesc {

INDICATOR( "Indicator", LEFT, 56, true ) {
public String getLabel( CodeProportion element ) {
return element.getMetric().getLabel();
}
},
SQI( "SQI", 10, false ) {
public String getLabel( CodeProportion element ) {
// ...
}
},
// ... more enum fields

private final String headLabel;
private final int weight;
private final boolean hasImage;

CockpitColumnDesc( String headLabel, ColumnAlignment align, int weight, boolean hasImage ) {
this.headLabel = headLabel;
this.align = align;
this.weight = weight;
this.hasImage = hasImage;
}

CockpitColumnDesc( String headLabel, int weight, boolean hasImage ) {
this( headLabel, RIGHT, weight, hasImage );
}

public int getWeight() {
return weight;
}

public String getHeadLabel() {
return headLabel;
}
// ...
}
Now, having about a dozen or so column description enums like this, it became a little unwieldy to add more column information (such as the alignment, i.e. LEFT, RIGHT or CENTER). Each of the enums needed another field that kept the alignment, a getter and another constructor if a sensible default value was to apply. In other words, in order to add alignment information, we still had to write a bunch of code lines into each of the enums (that is, a bunch of code lines that was practically identical for each of them). That looked unelegant to me. The elegant solution was to use a custom annotation type.

Making it look nicer

Annotations are normally used to attach meta data to language elements (classes, methods, fields), so that development tools can read them and do something sensible with the information. For instance, the well-known @SuppressWarnings annotation, when put above a method declaration, tells the Java compiler to shup up about some thing it might have complained about otherwise. (And gentle reader, you won't be surprised that this feature is misused every so often...)

On the other hand, you can make annotation information available at runtime, and read it via reflection mechanisms. Let's say you declare a new annotation type:
@Target( value = { ElementType.FIELD } )
@Retention( RetentionPolicy.RUNTIME )
public @interface UsusTreeColumn {
String header() default "";

// column weight (percentage of overall width in the table that this column takes)
int weight() default 5;

ColumnAlignment align() default ColumnAlignment.LEFT;

}
The bit about the RetentionPolicy tells the compiler to include the annotation information in the compiled code, so that it can be loaded at runtime. If someone uses your annotation like so:
@UsusTreeColumn( header = "SQI", align = RIGHT, weight = 10 )
SQI( false ) {
public String getLabel( CodeProportion element ) {
// ...
}
}
you can reach the info in the annotation this way:

        try {
Field field = loadField( "SQI" );
for( Annotation annotation : field.getAnnotations() ) {
if( annotation instanceof UsusTreeColumn ) {
UsusTreeColumn column = (UsusTreeColumn)annotation;
String header = column.header();
// ...
}
}
} catch( NoSuchFieldException nosufex ) {
// ...
}

// ...

private Field loadField() throws NoSuchFieldException {
Class enumClass = columnDescEnumValue.getClass();
if( enumClass.isAnonymousClass() ) {
enumClass = enumClass.getEnclosingClass();
}
return enumClass.getDeclaredField( columnDescEnumValue.toString() );
}

Basically, you have to find the language element (in this case a field from the enum type) that has the annotation attached to it, and then getAnnotations() from it. The way you find it is via reflection as usual. Once you have the annotation, you can cast it to the interface type you declared (in this case UsusTreeColumn) and simply use it like any other Java object. In essence, that is what our tree viewer now does. The enums, on the other hand, look compact and much more readable:

enum CockpitColumnDesc implements IColumnDesc {

@UsusTreeColumn( header = "Indicator", weight = 56 )
INDICATOR( true ) {
public String getLabel( CodeProportion element ) {
return element.getMetric().getLabel();
}
},
@UsusTreeColumn( header = "SQI", align = RIGHT, weight = 10 )
SQI( false ) {
public String getLabel( CodeProportion element ) {
// ...
}
}

// ...

private final boolean hasImage;

CockpitColumnDesc( boolean hasImage ) {
this.hasImage = hasImage;
}

public boolean hasImage() {
return hasImage;
}
}

Most of the information is compressed in the annotations, though it is still well compiler-checked; and sensible defaults can be used, so that we can leave out parameters in the annotations if we like. (The code for all the enums that describe columns in the Usus UI is now less than half that it was before. Not that code size reduction is everything that matters — but since the new code brings the same information in a much more compact notation, it's really more readable now.)