Clover coverage report - clover
Coverage timestamp: Sat Oct 8 2005 22:54:17 EDT
file stats: LOC: 440   Methods: 25
NCLOC: 323   Classes: 5
 
 Source file Conditionals Statements Methods TOTAL
WindowTracker.java 83.8% 93.1% 100% 91.3%
coverage coverage
 1   
 package abbot.tester;
 2   
 
 3   
 import java.awt.*;
 4   
 import java.awt.event.*;
 5   
 import javax.swing.JFrame;
 6   
 import javax.swing.SwingUtilities;
 7   
 import java.lang.ref.*;
 8   
 import java.util.*;
 9   
 
 10   
 import abbot.Log;
 11   
 import abbot.Platform;
 12   
 import abbot.util.Properties;
 13   
 import abbot.util.WeakAWTEventListener;
 14   
 import abbot.util.NamedTimer;
 15   
 
 16   
 /** Keep track of all known root windows, and all known showing/hidden/closed
 17   
  * windows.
 18   
  */
 19   
 public class WindowTracker {
 20   
     
 21   
     private static class Holder {
 22   
         public static final WindowTracker INSTANCE = new WindowTracker();
 23   
     }
 24   
     /** Maps unique event queues to the set of root windows found on each
 25   
         queue. 
 26   
      */
 27   
     private Map contexts;
 28   
     /** Maps components to their corresponding event queues. */
 29   
     private Map queues;
 30   
     private ContextTracker contextTracker;
 31   
     /** Windows which for which isShowing is true but are not yet ready for
 32   
         input. */
 33   
     private Map pendingWindows = new WeakHashMap();
 34   
     /** Windows which we deem are ready to use.  */
 35   
     private Map openWindows = new WeakHashMap();
 36   
     /** Windows which are not visible. */
 37   
     private Map hiddenWindows = new WeakHashMap();
 38   
     /** Windows which have sent a WINDOW_CLOSE event. */
 39   
     private Map closedWindows = new WeakHashMap();
 40   
     private WindowReadyTracker windowReadyTracker;
 41   
     private java.awt.Robot robot;
 42   
     static int WINDOW_READY_DELAY = 
 43   
         Properties.getProperty("abbot.window_ready_delay", 5000, 0, 60000);
 44   
     private Timer windowReadyTimer;
 45   
     
 46   
     /** Only ever want one of these. */
 47  807
     public static WindowTracker getTracker() {
 48  807
         return Holder.INSTANCE;
 49   
     }
 50   
 
 51   
     /** Create an instance of WindowTracker which will track all windows
 52   
      * coming and going on the current and subsequent app contexts. 
 53   
      * WARNING: if an applet loads this class, it will only ever see stuff in
 54   
      * its own app context.
 55   
      */
 56  107
     WindowTracker() {
 57  107
         contextTracker = new ContextTracker();
 58  107
         long mask = WindowEvent.WINDOW_EVENT_MASK
 59   
             | ComponentEvent.COMPONENT_EVENT_MASK;
 60  107
         new WeakAWTEventListener(contextTracker, mask);
 61  107
         windowReadyTracker = new WindowReadyTracker();
 62  107
         mask = InputEvent.MOUSE_MOTION_EVENT_MASK
 63   
             |InputEvent.MOUSE_EVENT_MASK|InputEvent.PAINT_EVENT_MASK;
 64  107
         new WeakAWTEventListener(windowReadyTracker, mask);
 65   
         // hold the event queue references weakly
 66   
         // each queue maps to a set of components (actually a weak hash map to
 67   
         // allow GC of the component keys).
 68  107
         contexts = new WeakHashMap();
 69  107
         contexts.put(Toolkit.getDefaultToolkit().getSystemEventQueue(),
 70   
                      new WeakHashMap());
 71   
         // hold both the component references and the event queues weakly
 72  107
         queues = new WeakHashMap();
 73   
         // Populate stuff that may already have shown/been hidden
 74  107
         Frame[] frames = Frame.getFrames();
 75  107
         synchronized(openWindows) {
 76  107
             for (int i=0;i < frames.length;i++) {
 77  33
                 scanExistingWindows(frames[i]);
 78   
             }
 79   
         }
 80  107
         try {
 81  107
             robot = new java.awt.Robot();
 82   
         }
 83   
         catch(AWTException e) {
 84   
         }
 85  107
         windowReadyTimer = new NamedTimer("Window Ready Timer", true);
 86   
     }
 87   
 
 88  37
     private void scanExistingWindows(Window w) {
 89   
         // Make sure we catch subsequent show/hide events for this window
 90  37
         new WindowWatcher(w);
 91  37
         Window[] windows = w.getOwnedWindows();
 92  37
         for (int i=0;i < windows.length;i++) {
 93  4
             scanExistingWindows(windows[i]);
 94   
         }
 95  37
         openWindows.put(w, Boolean.TRUE);
 96  37
         if (!w.isShowing()) {
 97  37
             hiddenWindows.put(w, Boolean.TRUE);
 98   
         }
 99  37
         noteContext(w);
 100   
     }
 101   
 
 102   
     /** Returns whether the window is ready to receive OS-level event input.
 103   
         A window's "isShowing" flag may be set true before the WINDOW_OPENED
 104   
         event is generated, and even after the WINDOW_OPENED is sent the
 105   
         window peer is not guaranteed to be ready.
 106   
     */ 
 107  10618
     public boolean isWindowReady(Window w) {
 108  10618
         synchronized(openWindows) {
 109  10618
             if (openWindows.containsKey(w)
 110   
                 && !hiddenWindows.containsKey(w)) {
 111  2240
                 return true;
 112   
             }
 113   
         }
 114  8378
         if (robot != null)
 115  8378
             checkWindow(w, robot);
 116  8378
         return false;
 117   
     }
 118   
 
 119   
     /** Return the event queue corresponding to the given component.  In most
 120   
         cases, this is the same as
 121   
         Component.getToolkit().getSystemEventQueue(), but in the case of
 122   
         applets will bypass the AppContext and provide the real event queue.
 123   
     */
 124  817
     public EventQueue getQueue(Component c) {
 125   
         // Components above the applet in the hierarchy may or may not share
 126   
         // the same context with the applet itself.
 127  817
         while (!(c instanceof java.applet.Applet) && c.getParent() != null)
 128  3201
             c = c.getParent();
 129  817
         synchronized(contexts) {
 130  817
             WeakReference ref = (WeakReference)queues.get(c);
 131  817
             EventQueue q = ref != null ? (EventQueue)ref.get() : null;
 132  817
             if (q == null)
 133  15
                 q = c.getToolkit().getSystemEventQueue();
 134  817
             return q;
 135   
         }
 136   
     }
 137   
 
 138   
     /** Returns all known event queues. */
 139  1646
     public Collection getEventQueues() {
 140  1646
         HashSet set = new HashSet();
 141  1646
         synchronized(contexts) {
 142  1646
             set.addAll(contexts.keySet());
 143  1646
             Iterator iter = queues.values().iterator();
 144  1646
             while (iter.hasNext()) {
 145  39019
                 WeakReference ref = (WeakReference)iter.next();
 146  39019
                 EventQueue q = (EventQueue)ref.get();
 147  39019
                 if (q != null)
 148  39019
                     set.add(q);
 149   
             }
 150   
         }
 151  1646
         return set;
 152   
     }
 153   
 
 154   
     /** Return all available root Windows.  A root Window is one
 155   
      * that has a null parent.  Nominally this means a list similar to that
 156   
      * returned by Frame.getFrames(), but in the case of an Applet may return
 157   
      * a few Dialogs as well.
 158   
      */
 159  14666
     public Collection getRootWindows() {
 160  14666
         Set set = new HashSet();
 161   
         // Use Frame.getFrames() here in addition to our watched set, just in
 162   
         // case any of them is missing from our set.
 163  14666
         synchronized(contexts) {
 164  14666
             Iterator iter = contexts.keySet().iterator();
 165  14666
             while (iter.hasNext()) {
 166  14983
                 EventQueue queue = (EventQueue)iter.next();
 167  14983
                 Map map = (Map)contexts.get(queue);
 168  14983
                 set.addAll(map.keySet());
 169   
             }
 170   
         }
 171  14666
         Frame[] frames = Frame.getFrames();
 172  14666
         for (int i=0;i < frames.length;i++) {
 173  824615
             set.add(frames[i]);
 174   
         }
 175   
         //Log.debug(String.valueOf(list.size()) + " total Frames");
 176  14666
         return set;
 177   
     }
 178   
 
 179   
     /** Provides tracking of window visibility state.  We explicitly add this
 180   
      * on WINDOW_OPEN and remove it on WINDOW_CLOSE to avoid having to process
 181   
      * extraneous ComponentEvents.
 182   
      */
 183   
     private class WindowWatcher
 184   
         extends WindowAdapter implements ComponentListener {
 185  2191
         public WindowWatcher(Window w) {
 186  2191
             w.addComponentListener(this);
 187  2191
             w.addWindowListener(this);
 188   
         }
 189  1260
         public void componentShown(ComponentEvent e) {
 190  1260
             markWindowShowing((Window)e.getSource());
 191   
         }
 192  1119
         public void componentHidden(ComponentEvent e) {
 193  1119
             synchronized(openWindows) {
 194   
                 //Log.log("Marking " + e.getSource() + " hidden");
 195  1119
                 hiddenWindows.put(e.getSource(), Boolean.TRUE);
 196  1119
                 pendingWindows.remove(e.getSource());
 197   
             }
 198   
         }
 199  2136
         public void windowClosed(WindowEvent e) {
 200  2136
             e.getWindow().removeWindowListener(this);
 201  2136
             e.getWindow().removeComponentListener(this);
 202   
         }
 203  214
         public void componentResized(ComponentEvent e) { }
 204  314
         public void componentMoved(ComponentEvent e) { }
 205   
     }
 206   
 
 207   
     /** Whenever we get a window that's on a new event dispatch thread, take
 208   
      * note of the thread, since it may correspond to a new event queue and
 209   
      * AppContext. 
 210   
      */
 211   
     // FIXME what if it has the same app context? can we check?
 212   
     private class ContextTracker implements AWTEventListener {
 213  59143
         public void eventDispatched(AWTEvent ev) {
 214   
 
 215  59143
             ComponentEvent event = (ComponentEvent)ev;
 216  59143
             Component comp = event.getComponent();
 217   
             // This is our sole means of accessing other app contexts
 218   
             // (if running within an applet).  We look for window events
 219   
             // beyond OPENED in order to catch windows that have already
 220   
             // opened by the time we start listening but which are not
 221   
             // in the Frame.getFrames list (i.e. they are on a different
 222   
             // context).   Specifically watch for COMPONENT_SHOWN on applets,
 223   
             // since we may not get frame events for them.
 224  59143
             if (!(comp instanceof java.applet.Applet)
 225   
                 && !(comp instanceof Window)) {
 226  40815
                 return;
 227   
             }
 228   
 
 229  18328
             int id = ev.getID();
 230  18328
             if (id == WindowEvent.WINDOW_OPENED) {
 231  1615
                 noteOpened(comp);
 232   
             }
 233  16713
             else if (id == WindowEvent.WINDOW_CLOSED) {
 234  1894
                 noteClosed(comp);
 235   
             }
 236  14819
             else if (id == WindowEvent.WINDOW_CLOSING) {
 237   
                 // ignore
 238   
             }
 239   
             // Take note of all other window events
 240  14806
             else if ((id >= WindowEvent.WINDOW_FIRST
 241   
                       && id <= WindowEvent.WINDOW_LAST)
 242   
                      || id == ComponentEvent.COMPONENT_SHOWN) {
 243  10533
                 synchronized(openWindows) {
 244  10533
                     if (!getRootWindows().contains(comp)
 245   
                         || closedWindows.containsKey(comp)) {
 246  552
                         noteOpened(comp);
 247   
                     }
 248   
                 }
 249   
             }
 250   
             // The context for root-level windows may change between
 251   
             // WINDOW_OPENED and subsequent events.
 252  18328
             synchronized(contexts) {
 253  18328
                 WeakReference ref = (WeakReference)queues.get(comp);
 254  18328
                 if (ref != null
 255   
                     && !comp.getToolkit().getSystemEventQueue().
 256   
                     equals(ref.get())) {
 257  50
                     noteContext(comp);
 258   
                 }
 259   
             }
 260   
         }
 261   
     }
 262   
 
 263   
     private class WindowReadyTracker implements AWTEventListener {
 264  18712
         public void eventDispatched(AWTEvent e) {
 265  18712
             if (e.getID() == MouseEvent.MOUSE_MOVED
 266   
                 || e.getID() == MouseEvent.MOUSE_DRAGGED) {
 267  7817
                 Component c = (Component)e.getSource();
 268  7817
                 Window w = c instanceof Window
 269   
                     ? (Window)c
 270   
                     : SwingUtilities.getWindowAncestor(c);
 271  7817
                 markWindowReady(w);
 272   
             }
 273   
         }
 274   
     }
 275  2254
     private void noteContext(Component comp) {
 276  2254
         EventQueue queue = comp.getToolkit().getSystemEventQueue();
 277  2254
         synchronized(contexts) {
 278  2254
             Map map = (Map)contexts.get(queue);
 279  2254
             if (map == null) {
 280  16
                 contexts.put(queue, map = new WeakHashMap());
 281   
             }
 282  2254
             if (comp instanceof Window && comp.getParent() == null) {
 283  1531
                 map.put(comp, Boolean.TRUE);
 284   
             }
 285  2254
             queues.put(comp, new WeakReference(queue));
 286   
         }
 287   
     }
 288   
 
 289  2167
     private void noteOpened(Component comp) {
 290   
         //Log.log("Noting " + comp + " opened");
 291  2167
         noteContext(comp);
 292   
         // Attempt to ensure the window is ready for input before recognizing
 293   
         // it as "open".  There is no Java API for this, so we institute an
 294   
         // empirically tested delay.
 295  2167
         if (comp instanceof Window) {
 296  2154
             new WindowWatcher((Window)comp);
 297  2154
             markWindowShowing((Window)comp);
 298   
         }
 299   
     }
 300   
 
 301  1894
     private void noteClosed(Component comp) {
 302  1894
         if (comp.getParent() == null) {
 303  1486
             EventQueue queue = comp.getToolkit().getSystemEventQueue();
 304  1486
             synchronized(contexts) {
 305  1486
                 Map whm = (Map)contexts.get(queue);
 306  1486
                 if (whm != null)
 307  1486
                     whm.remove(comp);
 308   
                 else {
 309  0
                     EventQueue foundQueue = null;
 310  0
                     Iterator iter = contexts.keySet().iterator();
 311  0
                     while (iter.hasNext()) {
 312  0
                         EventQueue q = (EventQueue)iter.next();
 313  0
                         Map map = (Map)contexts.get(q);
 314  0
                         if (map.containsKey(comp)) {
 315  0
                             foundQueue = q;
 316  0
                             map.remove(comp);
 317   
                         }
 318   
                     }
 319  0
                     if (foundQueue == null) {
 320  0
                         Log.log("Got WINDOW_CLOSED on "
 321   
                                 + Robot.toString(comp)
 322   
                                 + " on a previously unseen context: "
 323   
                                 + queue + "("
 324   
                                 + Thread.currentThread() + ")");
 325   
                     }
 326   
                     else {
 327  0
                         Log.log("Window " + Robot.toString(comp)
 328   
                                 + " sent WINDOW_CLOSED on "
 329   
                                 + queue + " but sent WINDOW_OPENED on "
 330   
                                 + foundQueue);
 331   
                     }
 332   
                 }
 333   
             }
 334   
         }
 335  1894
         synchronized(openWindows) {
 336   
             //Log.log("Marking " + comp + " closed");
 337  1894
             openWindows.remove(comp);
 338  1894
             hiddenWindows.remove(comp);
 339  1894
             closedWindows.put(comp, Boolean.TRUE);
 340  1894
             pendingWindows.remove(comp);
 341   
         }
 342   
     }
 343   
 
 344   
     /** Mark the given Window as ready for input.  Indicate whether any
 345   
      * pending "mark ready" task should be canceled.
 346   
      */
 347  14620
     private void markWindowReady(Window w) {
 348  14623
         synchronized(openWindows) {
 349   
             // If the window was closed after the check timer started running,
 350   
             // it will have canceled the pending ready.
 351   
             // Make sure it's still on the pending list before we actually
 352   
             // mark it ready.
 353  14623
             if (pendingWindows.containsKey(w)) {
 354   
                 //Log.log("Noting " + w + " ready");
 355  2870
                 closedWindows.remove(w);
 356  2870
                 hiddenWindows.remove(w);
 357  2870
                 openWindows.put(w, Boolean.TRUE);
 358  2870
                 pendingWindows.remove(w);
 359   
             }
 360   
         }
 361   
     }
 362   
 
 363   
     /** Indicate a window has set isShowing true and needs to be marked ready
 364   
         when it is actually ready.
 365   
     */
 366  3414
     private void markWindowShowing(final Window w) {
 367  3414
         synchronized(openWindows) {
 368  3414
             pendingWindows.put(w, Boolean.TRUE);
 369   
         }
 370   
     }
 371   
 
 372  8753
     private Insets getInsets(Container c) {
 373  8753
         try {
 374  8753
             Insets insets = c.getInsets();
 375  3857
             if (insets != null)
 376  3857
                 return insets;
 377   
         }
 378   
         catch(NullPointerException e) {
 379   
             // FileDialog.getInsets() throws (1.4.2_07)
 380   
         }
 381  4896
         return new Insets(0, 0, 0, 0);
 382   
     }
 383   
 
 384   
     private static int sign = 1;
 385   
     /** Actively check whether the given window is ready for input.
 386   
      * @param robot
 387   
      * @see #isWindowReady
 388   
      */
 389  8378
     private void checkWindow(final Window w, java.awt.Robot robot) {
 390   
         // Must avoid frame borders, which are insensitive to mouse
 391   
         // motion (at least on w32).
 392  8378
         final Insets insets = getInsets(w);
 393  8378
         final int width = w.getWidth();
 394  8378
         final int height = w.getHeight();
 395  8378
         int x = w.getX() + insets.left
 396   
             + (width-(insets.left+insets.right))/2;
 397  8378
         int y = w.getY() + insets.top
 398   
             + (height-(insets.top+insets.bottom))/2;
 399  8378
         if (x != 0 && y != 0) {
 400  6336
             robot.mouseMove(x, y);
 401  6336
             if (width > height)
 402  2532
                 robot.mouseMove(x + sign, y);
 403   
             else
 404  3804
                 robot.mouseMove(x, y + sign);
 405  6336
             sign = -sign;
 406   
         }
 407  8378
         synchronized(openWindows) {
 408  8378
             if (pendingWindows.get(w) == Boolean.TRUE
 409   
                 && isEmptyFrame(w)) {
 410   
                 // Force the window to be large enough
 411  21
                 SwingUtilities.invokeLater(new Runnable() {
 412  21
                     public void run() {
 413  21
                         int nw = Math.max(width,
 414   
                                          insets.left + insets.right + 1);
 415  21
                         int nh = Math.max(height,
 416   
                                          insets.top + insets.bottom + 1);
 417  21
                         w.setSize(nw, nh);
 418   
                     }
 419   
                 });
 420   
             }
 421   
             // At worst, time out and say the window is ready
 422   
             // after the configurable delay
 423  8378
             TimerTask task = new TimerTask() {
 424  6803
                 public void run() {
 425  6806
                     markWindowReady(w);
 426   
                 }
 427   
             };
 428  8378
             windowReadyTimer.schedule(task, WINDOW_READY_DELAY);
 429  8378
             pendingWindows.put(w, task);
 430   
         }
 431   
     }
 432   
 
 433   
     /** We can't get any motion events on an empty frame. */
 434  375
     private boolean isEmptyFrame(Window w) {
 435  375
         Insets insets = getInsets(w);
 436  375
         return insets.top + insets.bottom == w.getHeight()
 437   
             || insets.left + insets.right == w.getWidth();
 438   
     }
 439   
 }
 440