title: Java LostFocus and InputVerifier, moving in reverse-tab-order
body: <p>I have a GUI application that uses an InputVerifier to check the content of text fields before yielding the focus. This is all very normal. Yesterday, however, discovered a problem - it seems to be a bug, but I cannot find any mention of it anywhere. Before I report this as a bug, I thought I would ask: am I missing something obvious here?</p>
  
  <p>Situation:</p>
  
  <ul>
  <li>A set of text fields with InputVerifiers.</li>
  <li>Listeners for FocusLost and FocusGained on all controls, so I can see what is happening.</li>
  <li>A separate thread uses a DefaultKeyboardFocusManager to report (every 2 seconds) which control has the focus.</li>
  <li>I place invalid data in a JTextField in the middle of the form, and try to leave the control.</li>
  </ul>
  
  <p>If I try to move away from this control using the mouse, or using the tab-key, I cannot. The FocusLost event does not fire and the control properly retains the focus.</p>
  
  <p>However, if I try to move away from the control <i>in reverse tab order, using Shift-Tab</i>, <b>sometimes</b> the FocusLost event fires. If this happens, the separate thread reports that <b>no control has the focus</b>, i.e., getFocusOwner() returns null.
  <hr>
  Edit: below is a small sample program that shows the problem. The problem has nothing to do with the extra thread - the thread is just there to make the problem more obvious. If there is a race-condition, it is somewhere in Swing.</p>
  
  <p>To see the problem, go to the second text box and empty it. The control should retain the focus, and does so <em>unless</em> you leave it by pressing shift-tab. Unlike the full application, the error seems to occur here 100% of the time. This is true both under OpenJDK 6 and under Oracle Java 7.</p>
  
  <p>This is almost too obvious to be a bug, plus it happens in multiple Java environments. Hence, my suspicion that I am missing something obvious. Anyone?</p>
  
  <pre><code>public class FocusBugDemo extends JFrame {
      static JTextField txtOne = new JTextField("Ignore this control");
      static JTextField txtTwo = new JTextField("Delete this text, then press shift-tab");
      static JLabel lblFocus = new JLabel("");
      static KeyboardFocusManager kfm = new DefaultKeyboardFocusManager();
  
      public static void main(String[] args) {
          new FocusBugDemo();
          Thread t = new Thread() {
              @Override
              public void run() {
                  while(true) {
                      Component c = kfm.getFocusOwner();
                      String focusInfo = "elsewhere";
                      if (c == null) {                        focusInfo = "null";
                      } else if (c == txtOne) {       focusInfo = "txtOne";
                      } else if (c == txtTwo) {       focusInfo = "txtTwo";
                      }
                      lblFocus.setText(System.currentTimeMillis() + " - Focus owner " + focusInfo);
                      try {
                          Thread.sleep(1000);
                      } catch (InterruptedException e) {
                      }
                  }
              }
          };
          t.start();
      }
  
      private FocusBugDemo() {
          super("Focus bug demo");
          setDefaultCloseOperation(EXIT_ON_CLOSE);
          setPreferredSize(new Dimension(300,100));
          setLayout(new GridLayout(3,1));
          NotEmpty validator = new NotEmpty();
          txtOne.setInputVerifier(validator);
          txtTwo.setInputVerifier(validator);
          add(txtOne);
          add(txtTwo);
          add(lblFocus);
          pack();
          setVisible(true);
      }
  
      private class NotEmpty extends InputVerifier {
          @Override
          public boolean verify(JComponent input) {
              JTextField txtField = (JTextField) input;
              return (txtField.getText().length() &gt; 0);
          }
      }
  }
  </code></pre>