/* * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The contents of this file are subject to the terms of either the Universal Permissive License * v 1.0 as shown at http://oss.oracle.com/licenses/upl * * or the following license: * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other materials provided with * the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used to * endorse or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.openjdk.jmc.test.jemmy.misc.base.wrappers; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.im.InputContext; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.swt.SWTException; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Widget; import org.jemmy.Point; import org.jemmy.action.AbstractExecutor; import org.jemmy.control.Wrap; import org.jemmy.env.Environment; import org.jemmy.env.TestOut; import org.jemmy.image.awt.AWTImage; import org.jemmy.image.awt.AWTRobotCapturer; import org.jemmy.image.awt.AverageDistanceImageComparator; import org.jemmy.image.awt.FilesystemImageLoader; import org.jemmy.image.awt.StrictImageComparator; import org.jemmy.image.Image; import org.jemmy.image.ImageComparator; import org.jemmy.input.awt.AWTRobotInputFactory; import org.jemmy.input.DefaultCharBindingMap; import org.jemmy.interfaces.Focusable; import org.jemmy.interfaces.Keyboard.KeyboardButton; import org.jemmy.interfaces.Keyboard.KeyboardButtons; import org.jemmy.interfaces.Keyboard.KeyboardModifiers; import org.jemmy.interfaces.Mouse.MouseButtons; import org.jemmy.interfaces.Parent; import org.jemmy.lookup.AbstractLookup; import org.jemmy.lookup.Lookup; import org.jemmy.operators.Screen; import org.jemmy.resources.StringComparePolicy; import org.jemmy.swt.SWTMenu; import org.jemmy.swt.Shells; import org.jemmy.swt.lookup.ByItemLookup; import org.jemmy.swt.lookup.ByName; import org.jemmy.swt.lookup.ByTextControlLookup; import org.jemmy.swt.lookup.ByTextShell; import org.junit.Assert; import org.openjdk.jmc.test.jemmy.misc.fetchers.Fetcher; import org.openjdk.jmc.test.jemmy.misc.fetchers.FetcherWithInput; import org.openjdk.jmc.test.jemmy.misc.wrappers.MCProgressIndicator; import org.openjdk.jmc.test.jemmy.misc.wrappers.MCTabFolder; import org.openjdk.jmc.test.jemmy.misc.wrappers.MCTable; /** * The base class for the Mission Control Jemmy wrappers */ public class MCJemmyBase { private static final boolean VERBOSE_JEMMY_LOGGING = "true" .equalsIgnoreCase(System.getProperty("mc.test.jemmy.verbose.logging")); private static final long EDITOR_LOAD_WAIT_DEFAULT_TIMEOUT_MS = 30000; private static final long EDITOR_LOAD_WAIT_TIMEOUT_MS = Long.getLong("jmc.test.editor.load.wait.timeout", EDITOR_LOAD_WAIT_DEFAULT_TIMEOUT_MS); public static final long VISIBLE_LOOKUP_DEFAULT_TIMEOUT_MS = 10000; private static final long VISIBLE_LOOKUP_TIMEOUT_MS = Long.getLong("jmc.test.visible.lookup.timeout", VISIBLE_LOOKUP_DEFAULT_TIMEOUT_MS); private static final int BETWEEN_KEYSTROKES_SLEEP = 100; public static final int LOOKUP_SLEEP_TIME_MS = 100; protected static Wrap shell; protected Wrap control; public static final KeyboardButtons SELECTION_BUTTON; public static final KeyboardButtons EXPAND_BUTTON; public static final KeyboardButtons COLLAPSE_BUTTON; public static final KeyboardButtons CLOSE_BUTTON; public static final KeyboardModifiers SHORTCUT_MODIFIER; public static final String OS_NAME; private static final int IDLE_LOOP_COUNT = 3; private static final int IDLE_LOOP_TIME_STEP = 100; private static final int IDLE_LOOP_TIMEOUT_MS = 10000; private static Integer unnamedImageCounter = 0; private static final Map, Class> primitiveMap = new HashMap<>(); protected static Wrap focusedSection; static { Environment.getEnvironment().setOutput(AbstractLookup.OUTPUT, (VERBOSE_JEMMY_LOGGING) ? new TestOut() : TestOut.getNullOutput()); Environment.getEnvironment().setOutput(AbstractExecutor.NON_QUEUE_ACTION_OUTPUT, (VERBOSE_JEMMY_LOGGING) ? new TestOut() : TestOut.getNullOutput()); Environment.getEnvironment().setOutput(AbstractExecutor.QUEUE_ACTION_OUTPUT, (VERBOSE_JEMMY_LOGGING) ? new TestOut() : TestOut.getNullOutput()); Environment.getEnvironment().setOutput(Wrap.OUTPUT, (VERBOSE_JEMMY_LOGGING) ? new TestOut() : TestOut.getNullOutput()); Environment.getEnvironment().setInputFactory(new AWTRobotInputFactory()); Environment.getEnvironment().setImageCapturer(new AWTRobotCapturer()); Environment.getEnvironment().setImageLoader(new FilesystemImageLoader()); /** * Overriding because on Linux it is wrongly assumed that SPACE does a proper selection * (which is not the case with check or radio style where the menu isn't closed) */ Environment.getEnvironment().setProperty(KeyboardButton.class, SWTMenu.SELECTION_BUTTON_PROP, KeyboardButtons.ENTER); if ("sv".equals(InputContext.getInstance().getLocale().getLanguage())) { Environment.getEnvironment().setProperty("LANG", "sv"); } AWTImage.setImageRoot(getResultDir()); AWTImage.setComparator(new AverageDistanceImageComparator()); Screen.SCREEN.getEnvironment().setInputFactory(new AWTRobotInputFactory()); OS_NAME = System.getProperty("os.name").toLowerCase(); SELECTION_BUTTON = OS_NAME.contains("linux") ? KeyboardButtons.SPACE : KeyboardButtons.ENTER; EXPAND_BUTTON = OS_NAME.contains("os x") ? KeyboardButtons.RIGHT : KeyboardButtons.ADD; COLLAPSE_BUTTON = OS_NAME.contains("os x") ? KeyboardButtons.LEFT : KeyboardButtons.SUBTRACT; CLOSE_BUTTON = KeyboardButtons.W; SHORTCUT_MODIFIER = OS_NAME.contains("os x") ? KeyboardModifiers.META_DOWN_MASK : KeyboardModifiers.CTRL_DOWN_MASK; Environment.getEnvironment().setProperty(Boolean.class, SWTMenu.SKIPS_DISABLED_PROP, (OS_NAME.contains("windows")) ? false : true); // keyboard re-mapping for Mac OS X with Swedish keyboard if ("sv".equalsIgnoreCase(InputContext.getInstance().getLocale().getLanguage()) && OS_NAME.contains("os x")) { // first making sure that the DefaultCharBindingMap has been loaded and initialized getShell().keyboard(); DefaultCharBindingMap map = (DefaultCharBindingMap) Environment.getEnvironment().getBindingMap(); map.removeChar('+'); map.addChar('+', KeyboardButtons.MINUS); map.removeChar('-'); map.addChar('-', KeyboardButtons.SLASH); map.removeChar('_'); map.addChar('_', KeyboardButtons.SLASH, KeyboardModifiers.SHIFT_DOWN_MASK); map.removeChar('/'); map.addChar('/', KeyboardButtons.D7, KeyboardModifiers.SHIFT_DOWN_MASK); map.removeChar('\\'); map.addChar('\\', KeyboardButtons.D7, KeyboardModifiers.SHIFT_DOWN_MASK, KeyboardModifiers.ALT_DOWN_MASK); map.removeChar(':'); map.addChar(':', KeyboardButtons.PERIOD, KeyboardModifiers.SHIFT_DOWN_MASK); map.removeChar(';'); map.addChar(';', KeyboardButtons.COMMA, KeyboardModifiers.SHIFT_DOWN_MASK); map.removeChar('~'); map.addChar('~', KeyboardButtons.CLOSE_BRACKET, KeyboardModifiers.ALT_DOWN_MASK); map.removeChar('='); map.addChar('=', KeyboardButtons.D0, KeyboardModifiers.SHIFT_DOWN_MASK); } primitiveMap.put(boolean.class, Boolean.class); primitiveMap.put(byte.class, Byte.class); primitiveMap.put(char.class, Character.class); primitiveMap.put(short.class, Short.class); primitiveMap.put(int.class, Integer.class); primitiveMap.put(long.class, Long.class); primitiveMap.put(float.class, Float.class); primitiveMap.put(double.class, Double.class); } protected static File getResultDir() { if (System.getProperty("results.dir") != null) { return new File(System.getProperty("results.dir")); } else { return new File(System.getProperty("user.dir")); } } /** * Gets the main shell of Mission Control * * @return the main shell of Mission Control */ protected static Wrap getShell() { return Shells.SHELLS.lookup(Shell.class, new ByTextShell<>("JDK Mission Control")).wrap(); } /** * Gets a shell by text * * @param text * the text string to lookup the shell with * @return the associated shell */ protected static Wrap getShellByText(String text) { return Shells.SHELLS.lookup(Shell.class, new ByTextShell<>(text)).wrap(); } /** * Tries to set focus on Mission Control */ public static void focusMc() { getShell().as(Focusable.class).focuser().focus(); } /** * Checks if supplied control is identical to our control * * @param otherControl * the control shall be compared with this control * @return {@code true} if the controls are equal */ public Boolean controlsAreEqual(Control otherControl) { return (otherControl.equals(this.control.getControl())); } /** * Determines if this control has a specific control as an ancestor or not * * @param possibleAncestor * the control that should be checked for among the ancestors * @return {@code true} if the control provided as parameter is an ancestor of this control */ public Boolean hasAsAncestor(MCJemmyBase possibleAncestor) { final Control control = this.control.getControl(); Fetcher fetcher = new Fetcher() { @Override public void run() { Boolean parentFound = false; try { Composite ancestor = control.getParent(); while (!(ancestor instanceof Shell)) { if (possibleAncestor.controlsAreEqual(ancestor)) { parentFound = true; break; } ancestor = ancestor.getParent(); } } catch (SWTException e) { suppressWidgetDisposedException(e); } setOutput(parentFound); } }; Display.getDefault().syncExec(fetcher); return fetcher.getOutput(); } /** * Does a lookup waiting for the editor with the supplied title to show up before returning * * @param title * the title of the editor * @return {@code true} if the editor was found, otherwise {@code false} */ public static boolean waitForEditor(String title) { return waitForEditor(EDITOR_LOAD_WAIT_TIMEOUT_MS, title); } /** * Search for an editor with a time limit on retries * * @param maxWait * the max number of milliseconds to repeat the lookup of the editor (unless found). * @param title * the title of the editor to find (exact match) * @return {@code true} if the editor was found, otherwise {@code false} */ public static boolean waitForEditor(long maxWait, String title) { return waitForEditor(title, StringComparePolicy.EXACT, maxWait); } /** * Search for an editor with a retry count * * @param title * the title of the editor to find (exact match) * @param maxRetries * The max number of times to retry the lookup of the editor. Each retry might take a * considerable amount of time so use this wisely. * @return {@code true} if the editor was found, otherwise {@code false} */ public static boolean waitForEditor(String title, int maxRetries) { return waitForEditor(title, StringComparePolicy.EXACT, maxRetries); } /** * Search for an editor with a retry count * * @param title * the title of the editor to find * @param policy * a {@link StringComparePolicy} used for matching the title text * @param maxRetries * The max number of times to retry the lookup of the editor. Each retry might take a * considerable amount of time so use this wisely. * @return {@code true} if the editor was found, otherwise {@code false} */ public static boolean waitForEditor(String title, StringComparePolicy policy, int maxRetries) { boolean found = false; while (!found && maxRetries > 0) { found = isEditorLoadComplete(getEditorLookup(title, policy)); if (!found) { sleep(LOOKUP_SLEEP_TIME_MS); } maxRetries--; } return found; } /** * Search for an editor with a time limit on retries * * @param title * the title of the editor to find * @param policy * a {@link StringComparePolicy} used for matching the title with the title text * @param maxWait * the max number of milliseconds to repeat the lookup of the editor (unless found) * @return {@code true} if the editor was found, otherwise {@code false} */ public static boolean waitForEditor(String title, StringComparePolicy policy, long maxWait) { boolean found = false; long maxTimeStamp = System.currentTimeMillis() + maxWait; while (!found && System.currentTimeMillis() < maxTimeStamp) { found = isEditorLoadComplete(getEditorLookup(title, policy)); if (!found) { sleep(LOOKUP_SLEEP_TIME_MS); } } return found; } @SuppressWarnings("unchecked") private static Lookup getEditorLookup(String title, StringComparePolicy policy) { return getShell().as(Parent.class, CTabFolder.class).lookup(CTabFolder.class, new ByItemLookup(title, policy)); } private static boolean isEditorLoadComplete(Lookup editorLookup) { boolean hasProgressIndicator = false; int numOfEditors = editorLookup.size(); if (numOfEditors == 1) { MCTabFolder tf = new MCTabFolder(editorLookup.wrap(), getShell()); List jpis = MCProgressIndicator.getVisible(getShell()); for (MCProgressIndicator jpi : jpis) { try { if (jpi.hasAsAncestor(tf)) { hasProgressIndicator = true; break; } } catch (SWTException e) { suppressWidgetDisposedException(e); } } } return numOfEditors == 1 && !hasProgressIndicator; } /** * Search for an editor with a retry count * * @param title * the title of the editor to find (substring match) * @param maxRetries * The max number of times to retry the lookup of the editor. Each retry might take a * considerable amount of time so use this wisely. * @return {@code true} if found, otherwise {@code false} */ public static boolean waitForSubstringMatchedEditor(String title, int maxRetries) { return waitForEditor(title, StringComparePolicy.SUBSTRING, maxRetries); } /** * Search for an editor with a time limit on retries * * @param maxWait * the max number of milliseconds to repeat the lookup of the editor. * @param title * the title of the editor to find (substring match) * @return {@code true} if found, otherwise {@code false} */ public static boolean waitForSubstringMatchedEditor(long maxWait, String title) { return waitForEditor(title, StringComparePolicy.SUBSTRING, maxWait); } /** * Search for an editor with a time limit (defined by {@code EDITOR_LOAD_WAIT_TIMEOUT_MS}) on * retries * * @param title * the title of the editor to find (substring match) * @return {@code true} if found, otherwise {@code false} */ public static boolean waitForSubstringMatchedEditor(String title) { return waitForSubstringMatchedEditor(EDITOR_LOAD_WAIT_TIMEOUT_MS, title); } /** * Convenience method to put test execution on hold by putting the thread to sleep * * @param millis * the time to sleep in milliseconds. * @return The actual time slept in milliseconds. Should be equal to millis unless an * {@link InterruptedException} was thrown. */ public static long sleep(long millis) { long time = System.currentTimeMillis(); long slept = millis; try { Thread.sleep(millis); } catch (InterruptedException e) { slept = System.currentTimeMillis() - time; } return slept; } /** * This method finds a widget that may or may not exist by means of doing a lookup and then * walking through the results to see if there is a match. The difference to lookups with * specific criterion is that this doesn't timeout and throw an exception if not found. Instead * null is returned. Note: This method expects the property to hold a String or a list of * Strings * * @param clazz * the class of the widget to look for * @param text * the string value used for matching * @param property * the property to match with the supplied text parameter (has to return a String or * List of String to be properly matched) * @param policy * a {@link StringComparePolicy} to use when matching the widget * @return the control searched for or {@code null} if not found */ @SuppressWarnings("unchecked") protected static Wrap findWrap( Class clazz, String text, String property, StringComparePolicy policy) { Lookup lookup = getShell().as(Parent.class, Control.class).lookup(clazz); Wrap result = null; for (int i = 0; i < lookup.size() && result == null; i++) { Wrap wrap = lookup.wrap(i); Object object = wrap.getProperty(property); if (object instanceof List) { List values = new ArrayList().getClass().cast(object); for (String value : values) { if (policy.compare(value, text)) { result = wrap; break; } } } else { if (policy.compare(String.class.cast(object), text)) { result = wrap; } } } return result; } /** * Convenience method to find out if a widget is disposed * * @param widgetWrap * the wrapped widget to check * @return {@code true} if disposed (or disposing), otherwise {@code false} */ protected static boolean isDisposed(final Wrap widgetWrap) { return isDisposed(widgetWrap.getControl()); } /** * Convenience method to find out if a widget is disposed * * @param widget * the widget to check * @return {@code true} if disposed (or disposing), otherwise {@code false} */ protected static boolean isDisposed(final Widget widget) { Fetcher fetcher = new Fetcher() { @Override public void run() { if (widget != null) { setOutput(widget.isDisposed()); } else { setOutput(true); } } }; Display.getDefault().syncExec(fetcher); return fetcher.getOutput(); } /** * Convenience method to find out if the widget of this wrapper is disposed * * @return {@code true} if disposed (or disposing), otherwise {@code false} */ public boolean isDisposed() { return isDisposed(control); } /** * Convenience method to find out if this control is enabled * * @return {@code true} if enabled, otherwise {@code false} */ public boolean isEnabled() { Fetcher fetcher = new Fetcher() { @Override public void run() { setOutput(control.getControl().isEnabled()); } }; Display.getDefault().syncExec(fetcher); return fetcher.getOutput(); } /** * Opens the context menu by right-clicking at a provided point * * @param p * origin point of context (right-click) */ private void openContextMenuAtPoint(Point p) { Display.getDefault().syncExec(() -> { control.mouse().click(1, p, MouseButtons.BUTTON3); }); } /** * Checks the isEnabled value for a menu item * * @param p * the point on the screen at which to open the context menu * @param menuItemText * the menu item of interest * @return the isEnabled value for the menu item of interest */ public boolean isContextMenuItemEnabled(Point p, String menuItemText) { openContextMenuAtPoint(p); Fetcher fetcher = new Fetcher() { @Override public void run() { Menu menu = control.getControl().getMenu(); for (MenuItem item : menu.getItems()) { if(menuItemText.equals(item.getText())) { setOutput(item.isEnabled()); break; } } } }; Display.getDefault().syncExec(fetcher); return (fetcher.getOutput() == null) ? false : fetcher.getOutput(); } /** * Convenience method to find out if a control is visible * * @param controlWrap * the control to check * @return {@code true} if visible, otherwise {@code false} */ protected static boolean isVisible(final Wrap controlWrap) { Fetcher fetcher = new Fetcher() { @Override public void run() { try { setOutput(controlWrap.getControl().isVisible()); } catch (SWTException e) { suppressWidgetDisposedException(e); } } }; Display.getDefault().syncExec(fetcher); return (fetcher.getOutput() == null) ? false : fetcher.getOutput(); } /** * Convenience method to find out if this control is visible * * @return {@code true} if visible, otherwise {@code false} */ public boolean isVisible() { return isVisible(control); } /** * Iterates through the wraps found by a lookup and returns a list of all visible wraps. * * @param lookup * the {@link Lookup} to search through * @return a list of all visible wraps found by the lookup */ protected static List> getVisible(Lookup lookup) { return getVisible(lookup, true); } /** * Iterates through the wraps found by a lookup and returns a list of all visible wraps. * * @param lookup * the {@link Lookup} to search through * @param waitForIdle * {@code true} if "UI-update queue" should be empty before looking for controls * @return a list of all visible wraps found by the {@link Lookup} */ protected static List> getVisible(Lookup lookup, boolean waitForIdle) { return getVisible(lookup, waitForIdle, VISIBLE_LOOKUP_TIMEOUT_MS); } /** * Iterates through the wraps found by a lookup and returns a list of all visible wraps. * * @param lookup * the {@link Lookup} to search through * @param waitForIdle * {@code true} if "UI-update queue" should be empty before looking for controls * @param maxWaitMs * the timeout in milliseconds before ending the lookup * @return a list of all visible wraps found by the {@link Lookup} */ protected static List> getVisible( Lookup lookup, boolean waitForIdle, long maxWaitMs) { return getVisible(lookup, waitForIdle, maxWaitMs, true); } /** * Iterates through the wraps found by a lookup and returns a list of all visible wraps. Will * retry a maximum of {@code VISIBLE_LOOKUP_MAX_RETRY_COUNT} times with a * {@code VISIBLE_LOOKUP_SLEEP_TIME_MS} ms sleep in between if lookup has zero length * * @param lookup * the {@link Lookup} to search through * @param waitForIdle * {@code true} if "UI-update queue" should be empty before looking for controls * @param maxWaitMs * the timeout in milliseconds before ending the lookup * @param assertEmpty * if {@code true} will assert the the resulting list isn't empty assertion will be * done * @return a list of all visible wraps found by the {@link Lookup} */ @SuppressWarnings("unchecked") protected static List> getVisible( Lookup lookup, boolean waitForIdle, long maxWaitMs, boolean assertEmpty) { if (waitForIdle) { waitForIdle(); } List> list = new ArrayList<>(); long lookupEndTime = System.currentTimeMillis() + maxWaitMs; do { for (int i = 0; i < lookup.size(); i++) { Wrap wrap = (Wrap) lookup.wrap(i); if (isVisible(wrap)) { list.add(wrap); } } if (list.size() == 0) { sleep(LOOKUP_SLEEP_TIME_MS); } } while (list.size() == 0 && lookupEndTime > System.currentTimeMillis()); if (assertEmpty) { Assert.assertTrue("No visible controls found", list.size() > 0); } return list; } /** * Iterates through the wraps found by a lookup and returns a list of all visible wraps. Will * retry a maximum of {@code VISIBLE_LOOKUP_MAX_RETRY_COUNT} times with a * {@code VISIBLE_LOOKUP_SLEEP_TIME_MS} ms sleep in between if lookup has zero length * * @param lookup * the {@link Lookup} to search through * @param waitForIdle * {@code true} if "UI-update queue" should empty before looking for controls * @param assertEmpty * if {@code true} will assert the the resulting list isn't empty. Otherwise no * assertion will be done * @return a list of all visible wraps found by the {@link Lookup} */ protected static List> getVisible( Lookup lookup, boolean waitForIdle, boolean assertEmpty) { return getVisible(lookup, waitForIdle, VISIBLE_LOOKUP_TIMEOUT_MS, assertEmpty); } /** * Convenience method to find out if Mission Control UI is busy * * @return {@code true} if busy */ public static boolean isBusy() { Fetcher fetcher = new Fetcher() { @Override public void run() { setOutput(!Job.getJobManager().isIdle()); } }; Display.getDefault().syncExec(fetcher); return fetcher.getOutput(); } /** * Inspects a Widget (graphically) for updates by comparing two snapshots of it * * @param widget * the widget to inspect for change (graphically) * @param waitTimeMillis * the time to wait between the snapshots of the Widget * @return {@code true} if the Widget has changed */ public boolean isWidgetUpdating(Wrap widget, int waitTimeMillis) { sleep(waitTimeMillis); org.jemmy.image.Image firstImage = widget.getScreenImage(); sleep(waitTimeMillis); org.jemmy.image.Image secondImage = widget.getScreenImage(); // Set the Comparator to be strict, first get and save the current // Comparator for later restoral ImageComparator current = AWTImage.getComparator(); AWTImage.setComparator(new StrictImageComparator()); // diff will be null if the Images are identical org.jemmy.image.Image diff = secondImage.compareTo(firstImage); AWTImage.setComparator(current); return (diff == null) ? false : true; } /** * Inspects this control for updates by comparing two snapshots of it * * @param waitTimeMillis * the time to wait between the snapshots of the Widget * @return {@code true} if the Widget has changed */ public boolean isWidgetUpdating(int waitTimeMillis) { return isWidgetUpdating(control, waitTimeMillis); } /** * Waits for background jobs to finish before executing a script line. Since background jobs * post to the UI-thread asynchronously we must ensure they get a chance to run, so we we spin * asynchronously in a count down process. IDLE_LOOP_COUNT specifies the number iterations * without the job manager being activated as being enough to make sure we are in a calm/idle * state. */ public static void waitForIdle() { int counter = IDLE_LOOP_COUNT; long startTimestamp = System.currentTimeMillis(); while (counter > 0) { if ((System.currentTimeMillis() - startTimestamp) > IDLE_LOOP_TIMEOUT_MS) { break; } if (isBusy()) { counter = IDLE_LOOP_COUNT; } else { counter--; } sleep(IDLE_LOOP_TIME_STEP); } } /** * Saves a picture of Mission Control's shell. If Mission Control is behind other applications, * the rectangle will show what's really on top. * * @param imageName * the name of the saved file, ".png" will be added at the end */ public static void saveMcImage(String imageName) { Image pic = getMcImage(); pic.save(imageName + ".png"); } /** * Saves a picture of Mission Control's shell. If Mission Control is behind other applications, * the rectangle will show what's really on top. The image will be given a name which is unique * for the execution. */ public static void saveMcImage() { unnamedImageCounter++; saveMcImage("unnamed_mc_image_" + String.format("%03d", unnamedImageCounter) + ".png"); } /** * Returns an image of the Mission Control shell * * @return an {@link Image} */ public static Image getMcImage() { return getShell().getScreenImage(); } /** * Returns an image of this control * * @return an {@link Image} of this control */ public Image getThisImage() { return control.getScreenImage(); } /** * Saves a picture of this control. If the control is behind other applications or Mission * Control controls, the rectangle will show what's really on top. * * @param imageName * the name of the saved file. ".png" will be added at the end */ public void saveThisImage(String imageName) { Image pic = getThisImage(); saveImage(imageName + ".png", pic); } /** * Saves a picture of this control. If the control is behind other applications or Mission * Control controls, the rectangle will show what's really on top. The image will be given a * name which is unique for the execution. */ public void saveThisImage() { unnamedImageCounter++; saveThisImage("unnamed_mc_image_" + String.format("%03d", unnamedImageCounter) + ".png"); } /** * Saves the image with the specified name * * @param fileName * the name of the image file * @param image * the image */ public static void saveImage(String fileName, Image image) { image.save(fileName); } /** * Focuses on a specific section to use instead of the main shell. * * @param name * the name of the section to focus on */ @SuppressWarnings("unchecked") public static void focusSectionByName(String name) { focusedSection = (Wrap) getVisible( getShell().as(Parent.class, Control.class).lookup(new ByName(name))).get(0); } /** * Focuses on a specific section to use instead of the main shell. * * @param title * the title of the section to focus on */ @SuppressWarnings("unchecked") public static void focusSectionByTitle(String title) { focusedSection = (Wrap) getVisible( getShell().as(Parent.class, Control.class).lookup(new ByTextControlLookup<>(title))).get(0); } /** * Focuses on a specific section to use instead of the main shell. * * @param title * the title of the section to focus on * @param waitForIdle * if {@code true} will first wait for the UI to be idle before setting focus */ @SuppressWarnings("unchecked") public static void focusSectionByTitle(String title, boolean waitForIdle) { focusedSection = (Wrap) getVisible( getShell().as(Parent.class, Control.class).lookup(new ByTextControlLookup<>(title)), waitForIdle) .get(0); } /** * House keeping: clearing the focusedSection reference so that it won't be used in further * lookups */ public static void clearFocus() { focusedSection = null; } /** * @return a {@link List} of {@link MCTable} either in the currently focused section or * globally in the shell */ public static List getTables() { if (focusedSection != null) { return MCTable.getAll(focusedSection); } else { return MCTable.getAll(getShell()); } } /** * Get all tables in the focused section (if set), otherwise from the Mission Control main shell * * @param waitForIdle * {@code true} if "UI-update queue" should be empty before looking for controls * @return a {@link List} of {@link MCTable} either in the currently focused section or * globally in the shell */ public static List getTables(boolean waitForIdle) { if (focusedSection != null) { return MCTable.getAll(focusedSection, waitForIdle); } else { return MCTable.getAll(getShell(), waitForIdle); } } /** * Runs the method and returns the result if a matching method is found. If not, null will * always be returned. Note that the method could return null as well if the operation succeeds * so this needs to be handled in a proper way by the caller. * * @param returnType * the type of the returned object * @param object * the object on which to run the method * @param methodName * the name of the method to run * @param params * an object array of parameters for the method. null if no parameters * @return The result of running the method. null if no matching method is found (name, * parameters and return type). Note that the method could return null as well if the * operation succeeds so this needs to be handled in a proper way by the caller. */ public static T runMethod(Class returnType, Object object, String methodName, Object ... params) { T result = null; try { Class[] paramTypes = null; if (params != null) { paramTypes = new Class[params.length]; for (int i = 0; i < params.length; i++) { paramTypes[i] = params[i].getClass(); } } Method method = getCompatibleMethod(object, methodName, paramTypes); Class methodReturnType = method.getReturnType(); if (methodReturnType.isPrimitive()) { methodReturnType = primitiveMap.get(methodReturnType); } if (returnType.equals(Void.class) || returnType.isAssignableFrom(methodReturnType)) { result = returnType.cast(method.invoke(object, params)); } } catch (Exception e) { // do nothing, just return null } return result; } private static Method getCompatibleMethod(Object object, String methodName, Class ... paramTypes) throws SecurityException, NoSuchMethodException { if (paramTypes != null) { Method[] methods = object.getClass().getMethods(); for (Method method : methods) { Method m = method; if (!m.getName().equals(methodName)) { continue; } Class[] actualTypes = m.getParameterTypes(); if (actualTypes.length != paramTypes.length) { continue; } boolean found = true; for (int j = 0; j < actualTypes.length; j++) { if (!actualTypes[j].isAssignableFrom(paramTypes[j])) { if (actualTypes[j].isPrimitive()) { found = primitiveMap.get(actualTypes[j]).equals(paramTypes[j]); } else if (paramTypes[j].isPrimitive()) { found = primitiveMap.get(paramTypes[j]).equals(actualTypes[j]); } else { found = false; } } if (!found) { break; } } if (found) { return m; } } throw new NoSuchMethodException("Could not find method " + methodName + " with parameters " + paramTypes); } else { return object.getClass().getMethod(methodName); } } /** * Returns the name of this control * * @return the name of this control. Null if no name has been set */ public String getName() { return control.getProperty(String.class, Wrap.NAME_PROP_NAME); } /** * @return the text of this control */ public String getText() { return control.getProperty(String.class, Wrap.TEXT_PROP_NAME); } /** * Clicks this control (once) */ public void click() { click(1); } /** * Clicks this control {@code times} times * * @param times * the number of times to mouse click this control */ public void click(int times) { control.mouse().click(times); } /** * Default implementation of a copy to clipboard for any wrapper */ public void copyToClipboard() { control.as(Focusable.class).focuser().focus(); sleep(BETWEEN_KEYSTROKES_SLEEP); control.keyboard().pushKey(KeyboardButtons.A, SHORTCUT_MODIFIER); sleep(BETWEEN_KEYSTROKES_SLEEP); control.keyboard().pushKey(KeyboardButtons.C, SHORTCUT_MODIFIER); sleep(BETWEEN_KEYSTROKES_SLEEP); } /** * Gets the system clipboard contents as a string * * @return the clipboard contents */ public static String getStringFromClipboard() { return getFromClipBoard(String.class); } /** * Finds out if content assist is present. The assumption is that content assist is present if * there is a Shell with a single Composite that in turn has a single child of type Table * * @return {@code true} if content assist is present, otherwise {@code false} */ public static boolean isContentAssistPresent() { boolean found = true; long endTime = System.currentTimeMillis() + 1000; while (found && System.currentTimeMillis() < endTime) { FetcherWithInput>, Boolean> fetcher = new FetcherWithInput>, Boolean>( getVisible(Shells.SHELLS.lookup(Shell.class), false, false)) { @Override public void run() { boolean isPresent = false; for (Wrap shellWrap : getInput()) { Control[] shellChildren = shellWrap.getControl().getChildren(); if (shellChildren.length == 1 && shellChildren[0] instanceof Composite) { Control[] compositeChildren = Composite.class.cast(shellChildren[0]).getChildren(); if (compositeChildren.length == 1 && compositeChildren[0] instanceof Table) { isPresent = true; break; } } } setOutput(isPresent); } }; Display.getDefault().syncExec(fetcher); found = fetcher.getOutput(); if (found) { sleep(100); } } return found; } private static T getFromClipBoard(Class returnType) { Transferable transferable = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null); DataFlavor thisFlavor = new DataFlavor(returnType, returnType.getName()); try { if (transferable != null && transferable.isDataFlavorSupported(thisFlavor)) { return returnType.cast(transferable.getTransferData(thisFlavor)); } } catch (UnsupportedFlavorException e) { System.out.println("Clipboard content flavor is not supported " + e.getMessage()); } catch (IOException e) { System.out.println("Clipboard content could not be retrieved " + e.getMessage()); } return null; } private static void suppressWidgetDisposedException(SWTException e) { if (!e.getMessage().contains("Widget is disposed")) { // Unexpected exception. Re-throw it throw e; } } /** * Setting focus on this control programmatically (if not already focused). */ protected void ensureFocus() { Fetcher fetcher = new Fetcher() { @Override public void run() { if (!control.getControl().isFocusControl()) { control.getControl().setFocus(); } setOutput(control.getControl().isFocusControl()); } }; Display.getDefault().syncExec(fetcher); if (!fetcher.getOutput()) { // fallback if the programmatic focusing didn't work control.as(Focusable.class).focuser().focus(); } } }