1 /*
   2  * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.netbeans.jemmy;
  24 
  25 import java.awt.Component;
  26 import java.awt.Frame;
  27 import java.awt.Window;
  28 import java.util.stream.Stream;
  29 
  30 /**
  31  * A WindowWaiter is a utility class used to look or wait for Windows. It
  32  * contains methods to search for a Window among the currently showing Windows
  33  * as well as methods that wait for a Window to show within an allotted time
  34  * period.
  35  *
  36  * Searches and waits always involve search criteria applied by a
  37  * ComponentChooser instance. Searches and waits can both be restricted to
  38  * windows owned by a given window.
  39  *
  40  * <BR>Timeouts used: <BR>
  41  * WindowWaiter.WaitWindowTimeout - time to wait window displayed <BR>
  42  * WindowWaiter.AfterWindowTimeout - time to sleep after window has been
  43  * dispayed <BR>
  44  *
  45  * @see org.netbeans.jemmy.Timeouts
  46  *
  47  * @author Alexandre Iline (alexandre.iline@oracle.com)
  48  */
  49 public class WindowWaiter extends Waiter<Window, Void> implements Timeoutable {
  50 
  51     public static boolean FIND_INVISIBLE_WINDOWS = false;
  52 
  53     private final static long WAIT_TIME = 60000;
  54     private final static long AFTER_WAIT_TIME = 0;
  55 
  56     private ComponentChooser chooser;
  57     private Window owner = null;
  58     private int index = 0;
  59     private Timeouts timeouts;
  60 
  61     /**
  62      * Constructor.
  63      */
  64     public WindowWaiter() {
  65         super();
  66         setTimeouts(JemmyProperties.getProperties().getTimeouts());
  67     }
  68 
  69     /**
  70      * Searches for a window. The search proceeds among the currently showing
  71      * windows for the {@code index+1}'th window that is both owned by the
  72      * {@code java.awt.Window} {@code owner} and that meets the
  73      * criteria defined and applied by the {@code ComponentChooser}
  74      * parameter.
  75      *
  76      * @param owner The owner window of all the windows to be searched.
  77      * @param cc A component chooser used to define and apply the search
  78      * criteria.
  79      * @param index The ordinal index of the window in the set of currently
  80      * displayed windows with the proper window ownership and a suitable title.
  81      * The first index is 0.
  82      * @return a reference to the {@code index+1}'th window that is
  83      * showing, has the proper window ownership, and that meets the search
  84      * criteria. If there are fewer than {@code index+1} windows, a
  85      * {@code null} reference is returned.
  86      */
  87     public static Window getWindow(Window owner, ComponentChooser cc, int index) {
  88         return getAWindow(owner, new IndexChooser(cc, index));
  89     }
  90 
  91     /**
  92      * Searches for a window. Search among the currently showing windows for the
  93      * first that is both owned by the {@code java.awt.Window}
  94      * {@code owner} and that meets the search criteria applied by the
  95      * {@code ComponentChooser} parameter.
  96      *
  97      * @param owner The owner window of the windows to be searched.
  98      * @param cc A component chooser used to define and apply the search
  99      * criteria.
 100      * @return a reference to the first window that is showing, has a proper
 101      * owner window, and that meets the search criteria. If no such window can
 102      * be found, a {@code null} reference is returned.
 103      */
 104     public static Window getWindow(Window owner, ComponentChooser cc) {
 105         return getWindow(owner, cc, 0);
 106     }
 107 
 108     /**
 109      * Searches for a window. The search proceeds among the currently showing
 110      * windows for the {@code index+1}'th window that meets the criteria
 111      * defined and applied by the {@code ComonentChooser} parameter.
 112      *
 113      * @param cc A component chooser used to define and apply the search
 114      * criteria.
 115      * @param index The ordinal index of the window in the set of currently
 116      * displayed windows. The first index is 0.
 117      * @return a reference to the {@code index+1}'th window that is showing
 118      * and that meets the search criteria. If there are fewer than
 119      * {@code index+1} windows, a {@code null} reference is returned.
 120      */
 121     public static Window getWindow(ComponentChooser cc, int index) {
 122         return getAWindow(new IndexChooser(cc, index));
 123     }
 124 
 125     /**
 126      * Searches for a window. Search among the currently showing windows for one
 127      * that meets the search criteria applied by the
 128      * {@code ComponentChooser} parameter.
 129      *
 130      * @param cc A component chooser used to define and apply the search
 131      * criteria.
 132      * @return a reference to the first window that is showing and that meets
 133      * the search criteria. If no such window can be found, a {@code null}
 134      * reference is returned.
 135      */
 136     public static Window getWindow(ComponentChooser cc) {
 137         return getWindow(cc, 0);
 138     }
 139 
 140     static {
 141         Timeouts.initDefault("WindowWaiter.WaitWindowTimeout", WAIT_TIME);
 142         Timeouts.initDefault("WindowWaiter.AfterWindowTimeout", AFTER_WAIT_TIME);
 143     }
 144 
 145     /**
 146      * Defines current timeouts.
 147      *
 148      * @param timeouts A collection of timeout assignments.
 149      * @see org.netbeans.jemmy.Timeoutable
 150      * @see org.netbeans.jemmy.Timeouts
 151      * @see #getTimeouts
 152      */
 153     @Override
 154     public void setTimeouts(Timeouts timeouts) {
 155         this.timeouts = timeouts;
 156         Timeouts times = timeouts.cloneThis();
 157         times.setTimeout("Waiter.WaitingTime",
 158                 timeouts.getTimeout("WindowWaiter.WaitWindowTimeout"));
 159         times.setTimeout("Waiter.AfterWaitingTime",
 160                 timeouts.getTimeout("WindowWaiter.AfterWindowTimeout"));
 161         setWaitingTimeOrigin("WindowWaiter.WaitWindowTimeout");
 162         super.setTimeouts(times);
 163     }
 164 
 165     /**
 166      * Return current timeouts.
 167      *
 168      * @return the collection of current timeout assignments.
 169      * @see org.netbeans.jemmy.Timeoutable
 170      * @see org.netbeans.jemmy.Timeouts
 171      * @see #setTimeouts
 172      */
 173     @Override
 174     public Timeouts getTimeouts() {
 175         return timeouts;
 176     }
 177 
 178     /**
 179      * Action producer--get a window. Get a window. The search uses constraints
 180      * on window ownership, ordinal index, and search criteria defined by an
 181      * instance of {@code org.netbeans.jemmy.ComponentChooser}.
 182      *
 183      * @param obj Not used.
 184      * @return the window waited upon. If a window cannot be found then a
 185      * {@code null} reference is returned.
 186      * @see org.netbeans.jemmy.Action
 187      */
 188     @Override
 189     public Window actionProduced(Void obj) {
 190         return WindowWaiter.getWindow(owner, chooser, index);
 191     }
 192 
 193     /**
 194      * Waits for a window to show. Wait for the {@code index+1}'th window
 195      * that meets the criteria defined and applied by the
 196      * {@code ComonentChooser} parameter to show up.
 197      *
 198      * @param ch A component chooser used to define and apply the search
 199      * criteria.
 200      * @param index The ordinal index of the window in the set of currently
 201      * displayed windows. The first index is 0.
 202      * @return a reference to the {@code index+1}'th window that shows and
 203      * that meets the search criteria. If fewer than {@code index+1}
 204      * windows show up in the allotted time period then a {@code null}
 205      * reference is returned.
 206      * @throws TimeoutExpiredException
 207      * @see #actionProduced(Object)
 208      * @exception InterruptedException
 209      */
 210     public Window waitWindow(ComponentChooser ch, int index)
 211             throws InterruptedException {
 212         chooser = ch;
 213         owner = null;
 214         this.index = index;
 215         return waitWindow();
 216     }
 217 
 218     /**
 219      * Waits for a window to show. Wait for a window that meets the search
 220      * criteria applied by the {@code ComponentChooser} parameter to show
 221      * up.
 222      *
 223      * @param ch A component chooser used to define and apply the search
 224      * criteria.
 225      * @return a reference to the first window that shows and that meets the
 226      * search criteria. If no such window can be found within the time period
 227      * allotted, a {@code null} reference is returned.
 228      * @throws TimeoutExpiredException
 229      * @see #actionProduced(Object)
 230      * @exception InterruptedException
 231      */
 232     public Window waitWindow(ComponentChooser ch)
 233             throws InterruptedException {
 234         return waitWindow(ch, 0);
 235     }
 236 
 237     /**
 238      * Waits for a window to show. Wait for the {@code index+1}'th window
 239      * to show that is both owned by the {@code java.awt.Window}
 240      * {@code o} and that meets the criteria defined and applied by the
 241      * {@code ComponentChooser} parameter.
 242      *
 243      * @param o The owner window of all the windows to be searched.
 244      * @param ch A component chooser used to define and apply the search
 245      * criteria.
 246      * @param index The ordinal index of the window in the set of currently
 247      * displayed windows with the proper window ownership and a suitable title.
 248      * The first index is 0.
 249      * @return a reference to the {@code index+1}'th window to show that
 250      * has the proper window ownership, and that meets the search criteria. If
 251      * there are fewer than {@code index+1} windows, a {@code null}
 252      * reference is returned.
 253      * @throws TimeoutExpiredException
 254      * @see #actionProduced(Object)
 255      * @exception InterruptedException
 256      */
 257     public Window waitWindow(Window o, ComponentChooser ch, int index)
 258             throws InterruptedException {
 259         owner = o;
 260         chooser = ch;
 261         this.index = index;
 262         return waitAction(null);
 263     }
 264 
 265     /**
 266      * Waits for a window to show. Wait for the first window to show that is
 267      * both owned by the {@code java.awt.Window} {@code o} and that
 268      * meets the criteria defined and applied by the
 269      * {@code ComponentChooser} parameter.
 270      *
 271      * @param o The owner window of all the windows to be searched.
 272      * @param ch A component chooser used to define and apply the search
 273      * criteria.
 274      * @return a reference to the first window to show that has the proper
 275      * window ownership, and that meets the search criteria. If there is no such
 276      * window, a {@code null} reference is returned.
 277      * @throws TimeoutExpiredException
 278      * @see #actionProduced(Object)
 279      * @exception InterruptedException
 280      */
 281     public Window waitWindow(Window o, ComponentChooser ch)
 282             throws InterruptedException {
 283         return waitWindow(o, ch, 0);
 284     }
 285 
 286     /**
 287      * Wait till the count of windows which meet the the search criteria becomes
 288      * equal to count.
 289      *
 290      * @param ch a component chooser used to define and apply the search
 291      * criteria.
 292      * @param count the number of expected windows meeting the search criteria.
 293      * @throws InterruptedException
 294      */
 295     public static void waitWindowCount(ComponentChooser ch, int count)
 296             throws InterruptedException {
 297         waitWindowCount(null, ch, count);
 298     }
 299 
 300     /**
 301      * Wait till the count of windows which meet the the search criteria becomes
 302      * equal to count.
 303      *
 304      * @param owner The owner window of all the windows to be checked
 305      * @param ch a component chooser used to define and apply the search
 306      * criteria.
 307      * @param count the number of expected windows meeting the search criteria.
 308      * @throws InterruptedException
 309      */
 310     public static void waitWindowCount(Window owner, ComponentChooser ch, int count)
 311             throws InterruptedException {
 312         Waiter<String, Void> stateWaiter = new Waiter<>(new Waitable<String, Void>() {
 313             @Override
 314             public String actionProduced(Void obj) {
 315                 return countWindows(owner, ch) == count ? "" : null;
 316             }
 317 
 318             @Override
 319             public String getDescription() {
 320                 return "Wait till the count of windows matching the criteria "
 321                         + "specified by ComponentChooser reaches :" + count;
 322             }
 323 
 324             @Override
 325             public String toString() {
 326                 return "Operator.waitState.Waitable{description = "
 327                         + getDescription() + '}';
 328             }
 329         });
 330         stateWaiter.waitAction(null);
 331     }
 332 
 333     /**
 334      * Counts all the windows owned by the owner window which match the
 335      * criterion specified by component chooser.
 336      *
 337      * @param owner The owner window of all the windows to be checked
 338      * @param ch A component chooser used to define and apply the search
 339      * criteria
 340      * @return the number of matched windows
 341      */
 342     public static int countWindows(Window owner, ComponentChooser ch) {
 343         return new QueueTool().invokeAndWait(new QueueTool.QueueAction<Integer>(null) {
 344 
 345             @Override
 346             public Integer launch() {
 347                 Window[] windows;
 348                 if (owner == null) {
 349                     windows = Window.getWindows();
 350                 } else {
 351                     windows = owner.getOwnedWindows();
 352                 }
 353                 return (int) Stream.of(windows)
 354                         .filter(x -> ch.checkComponent(x)).count();
 355             }
 356         });
 357     }
 358 
 359     /**
 360      * Counts all the windows which match the criterion specified by component
 361      * chooser.
 362      *
 363      * @param ch A component chooser used to define and apply the search
 364      * criteria
 365      * @return the number of matched windows
 366      */
 367     public static int countWindows(ComponentChooser ch) {
 368         return countWindows(null, ch);
 369     }
 370 
 371     @Override
 372     public String getDescription() {
 373         return chooser.getDescription();
 374     }
 375 
 376     @Override
 377     public String toString() {
 378         return "WindowWaiter{" + "chooser=" + chooser + ", owner=" + owner + ", index=" + index + '}';
 379     }
 380 
 381     /**
 382      * Method can be used by a subclass to define chooser.
 383      *
 384      * @param ch a chooser specifying searching criteria.
 385      * @see #getComponentChooser
 386      */
 387     protected void setComponentChooser(ComponentChooser ch) {
 388         chooser = ch;
 389     }
 390 
 391     /**
 392      * Method can be used by a subclass to define chooser.
 393      *
 394      * @return a chooser specifying searching criteria.
 395      * @see #setComponentChooser
 396      */
 397     protected ComponentChooser getComponentChooser() {
 398         return chooser;
 399     }
 400 
 401     /**
 402      * Method can be used by a subclass to define window owner.
 403      *
 404      * @param owner Window-owner of the set of windows.
 405      * @see #getOwner
 406      */
 407     protected void setOwner(Window owner) {
 408         this.owner = owner;
 409     }
 410 
 411     /**
 412      * Method can be used by a subclass to define window owner.
 413      *
 414      * @return Window-owner of the set of windows.
 415      * @see #setOwner
 416      */
 417     protected Window getOwner() {
 418         return owner;
 419     }
 420 
 421     /**
 422      * @see org.netbeans.jemmy.Waiter#getWaitingStartedMessage()
 423      */
 424     @Override
 425     protected String getWaitingStartedMessage() {
 426         return "Start to wait window \"" + chooser.getDescription() + "\" opened";
 427     }
 428 
 429     /**
 430      * Overrides Waiter.getTimeoutExpiredMessage.
 431      *
 432      * @see org.netbeans.jemmy.Waiter#getTimeoutExpiredMessage(long)
 433      * @param timeSpent time from waiting start (milliseconds)
 434      * @return a message.
 435      */
 436     @Override
 437     protected String getTimeoutExpiredMessage(long timeSpent) {
 438         return ("Window \"" + chooser.getDescription() + "\" has not been opened in "
 439                 + timeSpent + " milliseconds");
 440     }
 441 
 442     /**
 443      * Overrides Waiter.getActionProducedMessage.
 444      *
 445      * @see org.netbeans.jemmy.Waiter#getActionProducedMessage(long, Object)
 446      * @param timeSpent time from waiting start (milliseconds)
 447      * @param result result of Waitable.actionproduced method.
 448      * @return a message.
 449      */
 450     @Override
 451     protected String getActionProducedMessage(long timeSpent, final Object result) {
 452         String resultToString;
 453         if (result instanceof Component) {
 454             // run toString in dispatch thread
 455             resultToString = new QueueTool().invokeSmoothly(
 456                     new QueueTool.QueueAction<String>("result.toString()") {
 457                 @Override
 458                 public String launch() {
 459                     return result.toString();
 460                 }
 461             }
 462             );
 463         } else {
 464             resultToString = result.toString();
 465         }
 466         return ("Window \"" + chooser.getDescription() + "\" has been opened in "
 467                 + timeSpent + " milliseconds"
 468                 + "\n    " + resultToString);
 469     }
 470 
 471     /**
 472      * @return a message.
 473      * @see org.netbeans.jemmy.Waiter#getGoldenWaitingStartedMessage()
 474      */
 475     @Override
 476     protected String getGoldenWaitingStartedMessage() {
 477         return "Start to wait window \"" + chooser.getDescription() + "\" opened";
 478     }
 479 
 480     /**
 481      * @return a message.
 482      * @see org.netbeans.jemmy.Waiter#getGoldenTimeoutExpiredMessage()
 483      */
 484     @Override
 485     protected String getGoldenTimeoutExpiredMessage() {
 486         return "Window \"" + chooser.getDescription() + "\" has not been opened";
 487     }
 488 
 489     /**
 490      * @return a message.
 491      * @see org.netbeans.jemmy.Waiter#getGoldenActionProducedMessage()
 492      */
 493     @Override
 494     protected String getGoldenActionProducedMessage() {
 495         return "Window \"" + chooser.getDescription() + "\" has been opened";
 496     }
 497 
 498     private static Window getAWindow(Window owner, ComponentChooser cc) {
 499         if (owner == null) {
 500             return WindowWaiter.getAWindow(cc);
 501         } else {
 502             Window result = null;
 503             Window[] windows = owner.getOwnedWindows();
 504             for (Window window : windows) {
 505                 if (cc.checkComponent(window)) {
 506                     return window;
 507                 }
 508                 if ((result = WindowWaiter.getWindow(window, cc)) != null) {
 509                     return result;
 510                 }
 511             }
 512             return null;
 513         }
 514     }
 515 
 516     private static Window getAWindow(ComponentChooser cc) {
 517         Window result = null;
 518         Frame[] frames = Frame.getFrames();
 519         for (Frame frame : frames) {
 520             if (cc.checkComponent(frame)) {
 521                 return frame;
 522             }
 523             if ((result = WindowWaiter.getWindow(frame, cc)) != null) {
 524                 return result;
 525             }
 526         }
 527         return null;
 528     }
 529 
 530     private Window waitWindow()
 531             throws InterruptedException {
 532         return waitAction(null);
 533     }
 534 
 535     private static class IndexChooser implements ComponentChooser {
 536 
 537         private int curIndex = 0;
 538         private int index;
 539         private ComponentChooser chooser;
 540 
 541         public IndexChooser(ComponentChooser ch, int i) {
 542             index = i;
 543             chooser = ch;
 544             curIndex = 0;
 545         }
 546 
 547         @Override
 548         public boolean checkComponent(Component comp) {
 549             if ((FIND_INVISIBLE_WINDOWS || (comp.isShowing() && comp.isVisible()))
 550                     && chooser.checkComponent(comp)) {
 551                 if (curIndex == index) {
 552                     return true;
 553                 }
 554                 curIndex++;
 555             }
 556             return false;
 557         }
 558 
 559         @Override
 560         public String getDescription() {
 561             return chooser.getDescription();
 562         }
 563 
 564         @Override
 565         public String toString() {
 566             return "IndexChooser{" + "curIndex=" + curIndex + ", index=" + index + ", chooser=" + chooser + '}';
 567         }
 568     }
 569 }