a JList "feature"


Yesterday I was doing performance testing on pro when I hit upon something strange: deleting an email from the mail view was taking too long.

What was even more strange was that this only happened when using the Delete key, and not when using the toolbar icon (strange since essentially the process is the same in both cases). This pointed so something with the keys--but what?

After a bit of debugging I discovered that the slowdown was happening on the repaint thread, which made it more complicated to debug since it happened outside of my control, so to speak. More testing.

Backtracking for a second: JLists can be used in "dynamic" mode: when you set the appropriate parameters the list will only load the items that are visible in the viewport, which is critical when there's access to disk-based data involved. So generally any kind of list activity implies only a few hits on the DB, say 10-20, which happens in a few milliseconds.

So I eventually placed a print statement on the getElementAt call of the ListModel and discovered the problem: when using the Delete key to delete an item, the entire contents of the ListModel where scanned, twice. On a typical email view this meant loading from the DB thousands of objects. Caching was kicking in, of course, but memory usage went through the roof. That, and the time it was taking was not acceptable. Not good.

But why was this happening? I checked and double checked the code and I couldn't find what could possibly be triggering a full reload of the list (twice!). Finally, I started checking the call traces on the getElementAt and I discovered that there's an inner class called KeyHandler inside BasicListUI (that handles the underlying UI for JList) that was doing something strange in its keyPressed method.

So what was it doing? The javadocs for that method say the following:

Moves the keyboard focus to the first element whose first letter matches the alphanumeric key pressed by the user. Subsequent same key presses move the keyboard focus to the next object that starts with the same letter.
When I read this, all I could think of was: Oh. My. God.

The problem, of course is that to determine whether "the next object starts with the same letter" or not it needs to obtain the String for that cell. This means getting the String if the contents are a String, or doing a toString() on the object.

Skipping for a moment on why on earth you'd want complex behavior like this pre-built... what if you're using the JList for an arbitrary component that doesn't translate into a String? What if the toString() is meaningless? Then the listener iterates through the entire list and, of course, fails to lock on to what it was looking for. And when the list is in the process of changing, it does it twice.

Oh yeah, it's a great "feature."

Even worse, the keyPressed call doesn't check for actual alphanumeric keys being pressed. Any key that is not a function key, cursors, or not registered through KeyStrokes (such as Page Up) goes through this loop.

Even if you're not hitting this problem head on as I was, it's strange to think that people that need this behavior would rely on a completely generic implementation that doesn't take into account the underlying storage mechanism.

So how to disable it? Since this "really helpful" listener is registered automatically on any list created, the only way I've found of disabling it is removing it "by hand" right after the list is created, as follows:


JList list = new JList();
KeyListener[] keyListeners = list.getKeyListeners();

for (int i=0; i list.removeKeyListener(keyListeners[i]);
}
By the way, this is the only listener that is pre-registered (I checked) so doing the loop actually just removes the listener in question. It's ugly, but it works.

This leaves me wondering what other "features" are lurking in there. It's pretty bad that something like this is set as default behavior with no warning, on the other hand it's good that once I identified the problem I could fix relatively easily even if the solution is less than ideal. In any case, it was an interesting (if at times maddening) trip into the deep core of Swing.

So if you're wondering why key presses are slowing down your JList implementation, it's quite possible this is the reason why.

Categories: soft.dev
Posted by diego on July 11 2004 at 6:01 PM
Comments (please see the comments & trackback policy).

Report it as a bug or RFE, please.

Posted by: Marcus Sundman at July 11, 2004 10:10 PM

Good info. I never used JList for large lists so I never ran into this behavior. Speaking of behavior, such behaviors should have been packaged as Decorators so one could 'adorn' a control with needed behaviors instead of throwing in smart features not useful to everyone.

Posted by: Don Park at July 12, 2004 12:54 AM

I think this is a very cool feature. It's very useful in IDEA, where I can type "Ctrl+O, t, o, s, Enter" to override toString().

Posted by: Keith Lea at July 12, 2004 4:48 PM

There is clearly a bug that we are not filtering out certain key combinations, and we'll look into fixing that.

You are certainly right that toString may not be appropriate and that some developers might not want this behavior. For this reason we provided the method getNextMatch in JList (and JTree). This method is responsible for finding the appropriate match based on a String. If you don't want automatic selecting based on typed keys you can override this method and return -1. If you still want automatic selecting but want to workaround the bug you could put a dumby binding in the InputMap for delete.

At a higher level it doesn't sound like we've done a good job of documenting this behavior. Where would you like to see this documented that would it have made it more obvious what is going on?

Posted by: Scott Violet at July 15, 2004 4:51 PM

Bug id for this is 5074388, it should show up on the JDC in the next couple of days.

Posted by: Scott Violet at July 15, 2004 5:45 PM

Copyright © Diego Doval 2002-2007.
Powered by
Movable Type 3.35