I write a lot of code in Java and as you might know (if you have read some of my blogposts before) I love to use Emacs for all things related to typing on the keyboard.
When you write large applications in Java, or any other language really, you will eventually need to utilize the debugger. This is simply due to the fact that you will never have 100% code coverage and that it is nearly impossible to fully anticipate how external system integration behaves at runtime.
One of the best debuggers around (in Java land at least) is the one within Netbeans. Besides good integration with the editing process it provides HotSwapping of class files. This means that while you are in a debugging session you can make alterations to code, upload it to the running JVM and execute that piece of code without having to restart the entire debugging session. I wanted to utilize this feature from within Emacs; the following describes how the core feature works.
The ability to redefine classes was actually added to the 1.4.2 JVM and is part of the actual JPDA specification. Of course sun implemented this in their reference implementation which is delivered with each Java SDK; jdb.
Thats cool; so how do we use it? Well, lets create a totally useless application to illustrate the feature. We’ll make a nice Hello World application, consisting of 2 files Main.java and Hello.java:
public class Main { public static void main(String args[]) { Hello hello = new Hello(); for (int i = 0; i < 100; ++i) { hello.say("World!"); } } } public class Hello { public void say(String what) { System.out.println("Helo, " + what); } }
As you can see, quite simple. The main program will loop 100 times printing “Helo, World!”. Notice the typing error, we will fix this without leaving the debugger really.
So, lets compile the source files by running javac -g Main.java. Both source files will be compile when you use the Java 5 or 6 compiler. Now, for the fun stuff… lets start debugging. We will start the debugger with the Main class and set a breakpoint in the method Hello.say after which we will run the application.
arjenw@machine:~/test$ jdb Main
Initializing jdb ...
> stop in Hello.say
Deferring breakpoint Hello.say.
It will be set after the class is loaded.
> run
run Main
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Set deferred breakpoint Hello.say
Breakpoint hit: "thread=main", Hello.say(), line=3 bci=0
3 System.out.println("Helo, " + what)
main[1] As you see, quickly after running the program the breakpoint is hit and jdb prints out where it is. You can clearly see the typo on the screen. Lets fix it!
public class Hello { public void say(String what) { System.out.println("Hello, " + what); } }
Then; recompile the class and lets redefine it in the debugging session.
main[1] redefine Hello Hello.class
main[1] reenter
>
Step completed: "thread=main", Hello.say(), line=3 bci=0
3 System.out.println("Hello, " + what);
main[1]
After redefining the class, which is done with the appropriately named redefine which takes as arguments the class name (including package name) and the filesystem location of the file, we reenter the current frame to reload the class file and as you see, the typo is fixed in the current debugging session.
There are, of course, several limitations to this feature; you can only create new classes and change method bodies. You can not do things like adding methods or fields and you can neither change the object hierarchy (extends / implements).
Since you can fully run jdb from within Emacs, by running M-x jdb, it only requires you to write some small elisp code snippets to prevent having to type the redefine statements based on the way you utilize Emacs with Java.