1 /*
   2  * Copyright (c) 2011, 2014, 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 
  24 
  25 import java.awt.AWTException;
  26 import java.awt.Color;
  27 import java.awt.Component;
  28 import java.awt.Dimension;
  29 import java.awt.Frame;
  30 import java.awt.Point;
  31 import java.awt.Robot;
  32 import java.awt.Window;
  33 import java.awt.event.WindowAdapter;
  34 import java.awt.event.WindowEvent;
  35 
  36 /*
  37  * @summary Utility routines that wait for a window to be displayed or for colors to be visible
  38  * @summary com.apple.junit.utils
  39  */
  40 public class VisibilityValidator {
  41     static final int SETUP_PERIOD = 5000; // Wait up to five seconds for our window events
  42     static final boolean DEBUG = false;
  43 
  44     volatile Window win = null;
  45     boolean activated = false;
  46     boolean opened = false;
  47     boolean focused = false;
  48     volatile boolean valid = false;
  49 
  50     //
  51     // Utility functions that encapsulates normal usage patterns
  52     //
  53     public static void setVisibleAndConfirm(Frame testframe) throws Exception {
  54         setVisibleAndConfirm(testframe, "Could not confirm test frame was visible");
  55     }
  56 
  57     public static void setVisibleAndConfirm(Frame testframe, String msg) throws Exception {
  58         if (testframe.isVisible()) {
  59             throw new RuntimeException("Frame is already visible");
  60         }
  61 
  62         VisibilityValidator checkpoint = new VisibilityValidator(testframe);
  63         testframe.setVisible(true);
  64         checkpoint.requireVisible();
  65         if (!checkpoint.isValid()) {
  66             //System.err.println(msg);
  67             throw new Exception("Frame not visible after " + SETUP_PERIOD + " milliseconds");
  68         }
  69     }
  70 
  71     //
  72     // Add listeners to the window
  73     //
  74     public VisibilityValidator(Window win) {
  75         this.win = win;
  76         WindowAdapter watcher = new WindowAdapter() {
  77             public void windowOpened(WindowEvent e) {
  78                 doOpen();
  79             }
  80 
  81             public void windowActivated(WindowEvent e) {
  82                 doActivate();
  83             }
  84 
  85             public void windowGainedFocus(WindowEvent e) {
  86                 doGainedFocus();
  87             }
  88         };
  89 
  90         win.addWindowListener(watcher);
  91         win.addWindowFocusListener(watcher);
  92     }
  93 
  94     // Make the window visible
  95     //
  96     // The only way to make it through this routine is for the window to generate BOTH
  97     // a windowOpened, a windowActivated event and a windowGainedFocus, or to timeout.
  98     //
  99     synchronized public void requireVisible() {
 100         int tries = 0;
 101 
 102         // wait for windowOpened and windowActivated events
 103         try {
 104             while ((opened == false) || (activated == false) || (focused == false)) {
 105                 if (tries < 4) {
 106                     tries += 1;
 107                     wait(SETUP_PERIOD);
 108                 } else {
 109                     break;
 110                 }
 111             }
 112 
 113             if (opened && activated) {
 114                 valid = true;
 115             } else {
 116                 valid = false;
 117             }
 118         } catch (InterruptedException ix) {
 119             valid = false;
 120         }
 121 
 122         // Extra-super paranoid checks
 123         if (win.isVisible() == false) {
 124             valid = false;
 125         }
 126 
 127         if (win.isShowing() == false) {
 128             valid = false;
 129         }
 130 
 131         if (win.isFocused() == false) {
 132             valid = false;
 133         }
 134 
 135         if (DEBUG) {
 136             if (!isValid()) {
 137                 System.out.println("\tactivated:" + new Boolean(activated));
 138                 System.out.println("\topened:" + new Boolean(opened));
 139                 System.out.println("\tfocused:" + new Boolean(focused));
 140                 System.out.println("\tvalid:" + new Boolean(valid));
 141                 System.out.println("\tisVisible():" + new Boolean(win.isVisible()));
 142                 System.out.println("\tisShowing():" + new Boolean(win.isShowing()));
 143                 System.out.println("\tisFocused():" + new Boolean(win.isFocused()));
 144             }
 145         }
 146 
 147     }
 148 
 149     synchronized void doOpen() {
 150         opened = true;
 151         notify();
 152     }
 153 
 154     synchronized void doActivate() {
 155         activated = true;
 156         notify();
 157     }
 158 
 159     synchronized void doGainedFocus() {
 160         focused = true;
 161         notify();
 162     }
 163 
 164     public boolean isValid() {
 165         return valid;
 166     }
 167 
 168     public boolean isClear() {
 169         return valid;
 170     }
 171 
 172     volatile static Robot robot = null;
 173 
 174     // utility function that waits until a Component is shown with the appropriate color
 175     public static boolean waitForColor(Component c, Color expected) throws AWTException, InterruptedException {
 176         Dimension dim = c.getSize();
 177         int xOff = dim.width / 2;
 178         int yOff = dim.height / 2;
 179         return waitForColor(c, xOff, yOff, expected);
 180     }
 181 
 182     // utility function that waits for 5 seconds for Component to be shown with the appropriate color
 183     public static boolean waitForColor(Component c, int xOff, int yOff, Color expected) throws AWTException, InterruptedException {
 184         return waitForColor(c, xOff, yOff, expected, 5000L);
 185     }
 186 
 187     // utility function that waits until a Component is up with the appropriate color
 188     public static boolean waitForColor(Component c, int xOff, int yOff, Color expected, long timeout) throws AWTException, InterruptedException {
 189         Point p = c.getLocationOnScreen();
 190         int x = (int)p.getX() + xOff;
 191         int y = (int)p.getY() + yOff;
 192         return waitForColor(x, y, expected, timeout);
 193     }
 194 
 195     // utility function that waits until specific screen coords have the appropriate color
 196     public static boolean waitForColor(int locX, int locY, Color expected, long timeout) throws AWTException, InterruptedException {        
 197         if (robot == null) {
 198             robot = new Robot();
 199         }
 200 
 201         long endtime = System.currentTimeMillis() + timeout;
 202         while (endtime > System.currentTimeMillis()) {
 203             if (colorMatch(robot.getPixelColor(locX, locY), expected)) {
 204                 return true;
 205             }
 206             Thread.sleep(50);
 207         }
 208 
 209         return false;
 210     }
 211 
 212     // utility function that asserts that two colors are similar to each other
 213     public static void assertColorEquals(final String message,
 214                                          final Color actual,
 215                                          final Color expected) {
 216         //System.out.println("actual color: " + actual);
 217         //System.out.println("expect color: " + expected);        
 218         if (!colorMatch(actual, expected)) {
 219             System.err.println(message);
 220         }
 221     }
 222 
 223     // determines if two colors are close in hue and brightness
 224     public static boolean colorMatch(final Color actual, final Color expected) {
 225         //System.out.println("colorMatch actual: " + actual + " expect: " + expected);
 226         final float[] actualHSB = getHSB(actual);
 227         final float[] expectedHSB = getHSB(expected);
 228 
 229         final float actualHue = actualHSB[0];
 230         final float expectedHue = expectedHSB[0];
 231         final boolean hueMatched = closeMatchHue(actualHue, expectedHue, 0.17f);
 232         //System.out.println("hueMatched? " + hueMatched);
 233         final float actualBrightness = actualHSB[2];
 234         final float expectedBrightness = expectedHSB[2];
 235         final boolean brightnessMatched = closeMatch(actualBrightness, expectedBrightness, 0.15f);
 236         //System.out.println("brightnessMatched? " + brightnessMatched);
 237         
 238         // check to see if the brightness was so low or so high that the hue got clamped to red
 239         if (brightnessMatched && !hueMatched) {
 240             return (expectedBrightness < 0.15f);
 241         }
 242         
 243         //System.out.println("colorMatched? " + (brightnessMatched && hueMatched));
 244         return brightnessMatched && hueMatched;
 245     }
 246 
 247     static float[] getHSB(final Color color) {
 248         final float[] hsb = new float[3];
 249         Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsb);
 250         return hsb;
 251     }
 252     
 253     // matches hues from 0.0 to 1.0, accounting for wrap-around at the 1.0/0.0 boundry
 254     static boolean closeMatchHue(final float actual, final float expected, final float tolerance) {
 255         if (closeMatch(actual, expected, tolerance)) return true;
 256         
 257         // all that remains is the overflow and underflow cases
 258         final float expectedHigh = expected + tolerance;
 259         final float expectedLow = expected - tolerance;
 260         
 261         if (expectedHigh > 1.0f) {
 262             // expected is too high, and actual was too low
 263             //System.out.println("\thue expected too high, actual too low");
 264             return closeMatch(actual + 0.5f, expected - 0.5f, tolerance);
 265         }
 266         
 267         if (expectedLow < 0.0f) {
 268             // expected is too low, and actual was too high
 269             //System.out.println("\thue expected too low, actual too high");
 270             return closeMatch(actual - 0.5f, expected + 0.5f, tolerance);
 271         }
 272         
 273         //System.out.println("\tcloseMatchHue? " + false);
 274         return false;
 275     }
 276     
 277     static boolean closeMatch(final float actual, final float expected, final float tolerance) {
 278         //System.out.println("\tcloseMatch actual: " + actual + " expect: " + expected + " tolerance: " + tolerance);
 279         //System.out.println("\tcloseMatch " + ((expected + tolerance) > actual) + " && " + ((expected - tolerance) < actual));
 280         return (expected + tolerance) > actual && (expected - tolerance) < actual;
 281     }
 282 }