In this project, you will develop a tool to perform automatic refactoring of Java source code. A refactoring is a program transformation that has no effect on how the program behaves. For example, renaming a local variable consistently throughout a method is a kind of refactoring. In your tool, you will use the visitor pattern to traverse an abstract syntax tree (AST) of Java source code and rewrite it. Your tool will work on a subset (detailed later) of the Java programming language.
We will talk in more detail about refactoring in a little while. Everything you need to know about refactoring for this project is described in this document. Do not wait until we get to it in lecture to start the project.
All the code you will need for this project is available in your CVS repository as an Eclipse project. This includes a basic parser for a subset of Java. Ensure that the basic parser working right away; scroll down to "Setting Up the Project" for instructions.
Here is the abstract syntax tree specification for this project.
A refactoring is a program transformation that is semantics-preserving, meaning that the program before and after refactoring has the same behavior. (In practice, refactorings often do modify the behavior of a program very slightly.) Canonical examples of refactorings are renaming local variables or fields consistently, removing an unused parameter from a method, or adding an extra parameter to a method with a default value.
Refactorings are useful in a number of circumstances. They can often be applied to make code clearer and easier to understand. Refactoring is also often a prelude to a more complicated transformation. For example, we might perform a refactoring that adds another parameter to a method with a default value. Once the (behavior-preserving) refactoring is done, then we begin changing the behavior of the program by using the parameter and changing calls to the method so they do not always use the default value. In this case, the idea is that the initial refactoring (adding a method parameter consistently) paves the way for the behavior change.
Each refactoring has one or more inputs that describe how things are going to be refactored. Sometimes refactorings are invalid -- either because they would cause the program to change its behavior, because they would result in code that does not compile, or because of complexity. For example, renaming a local variable so that it has the same name as another local variable is invalid.
In this project, you will implement a series of refactorings, each of which transforms a single Java class. In the description of each case below, we list at least one valid refactoring and some invalid refactoring(s) of the original example. A comprehensive discussion of valid and invalid refactorings can be found in a subsequent section of this project description.
public class Adder {
int count;
public void caller() {
addCount(3, "Adding three");
}
public int addCount(int x, String y) {
count += x;
return count;
}
}
Example: After refactoring method=addCount, n=1
public class Adder {
int count;
public void caller() {
addCount(3);
}
public int addCount(int x) {
count += x;
return count;
}
}
Example: Invalid refactoring: method=addCount, n=0
public class Physics {
public int coef = 0;
public double formula(int offset) {
double answer = 9.81 * (coef + offset);
return answer;
}
}
Example: After refactoring magicNum=9.81, type=double, name=G
public class Physics {
public int coef = 0;
private static final double G = 9.81;
public double formula(int offset) {
double answer = G * (coef + offset);
return answer;
}
}
Example: Invalid refactoring: magicNum=9.81, type=double, name=coef
public class Sales {
public int discount(int quantity, int inputVal, int yearToDate) {
if (inputVal > 100) inputVal -= 2;
if (inputVal > 50) inputVal -= 2;
if (quantity > 4) inputVal -= (inputVal *0.5);
return inputVal;
}
public int specialDiscount(int inputVal) {
int param0 = 10;
inputVal -= param0;
return inputVal;
}
}
Example: After refactoring method=discount
public class Sales {
public int discount(int quantity, int inputVal, int yearToDate) {
int param0 = inputVal;
if (param0 > 100) param0 -= 2;
if (param0 > 50) param0 -= 2;
if (quantity > 4) param0 -= (param0 *0.5);
return param0;
}
public int specialDiscount(int inputVal) {
int param0 = 10;
inputVal -= param0;
return inputVal;
}
}
Example: Invalid refactoring: method=discount2
public class Foo {
public void foo(int a) {
int i = 7;
int j,k;
int l = 8;
j = 40 - a;
k = a * 3;
if (k < j) {
k += i;
}
if (k > j) {
int b = 5;
j += 2;
k -= (l * b);
}
if (k > j) {
k -= l;
}
}
}
Example: After refactoring method=foo,
local=i
public class Foo {
public void foo(int a) {
int j,k;
int l = 8;
j = 40 - a;
k = a * 3;
if (k < j) {
int i = 7;
k += i;
}
if (k > j) {
int b = 5;
j += 2;
k -= (l * b);
}
if (k > j) {
k -= l;
}
}
}
Example: After refactoring method=foo,
local=l
public class Foo {
public void foo(int a) {
int l = 8;
int i = 7;
int j,k;
j = 40 - a;
k = a * 3;
if (k < j) {
k += i;
}
if (k > j) {
int b = 5;
j += 2;
k -= (l * b);
}
if (k > j) {
k -= l;
}
}
}
Example: Invalid refactoring: method=bar, local=iYou will perform refactorings by using the visitor pattern to traverse an abstract syntax tree of Java source code. In case you have forgotten, an abstract syntax tree is a structured representation of program text that elides unimportant (at least to a compiler) details and is unambiguous. For example, here is the abstract syntax tree corresponding to the example program in the first refactoring:
The classes implementing the AST are stored in the package javaparse; here is the specification. Each node in the abstract syntax tree represents an entity in the source program, but details like semicolons, commas, and parentheses are not explicitly represented. Each node in the figure with a capitalized name represents an object whose class extends Node, where each child of that object in the tree is one of its fields. For example, class SourceFile has a single field theClassDef of type ClassDef; in the figure, the topmost node labeled SourceFile represents an object whose class is SourceFile, and therefore has a single child corresponding to SourceFile's field theClassDef, which has class ClassDef. Each leaf in the tree, in italics, is a String literal. For example, the MethodDef object has a field name whose String value is "addCount". In the figure, we show the contents of a string only, eliding quotation marks (unless they actually appear in the string itself). All fields are public to facilitate the use of the visitor pattern.
The interface NodeVisitor describes a visitor to the AST nodes:
public interface NodeVisitor {
public void visit(SourceFile n);
public void visit(Modifier n);
public void visit(Type n);
public void visit(ClassDef n);
public void visit(VariableDef n);
...
}
Each AST Node must implement the Node interface, which defines an
accept method that takes a NodeVisitor and applies it:
public interface Node {
/** Standard Visitor Pattern accept method. Always
invokes v.visit(this). */
public void accept(NodeVisitor v);
}
Review the class notes if you do not understand how visitors work. As
a starting point, the project comes with four visitors, all of which
are stored in the package cmsc433.p3.helpers:
You must write the four refactorings listed above as NodeVisitors. Each of your refactoring classes should have a exactly one static method makeVisitor that produces a NodeVisitor. We have provided you with a file Main.java that can be used to parse a Java program, apply a refactoring, and output the result. It is invoked as follows:
The first argument is the name of the refactoring visitor class you wish to invoke. arg1 through argn are the arguments to the refactoring, and file.java1 is the Java source file to be refactored. The Main method allows this final file to end with either .java or with extension .java1 instead. All of the public tests, and any that you write for student tests, should end in extension .java1 and not .java. This is because of a limitation in the submit server.
When Main is invoked, using reflection it invokes
RefactoringVisitorName.makeVisitor(arg1, ..., argn)Where arg1...argn are passed as Strings to the factory method. It then sequentially parses and applies the refactoring to file.java1, which is assumed to be stored in your JavaSources directory. Main prints the results to standard output using the PrintVisitor. If you pass no RefactoringVisitorName to Main, then the file is parsed and printed with the PrintVisitor. As an example, here is makeVisitor for ChangeClassName:
static public NodeVisitor makeVisitor(String oldName, String newName) {
return new ChangeClassName(oldName, newName);
}
You can try this out by running the Main class with arguments
helpers.ChangeClassName ChangeClassNameTestInput NewName ChangeClassNameTestInput.java1This will output ChangeNameTestInput.java1 but with occurrences of ChangeClassNameTestInput replaced with NewName.
Here is a list of things you don't need to worry about, meaning we won't test your refactorings with any input programs containing these features.
In some cases, it may be impossible to apply a refactoring with the given parameters. For example, you can't remove assignments to parameters for a method that doesn't exist, and it's not a good idea to add a new method with the same name as an already existing method. In all such cases, your tool should raise an IllegalRefactoringException.
Important: The output of your refactorings will be either a refactored AST or an IllegalRefactoringException. Once you raise IllegalRefactoringException, it doesn't matter what you have or have not done to the AST (i.e., you don't need to write any code to restore it to its original state).
Below we give you details about what bad cases you must catch. We may have forgotten about some potentially bad refactorings. Although your tool only needs to handle the bad cases we describe below, the first person to report a new bad refactoring will get 5 extra credit points per refactoring, up to a total of 15 points. Note that you will only get credit for telling us about refactorings that are bad under our assumptions above; we already know there are many other things to worry about for the full Java source language.
You need to write four refactoring visitor classes:
public int discount(int quantity, int inputVal, int yearToDate)and all three parameters of discount are assigned to, then param0 must be used for quantity, param1 for inputVal, and param2 for yearToDate. Even if, yearToDate is assigned to before quantity and inputVal in the body of discount, param2 would still be used for yearToDate.
int i = 7;does not count as a use of i, the following does count as a use of i and as such would limit i's scope
int i; i = 7;
public void foo () {
int i = 0;
int j;
j = 45;
for (; i < 10; i++) {
j += i;
}
}
|
-----> WRONG! |
public void foo () {
int j;
j = 45;
for (int i = 0; i < 10; i++) {
j += i;
}
}
|
The tool will not accept all Java programs. In particular, the parser only handles Java 1.3, and our AST cannot handle all the features of Java. The tool will reject programs using either extends or implements. You do not need to worry about refactorings that interact with the methods of Object; you can pretend for purposes of this project that Object contains no fields or methods. That is, the input programs we give you won't contain references to fields or methods "normally" inherited from Object (e.g., equals() or toString()) because we will pretend Object is empty and, therefore, nothing is inherited from Object.
NodeVisitor v = ReplaceMagicNumber.makeVisitor("...", "...");
SourceFile sf = ....;
sf.accept(v);
Assuming an IllegalRefactoringException was not thrown, when the call
to the SourceFile object's accept method returns, we will look
to make sure you have correctly modified the AST "rooted" at the
SourceFile object.
For your student tests, you should have statement coverage of at least 70\% for the four classes you have to implement; i.e., ReduceScope.java, RemoveParamAssignments.java, RemoveUnusedParam.java, and ReplaceMagicNumber.java (along with any other classes you implement in support of these). Your student tests do not need to test PrintVisitor, ASTVisitor, ChangeClassName, etc.; all of these are in the cmsc433.p3.helpers package for this reason.
Here is a list of bad refactorings you may ignore. You don't need to worry about them (i.e., you don't need to write code to catch these cases):
This list may be updated later so please check the project description frequently.