Thursday, January 21, 2010

Debugging 401: Practicum

OK, enough with the generalities, it's time for a concrete example. And yes, I realize that debugging stories are much like fishing stories — and that fishing stories aren't terribly interesting. I've tried to stay focused, but I'll understand if you fall asleep.

A week or so ago, I saw a question on Stack Overflow that let me put all of these techniques to work. In fact, it was a nearly perfect case study of the debugging process, not to mention interesting enough to take an hour out of my day.

The question seemed simple enough: the person posting it was trying to set the F2 key as a menu accelerator in a Swing application (for those not familiar with GUIs, an “accelerator” is a way to invoke a menu item using a single keystroke; for an example, Netscape's “New Window” accelerator is Ctrl-N). On the surface, this seemed like user error, and since I happened to be working on a Swing app at the time, I changed one of my menu items to use F2 so that I could respond with the “correct” code. To my surprise, it didn't work: the F2 keystrokes seemed to vanish.

Other responders had suggested that perhaps the operating system was capturing the keypress event before it got to the program, and this seemed like a reasonable hypotheses: think of Alt-Tab or Ctrl-Alt-Delete under Windows. So, applying Scientific Method, my first task was to falsify that hypothesis, and I created a SSCEP to do so.

This first program was extremely simple: a JFrame with a JLabel as its content, and an event handler that listened for keystrokes and printed them. I ran it, hit F2, and saw that the keystrokes were being reported. Time for a new hypothesis.

My second hypothesis was that the F2 keystroke was being remapped by the operating system, so that it appeared to be some other key. Since my SSCEP was printing the keycodes, and the JDK comes with source code that showed the expected keycodes, I could easily check this. Another hypothesis falsified.

Next hypothesis: the Swing menu system is ignoring F2. Seems unlikely, but I'd apparently exhausted all other answers. So I added a menu to the SSCEP, and printed its invocations. And had another hypothesis shot down: F2 invoked the menu item.

By this point I was hooked: I saw the behavior in my own program, but wasn't able to replicate it in a SSCEP. Time to step back, and think about how my SSCEP differed from my actual application. This was easy: my program's main window was a simple tabular display using a JTable; other than that, just a menu. So I replaced the JLabel in my SSCEP with a JTable, and the F2 key stopped working!

Time for some research, starting with Sun's bug database; nothing there about JTable breaking accelerators. A little more research, and I ended up at the Sun tutorial on key bindings. Oh yeah, I remember that: individual components can intercept keystrokes; it's how the text components implement copy and paste without any program code.

This seemed very promising, so I changed my SSCEP to call getInputMap() on my table, and printed the key bindings. And didn't see a binding for F2. However, I was not yet ready to abandon this hypothesis — largely because I didn't have any others waiting.

So I fired up the debugger. Since I suspected that the table was consuming the keystroke itself, I set my first breakpoint in the processKeyEvent() method. This is a short method, so stepped through it and saw that yes, the event was being consumed due to a key binding.

It took me a little more time, but I discovered that the key binding in question wasn't in the “normal” binding map, which was why I didn't see it when I first examined the bindings. I won't bore you with the details; suffice to say that this discovery came from continued application of the scientific method, along with a little background reading of code and JavaDoc. And led to an answer that the original poster seems to have ignored … oh well, at least I had fun debugging.

No comments: