R3 Tutorial

R3 is a Java package that supports to script refactorings in Java methods. Here is the idea: we create a database of objects from the source code of an Eclipse project. For every Java class defined in source, there is a unique R3 object representing that class. For every Java method that is defined in your project's source, there is a unique R3 object for that method. For every Java field and method parameter, there is a unique R3 object, and so on. R3 objects are linked together to encode the containment relationships in your Java source: each R3 class object is connected to the R3 method and field objects in that class.

 

The methods of R3 objects allow you to navigate to related R3 objects OR perform refactorings (e.g., rename a class or method; move a class or method, etc.). Thus, R3 refactoring scripts are written by (1) importing the R3 package, and (2) writing a Java method that locates R3 objects and refactors them.

 

Here is an example script that renames method myPackage.Graphic.draw() in Eclipse project MyProject to ¡°display¡±.

 

RPackage           pkg = RProject.getPackage(¡°MyProject¡±, ¡°myPackage¡±);

RClassOrInterface  cls = pkg.getClass(¡°Graphic¡±);

RMethod              m = cls.getMethod(¡°draw¡±);

m.rename(¡°display¡±);

 

R3¡¯s class objects (e.g., pkg, cls, m) correspond to Java program elements (e.g., package myPackage, class declaration Graphic, method declaration draw). R3 methods are refactorings (e.g., rename) or ways to locate R3¡¯s class objects (e.g., getPackage, getClass, getMethod).  In the above code:

 

Line 1 assigns variable pkg to the R3 object that represents the myPackage package of MyProject

Line 2 assigns variable cls to the R3 object that represents the Graphic class within myPackage

Line 3 assigns variable m to the R3 object that represents the draw() method of the Graphic class.

Line 4 renames method m to ¡°display¡±.

 

Relatives. Here is an important concept in OO refactorings that is observed by Eclipse and all other refactoring tools, including R3. Consider the class diagram below. If you rename or alter the arguments to any draw() method in this figure, you will rename or alter the arguments in ALL of the draw() methods. We use the term relatives to refer the set of methods that are updated simultaneously. Keep this concept in mind as you read on. You will see this discussion again later.

 

 

Here are the only R3 methods that you may use in this assignment (you may not use some):

 

Class Declaration

Member Method

Return Type

Signature

RPackage

RClassOrInterface

newClass(String)

RClassOrInterface

RField

addSingleton()

RMethod 

getMethod(String...)

ArrayList<RMethod>

getAllMethods()

void

delete()

RMethod

RMethod

moveAndDelegate(RParameter)

RMethod

move(RParameter)

boolean

isMovable(RParameter)

RParameter

addParameter(RClassOrInterface, String)

boolean

removeParameter(RParameter)

RParameter

getParameter(int)

ArrayList<RMethod>

getRelatives()

void

rename(String)

RMethod

replace(String)

 

RClassOrInterface RPackage.newClass ( String className )

 

This method adds a new class (with className) to the given package and returns its RClassOrInterface object.

 

To add a class named ¡°Graphic¡± to the pkg RPackage:

 

RClassOrInterface cls = pkg.newClass(¡°Graphic¡±);

 

 

RField RClassOrInterface.addSingleton ( )

 

This method converts a class into a singleton. A public static final field (named ¡°instance¡±) is created to hold a single instance of the class. The RField object of the ¡°instance¡± field is returned.

 

To make RClassOrInterface cls a singleton:

 

RField f = cls.addSingleton();

 

 

RMethod RClassOrInterface.getMethod ( String... sig )

 

This method returns the RMethod object whose signature matches sig, an array whose 0th element is the name of the method, followed by the qualified type of the class (e.g. ¡°myPackage.MyClass¡±), primitive type (e.g. ¡°int¡±), etc. of each parameter.

 

For example, the following code returns the RMethod object m for method erase(int x, int y, Box b) in class myPackage.Graphic of project MyProject:

 

RPackage          pkg  = RProject.getPackage(¡°MyProject¡±, ¡°myPackage¡±);

RClassOrInterface cls  = pkg.getClass(¡°Graphic¡±);

String[]          sig = {¡°erase¡±, ¡°int¡±, ¡°int¡±, ¡°myPackage.Box¡±};

RMethod           m   = cls.getMethod(sig);

 

If no such method exists, null is returned.

 

 

Set<RMethod> RClassOrInterface.getAllMethods ( )

 

This method returns the set of RMethod objects that correspond to the methods in the given RClassOrInterface object.

 

For example let cls be the R3 object of class ¡°Graphic¡±.  To print the names of all methods in ¡°Graphic¡±:

 

for ( RMethod m : cls.getAllMethods() ) {

System.out.println(m.getName());

}

 

 

void RClassOrInterface.delete ( )

 

This method deletes the given class.

 

 

RMethod RMethod.moveAndDelegate  (  RParameter p  )

 

Given RParameter p of RMethod m you can move m to p's class. Here is how to move method getBrush(int, Color, Tool) to class Tool:

 

RPackage           pkg = RProject.getPackage(¡°MyProject¡±,¡°myPackage¡±);

RClassOrInterface  cls = pkg.getClass(¡°Palette¡±);

String[]           sig = {¡°getBrush¡±, ¡°int¡±, ¡°myPackage.Color¡±, ¡°myPackage.Tool¡±};

 

RMethod    m = cls.getMethod(sig);

RParameter p = m.getParameter(2);    //Be careful of setting the parameter index. 0 indicates the first parameter!

m.moveAndDelegate(p);

 

An important side effect of moveAndDelegate() is that a delegate method (which has the same name and parameters of the original method) is retained in the class of m. The delegate simply relays any call to the moved method.

 

 

For the given method, moveAndDelegate returns null if invoked on RMethod objects of method declarations that cannot be moved. For example, abstract methods and interface methods cannot be moved.

 

 

RMethod RMethod.move ( RParameter p )

 

This method is a special case of moveAndDelegate. It moves the given method to the given home parameter p. No delegate is created. It always returns null.

 

 

boolean RMethod.isMovable ( RParameter p )

 

It is not always possible to move any methods. For example, abstract methods and interface methods cannot be moved. This method returns true if the given method can be moved. Otherwise, false.

 

 

ArrayList<RMethod> RMethod.getRelatives ( )

 

This is a very important method in R3 refactoring scripts. If you use any of the m() methods in the figure shown below as input to this method, you will be returned the set of ALL m() methods (including the input or ¡°seed¡± method) in the figure below. We say this set of methods is ¡°relatives¡±.  Here¡¯s why this is important: if you rename any m() method, ALL m() methods must be renamed for the program to remain correct. If you add or remove a parameter to any m() method, ALL m() methods must be changed in a similar way. If you want to move m() methods, say into a different hierarchy or into a class, you would individually try to move all of m()¡¯s relatives.

 

 

Programming Idiom (1). The standard way in R3 to perform a method rename, add or remove a parameter to a method, you use the programming loop idiom (below). Let seed be an RMethod object whose relatives we want to rename (or change method signature):

 

for (RMethod temp : seed.getRelatives()) {

temp.rename(¡°newName¡±);

}

 

Often, several operations are performed on each relative, so a getRelatives() loop is more involved than just one (rename) operation.

 

Programming Idiom (2). Here is a standard trick (use case) involving getRelatives(). Suppose you want to move a set of relatives and leave behind delegates. You want to rename each relative to ¡°moo¡± and all of its delegates to ¡°cow¡±. Given a method seed (again, any of one of the target methods), you iterate over its relatives. Each method is moved returning a delegate. You can rename the moved method at this point. So after the first loop, you have moved and renamed all relatives, and you have a delegate variable that is assigned to ONE delegate that was produced (any delegate method will do). You can now use this delegate to find all of its relatives for renaming. Carefully study the code below: you will encounter this in almost any R3 method that you write:

 

RMethod aDelegate = null;

 

// move-and-delegate & rename each relative of the seed

// remembering one delegate

for ( RMethod temp : seed.getRelatives() ) {

if( temp.isMovable() ) {

aDelegate = temp.moveAndDelegate(...)

temp.rename(¡°moo¡±);

}

}

 

// now rename all relatives of the remembered delegate

for ( RMethod temp : aDelegate.getRelatives() ) {

temp.rename(¡°cow¡±);

}

 

 

RParameter RMethod.addParameter ( RClassOrInterface nonPrimitiveType, String defaultValue )

 

This method adds a non-primitive argument of type nonPrimitiveType to the given method. All calls to this method are updated with the default value expressed as the Java String defaultValue. The position at which the parameter is added is the end of the current parameter list.

 

Note: Remember, if you add a parameter to a method, you must add the parameter to all of its relatives. Be sure to follow the programming idiom above.

 

 

boolean RMethod.removeParameter ( RParameter p )

 

This method removes the parameter corresponding to RParameter object p.

 

Note: Remember, if you remove a parameter from a method, you must remove the parameter from all of its relatives. Be sure to follow the programming idiom above.

 

For the given method, it returns false if there is no such parameter.

 

 

RParameter RMethod.getParameter ( int i )

 

For the given method, it returns the ith parameter. 0 indicates the first parameter. null is returned if there is no such parameter.

 

 

void RMethod.rename ( String newName )

 

Rename the given RMethod object to ¡°newName¡±.  Let m be an RMethod object. The programming idiom of rename is:

 

for (RMethod r : m.getRelatives() ) {

r.rename(¡°newName¡±);

}

 

 

RMethod RMethod.replace ( String name )

 

Suppose there are two methods m and n that have the same signatures, except for their names:

 

class MyClass {

void m(A a, B b, C c) {

//body of m

}

 

void n(A a, B b, C c) {

//body of n

}

}

 

The replace refactoring replaces method n with method m by:

 

so the output is:

 

class MyClass {

void m(A a, B b, C c) {

//body of n

}

}

 

The corresponding R3 script looks like:

 

          RMethod m = n.replace(m.getName());

 

If method m (whose signature is the same with n except its name m.getName()) does not exist, null is returned. Otherwise, it returns the RMethod object of m.

 

Please send questions by email:

 

To: jongwook@cs.utexas.edu

Title: [CS373S] yourUTEID