Introduction
In this lab you will learn to build a rewriting system that reads in Java programs and translates them to instrumented versions that track profiling information such as the number of times each method is called. Traditionally one of the hardest parts of this would be to leave the Java code formatted exactly as it was. Parsers have to ignore whitespace and comments, which typically means throwing them out before the parser sees them. This makes it difficult to emit them back out. 
Our goal is to translate single Java files with Main program such as:
class T {
public void foo() {
}
public static void main(String[] args) {
T t = new T();
for (int i=1; i<=10; i++) { t.foo(); }
}
}
into instrumented versions that will track the number of method calls and print the data out upon completion:
class T {
public static int count_foo = 0;
public static int count_main = 0;
public void foo() {
count_foo++;
}
public static void main(String[] args) {
user_main(args);
System.out.println("foo: "+count_foo);
System.out.println("main: "+count_main);
}
public static void user_main(String[] args) {
count_main++;
T t = new T();
for (int i=1; i<=10; i++) { t.foo(); }
}
}
The expected output is:
Implementation
The following operations must be done:
- add integer count for each method, static or instance method.
- insert an increment statement at the start of every method
- adding new main() and replace the old main with user_main()
To perform these operations you must know the list of methods and be able to detect the main() method. For our purposes here, you can assume that any method with the name main is the correct one.
ANTLR provides a nice little rewrite engine called TokenRewriteStream. You can insert stuff, replace, and delete chunks all you want. Note that the operations are done lazily--only if you convert the buffer to a String. This is very efficient because you are not moving data around all the time. As the buffer of tokens is converted to strings, the toString() method(s) check to see if there is an operation at the current index. If so, the operation is done and then normal String rendering continues on the buffer.
Because we are not doing any rewriting of existing input, merely inserting some extra text, we can accomplish this lab with a few actions added to the stock Java grammar. For example, we will need actions to Java.g such as the following to instrument Java method declarations.
((TokenRewriteStream)input).insertAfter($start, "...");
((TokenRewriteStream)input).insertBefore($stop, "...");
The main program looks like:
CharStream input = ... ;
JavaLexer lex = new JavaLexer(input);
TokenRewriteStream tokens = new TokenRewriteStream(lex);
JavaParser parser = new JavaParser(tokens);
parser.compilationUnit();
System.out.print(tokens.toString());