CMSC 433, Spring 2006

Programming Language Technologies and Paradigms

Project 3


Due 6pm on Wednesday, March 15, 2005 (usual late policy applies)

Updates

Introduction

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.

Background: Refactoring

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.

Background: Java Abstract Syntax Trees and Visitors

You 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: The Java parser used in this project is built using ANTLR, ANother Tool for Language Recognition. You don't need to know anything about ANTLR to do this project. As a quick check, you should be able to run Main (in the cmsc433.p3.helpers package) within Eclipse with the argument Physics.java1, which will parse the file JavaSources/Physics.java1 and print it out in the console (the .java1 extension is not a typo; see below).

The Project

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:

java Main RefactoringVisitorName arg1 ... argn file.java1

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.java1
This 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:

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.

What to Submit

Your submission must include the following classes:

Testing

Note that when we test your refactorings, we will invoke them directly rather than use the Main.java file we gave you. Each refactoring must have a makeVisitor method which returns a NodeVisitor object. Much like in Main.java, we will "invoke" your refactoring by calling accept on a SourceFile object, passing in a NodeVisitor returned from makeVisitor as a parameter. You can think of SourceFile as the root node of the AST. Here is some pseudocode to give you an idea:
  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.

Hints and Notes

Bad Refactorings You May Ignore

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.

Valid HTML 4.01!