Swinger Hack: Blow your stack

Here's a common Swing problem. You're a XYZListener and you've just received a fresh new XYZEvent. So you make some changes to XYZ, coloring it blue, doubling its value and removing its tartar sauce attributes. Unfortunately this all fails with an unpleasant error:
XYZ cannot be modified by XYZ's own Listener

Here's when this happens:
  • XYZListener is a DocumentListener and the thing you want to remove tartar sauce from is the Document. The Document slaps you when you try this.
  • XYZListener is a ListSelectionModelListener and the thing you want to change is the ListSelectionModel. The Listener might let this happen, but you're breaking the rules and that's a bad thing.

    The recommended solution
    All over the Internet forums, people hurt you by asking you to work around this problem in painful ways:
  • Subclass PlainDocument or DefaultListSelectionModel
  • Write your own implementation of Document or ListSelectionModel

    my 1337 hack solution
    Instead of fighting the locks by changing your code, you can get outside of them by changing when your code executes: Post a Runnable the the event queue to solve the problem later! So although this code doesn't work:
        public void changedUpdate(DocumentEvent e) {
    String uppercase = myTextField.getText().toUpperCase();

    This code changes the time that setText() is executed, to happen after the listeners have all been notified:
        public void changedUpdate(DocumentEvent e) {
    final String uppercase = myTextField.getText().toUpperCase();
    // avoid infinite loops
    if(uppercase.equals(myTextField.getText())) return;
    // schedule update
    SwingUtilities.invokeLater(new Runnable() {
    public void run() {

    Note that although this hack will help you get your work done, it has some weaknesses when compared to the recommended solution:
  • Your XYZ will be in an intermediate state for a microsecond between the original change and your update that follows.
  • You need to avoid infinite loops by not calling invokeAndWait() in response to events that you initiated.