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 |
|
RMethod |
getMethod(String...) |
|
ArrayList<RMethod> |
||
void
|
delete() |
|
RMethod |
RMethod |
moveAndDelegate(RParameter) |
RMethod |
move(RParameter) |
|
boolean |
isMovable(RParameter) |
|
RParameter |
addParameter(RClassOrInterface, String) |
|
boolean |
removeParameter(RParameter) |
|
RParameter |
getParameter(int) |
|
ArrayList<RMethod> |
||
void
|
rename(String) |
|
RMethod |
replace(String) |
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.
RMethod RMethod.move ( RParameter p )
boolean RMethod.isMovable
( RParameter p )
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:
Title: [CS373S] yourUTEID