diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 04dbca9..4df9239 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,15 +30,40 @@ jobs: java-version: '21' distribution: 'temurin' + - name: Cache Maven dependencies + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Configure Maven Central mirror + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml <<'EOF' + + + + central-mirror + Central Mirror + https://repo1.maven.org/maven2 + central + + + + EOF + - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} + build-mode: manual - - name: Build with Maven - run: mvn clean compile + - name: Build and test with Maven + run: mvn -B clean compile -Dmaven.wagon.http.retryHandler.count=5 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:${{matrix.language}}" \ No newline at end of file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 92823cb..065cd47 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -22,7 +22,7 @@ jobs: - name: Build project and run UI test uses: coactions/setup-xvfb@v1 with: - run: mvn clean verify -Pci + run: mvn -B clean verify -Pci - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 diff --git a/.github/workflows/maven_central_publish.yml b/.github/workflows/maven_central_publish.yml index dcbc3d0..64b6e28 100644 --- a/.github/workflows/maven_central_publish.yml +++ b/.github/workflows/maven_central_publish.yml @@ -39,6 +39,6 @@ jobs: }] - name: Release package - run: mvn clean deploy -Prelease,maven-central -DskipTests + run: mvn -B clean deploy -Prelease,maven-central -DskipTests env: GPG_SECRET_KEY_PASSPHRASE: ${{ secrets.GPG_SECRET_KEY_PASSPHRASE }} diff --git a/.github/workflows/maven_github_publish.yml b/.github/workflows/maven_github_publish.yml index dbd87ff..f16ee3c 100644 --- a/.github/workflows/maven_github_publish.yml +++ b/.github/workflows/maven_github_publish.yml @@ -38,7 +38,7 @@ jobs: }] - name: Publish package - run: mvn --batch-mode deploy -Prelease -DskipTests + run: mvn -B deploy -Prelease -DskipTests env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} GPG_SECRET_KEY_PASSPHRASE: ${{ secrets.GPG_SECRET_KEY_PASSPHRASE }} \ No newline at end of file diff --git a/README.md b/README.md index f1874c0..c577f41 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # WindowTester Pro -![GitHub release (v5.*)](https://img.shields.io/github/v/release/r4fterman/windowtester?filter=v5.*&display_name=tag) -[![Maven Central Version](https://img.shields.io/maven-central/v/io.github.r4fterman/com.windowtester.runtime?strategy=highestVersion&filter=5*)](https://mvnrepository.com/search?q=windowtester) -![Static Badge](https://img.shields.io/badge/Java-v17-blue) -![Static Badge](https://img.shields.io/badge/Maven-v3.5.4-blue) +![GitHub release (v6.*)](https://img.shields.io/github/v/release/r4fterman/windowtester?filter=v6.*&display_name=tag) +[![Maven Central Version](https://img.shields.io/maven-central/v/io.github.r4fterman/com.windowtester.runtime?strategy=highestVersion&filter=6*)](https://mvnrepository.com/search?q=windowtester) +![Static Badge](https://img.shields.io/badge/Java-v21-blue) +![Static Badge](https://img.shields.io/badge/Maven-v3.9.9-blue) [![License](https://img.shields.io/badge/License-EPL--1.0-green.svg)](LICENSE.md) [![Build WindowTester](https://github.com/r4fterman/windowtester/actions/workflows/maven.yml/badge.svg)](https://github.com/r4fterman/windowtester/actions/workflows/maven.yml) [![codecov](https://codecov.io/gh/r4fterman/windowtester/graph/badge.svg?token=fEDTM853bU)](https://codecov.io/gh/r4fterman/windowtester) @@ -34,7 +34,7 @@ can be run within your IDE, or they can be automated to run using [Maven](http:/ ## Requirements - JDK 21 -- JUnit 5 +- JUnit 6 Supported platforms: diff --git a/abbot/src/main/java/abbot/editor/EditorConstants.java b/abbot/src/main/java/abbot/editor/EditorConstants.java index 640af39..51a5e41 100644 --- a/abbot/src/main/java/abbot/editor/EditorConstants.java +++ b/abbot/src/main/java/abbot/editor/EditorConstants.java @@ -8,61 +8,6 @@ */ public interface EditorConstants { - String MENU_FILE = "menus.file"; - String MENU_EDIT = "menus.edit"; - String MENU_TEST = "menus.test"; - String MENU_INSERT = "menus.insert"; - String MENU_CAPTURE = "menus.capture"; - String MENU_HELP = "menus.help"; - String ACTION_PREFIX = "actions."; - String ACTION_EDITOR_ABOUT = "editor-about"; - String ACTION_EDITOR_EMAIL = "editor-email"; - String ACTION_EDITOR_BUGREPORT = "editor-submit-bug"; - String ACTION_EDITOR_WEBSITE = "editor-website"; - String ACTION_EDITOR_USERGUIDE = "editor-userguide"; - String ACTION_EDITOR_QUIT = "editor-quit"; - String ACTION_SCRIPT_OPEN = "script-open"; - String ACTION_SCRIPT_NEW = "script-new"; - String ACTION_SCRIPT_DUPLICATE = "script-duplicate"; - String ACTION_SCRIPT_SAVE = "script-save"; - String ACTION_SCRIPT_SAVE_AS = "script-save-as"; - String ACTION_SCRIPT_RENAME = "script-rename"; - String ACTION_SCRIPT_CLOSE = "script-close"; - String ACTION_SCRIPT_DELETE = "script-delete"; - String ACTION_SCRIPT_CLEAR = "script-clear"; - String ACTION_STEP_CUT = "step-cut"; - String ACTION_STEP_MOVE_UP = "step-move-up"; - String ACTION_STEP_MOVE_DOWN = "step-move-down"; - String ACTION_STEP_GROUP = "step-group"; - String ACTION_SELECT_TESTSUITE = "select-testsuite"; - String ACTION_EXPORT_HIERARCHY = "export-hierarchy"; - String ACTION_RUN = "run"; - String ACTION_RUN_TO = "run-to"; - String ACTION_RUN_SELECTED = "run-selected"; - String ACTION_RUN_LAUNCH = "run-launch"; - String ACTION_RUN_TERMINATE = "run-terminate"; - String ACTION_GET_VMARGS = "run-get-vmargs"; String ACTION_TOGGLE_FORKED = "toggle-forked"; - String ACTION_TOGGLE_SLOW_PLAYBACK = "toggle-slow-playback"; - String ACTION_TOGGLE_AWT_MODE = "toggle-awt-mode"; - String ACTION_TOGGLE_STOP_ON_FAILURE = "toggle-stop-on-failure"; - String ACTION_TOGGLE_STOP_ON_ERROR = "toggle-stop-on-error"; - String ACTION_INSERT_LAUNCH = "insert-launch"; - String ACTION_INSERT_APPLET = "insert-applet"; - String ACTION_INSERT_TERMINATE = "insert-terminate"; - String ACTION_INSERT_CALL = "insert-call"; - String ACTION_INSERT_SAMPLE = "insert-sample"; - String ACTION_INSERT_SEQUENCE = "insert-sequence"; - String ACTION_INSERT_SCRIPT = "insert-script"; - String ACTION_INSERT_FIXTURE = "insert-fixture"; - String ACTION_INSERT_COMMENT = "insert-comment"; - String ACTION_INSERT_EXPRESSION = "insert-expression"; - String ACTION_INSERT_ANNOTATION = "insert-annotation"; - String ACTION_DYNAMIC = "dynamic-actions"; - String ACTION_CAPTURE_IMAGE = "capture-image"; - String ACTION_CAPTURE_COMPONENT = "capture-component"; - String ACTION_SELECT_COMPONENT = "select-component"; - String ACTION_CAPTURE = "capture"; - String ACTION_CAPTURE_ALL = "capture-all"; } diff --git a/abbot/src/main/java/abbot/editor/editors/AppletviewerEditor.java b/abbot/src/main/java/abbot/editor/editors/AppletviewerEditor.java deleted file mode 100644 index 8b32e52..0000000 --- a/abbot/src/main/java/abbot/editor/editors/AppletviewerEditor.java +++ /dev/null @@ -1,134 +0,0 @@ -package abbot.editor.editors; - -import abbot.editor.widgets.ArrayEditor; -import abbot.i18n.Strings; -import abbot.script.Appletviewer; -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JPanel; -import javax.swing.JTextField; - -/** - * Provide convenient editing of an applet launch step. - */ -public class AppletviewerEditor extends StepEditor { - - public static final String HELP_DESC = Strings.get("editor.applet.desc"); - - private final Appletviewer applet; - - private final JTextField code; - private final ArrayEditor params; - private final JTextField codebase; - private final JTextField archive; - private final JTextField width; - private final JTextField height; - - public AppletviewerEditor(Appletviewer applet) { - super(applet); - this.applet = applet; - - code = addTextField(Strings.get("editor.applet.code"), applet.getCode()); - width = addTextField(Strings.get("editor.applet.width"), applet.getWidth()); - height = addTextField(Strings.get("editor.applet.height"), applet.getHeight()); - // For some reason, if we *don't* futz with the layout of - // width/height, the pane never reconfigures properly for the array - // editor. - Component c; - ArrayList list = new ArrayList(); - while ((c = getComponent(getComponentCount() - 1)) != code) { - remove(c); - list.add(c); - } - JPanel p = new JPanel(); - // p.setBorder(new TitledBorder(Strings.get("editor.applet.size"))); - p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); - for (int i = list.size() - 1; i >= 0; i--) { - p.add((Component) list.get(i)); - if (i != 0) { - p.add(Box.createHorizontalStrut(MARGIN)); - } - } - add(p); - - params = addArrayEditor(Strings.get("editor.applet.params"), applet.getParamsAsArray()); - - codebase = addTextField(Strings.get("editor.applet.codebase"), applet.getCodebase()); - archive = addTextField(Strings.get("editor.applet.archive"), applet.getArchive()); - } - - public void actionPerformed(ActionEvent ev) { - Object src = ev.getSource(); - if (src == code) { - applet.setCode(code.getText()); - fireStepChanged(); - } else if (src == params) { - Object[] values = params.getValues(); - Map map = new HashMap(); - for (int i = 0; i < values.length; i++) { - String v = (String) values[i]; - int eq = v.indexOf("="); - if (eq != -1) { - String key = v.substring(0, eq); - String value = v.substring(eq + 1); - map.put(key, value); - } - } - applet.setParams(map); - fireStepChanged(); - } else if (src == codebase) { - String value = codebase.getText(); - if ("".equals(value)) { - value = null; - } - applet.setCodebase(value); - fireStepChanged(); - } else if (src == archive) { - String value = archive.getText(); - if ("".equals(value)) { - value = null; - } - applet.setArchive(value); - fireStepChanged(); - } else if (src == width) { - String value = width.getText(); - if ("".equals(value)) { - value = null; - } - try { - Integer.parseInt(value); - applet.setWidth(value); - width.setForeground(DEFAULT_FOREGROUND); - fireStepChanged(); - } catch (NumberFormatException e) { - width.setForeground(ERROR_FOREGROUND); - } - } else if (src == height) { - String value = height.getText(); - if ("".equals(value)) { - value = null; - } - try { - Integer.parseInt(value); - applet.setHeight(value); - width.setForeground(DEFAULT_FOREGROUND); - fireStepChanged(); - } catch (NumberFormatException e) { - width.setForeground(ERROR_FOREGROUND); - } - } else { - super.actionPerformed(ev); - } - - // Remove the default placeholder description - if (HELP_DESC.equals(applet.getDescription())) { - applet.setDescription(null); - fireStepChanged(); - } - } -} diff --git a/abbot/src/main/java/abbot/finder/AWTHierarchy.java b/abbot/src/main/java/abbot/finder/AWTHierarchy.java index b09d215..06cf130 100644 --- a/abbot/src/main/java/abbot/finder/AWTHierarchy.java +++ b/abbot/src/main/java/abbot/finder/AWTHierarchy.java @@ -42,24 +42,19 @@ public boolean contains(Component component) { @Override public void dispose(Window window) { - if (AWT.isAppletViewerFrame(window)) { - // Don't dispose, it must quit on its own - return; - } - Log.debug("Dispose " + window); Arrays.stream(window.getOwnedWindows()).forEach(this::dispose); if (AWT.isSharedInvisibleFrame(window)) { - // Don't dispose, or any child windows which may be currently - // ignored (but not hidden) will be hidden and disposed. + // Don't dispose, or any child windows that may be currently + // ignored (but not hidden) will be hidden and disposed of. return; } // Ensure the disposal is done on the swing thread so we can catch any // exceptions. If Window.dispose is called from a non-Swing thread, - // it will invoke the dispose action on the Swing thread but in that + // it will invoke the dispose action on the Swing thread, but in that // case we have no control over exceptions. Runnable action = () -> { @@ -114,15 +109,14 @@ public Collection getComponents(Component component) { new ArrayList<>(Arrays.asList(((Container) component).getComponents())); // Add other components which are not explicitly children, but // that are conceptually descendents - if (component instanceof JMenu menu) { - list.add(menu.getPopupMenu()); - } else if (component instanceof Window window) { - list.addAll(Arrays.asList(window.getOwnedWindows())); - } else if (component instanceof JDesktopPane) { - // Add iconified frames, which are otherwise unreachable. - // For consistency, they are still considerered children of - // the desktop pane. - list.addAll(findInternalFramesFromIcons((Container) component)); + switch (component) { + case JMenu menu -> list.add(menu.getPopupMenu()); + case Window window -> list.addAll(Arrays.asList(window.getOwnedWindows())); + case JDesktopPane jDesktopPane -> + // Add iconified frames, which are otherwise unreachable. + // For consistency, they are still considered children of the desktop pane. + list.addAll(findInternalFramesFromIcons((Container) component)); + default -> {} } return list; } diff --git a/abbot/src/main/java/abbot/finder/TestHierarchy.java b/abbot/src/main/java/abbot/finder/TestHierarchy.java index 51c6827..e7c734c 100644 --- a/abbot/src/main/java/abbot/finder/TestHierarchy.java +++ b/abbot/src/main/java/abbot/finder/TestHierarchy.java @@ -22,9 +22,6 @@ public class TestHierarchy extends AWTHierarchy { // Map of components to ignore private final Map filtered = new WeakHashMap<>(); - private static final boolean TRACK_APPLET_CONSOLE = - Boolean.getBoolean("abbot.applet.track_console"); - /** * Create a new TestHierarchy which does not contain any UI Components which might already exist. */ @@ -88,10 +85,6 @@ public boolean isFiltered(Component component) { return false; } - if ("sun.plugin.ConsoleWindow".equals(component.getClass().getName())) { - return !TRACK_APPLET_CONSOLE; - } - return filtered.containsKey(component) || ((component instanceof Window) && isFiltered(component.getParent())) || (!(component instanceof Window) && isWindowFiltered(component)); diff --git a/abbot/src/main/java/abbot/script/AppClassLoader.java b/abbot/src/main/java/abbot/script/AppClassLoader.java index 732ca5a..76dbc7e 100644 --- a/abbot/src/main/java/abbot/script/AppClassLoader.java +++ b/abbot/src/main/java/abbot/script/AppClassLoader.java @@ -15,8 +15,7 @@ * parent class loader gets a chance to look for the class (instead of the default behavior, which always delegates to * the parent class loader first). This behavior enables the class to be reloaded simply by using a new instance of * this class loader with each launch of the app.

This class mimics the behavior of sun.misc.Launcher$AppClassLoader - * as much as possible.

Bootstrap classes are always delegated to the bootstrap loader, with the exception of the - * sun.applet package, which should never be delegated, since it does not work properly unless it is reloaded.

The + * as much as possible.

Bootstrap classes are always delegated to the bootstrap loader.

The * parent of this class loader will be the normal, default AppClassLoader (specifically, the class loader which loaded * this class will be used). */ @@ -37,11 +36,6 @@ public class AppClassLoader extends NonDelegatingClassLoader { */ private final NonDelegatingClassLoader extensionsLoader; - /** - * Whether the framework itself is being tested. - */ - private final boolean frameworkIsUnderTest = false; - /** * Old class loader context for the thread where this loader was installed. */ @@ -50,7 +44,7 @@ public class AppClassLoader extends NonDelegatingClassLoader { private Thread installedThread = null; private String oldClassPath = null; - private class InstallationLock {} + private static class InstallationLock {} private final InstallationLock lock = new InstallationLock(); @@ -98,15 +92,9 @@ public boolean isEventDispatchThread() { // FIXME we should only need the delegate flag if stuff in the classpath // is also found on the system classpath, e.g. the framework itself - // Maybe just set it internally in case the classpaths overlap? + // Maybe just set it internally in case the class paths overlap? protected boolean shouldDelegate(String name) { - return bootstrapLoader.shouldDelegate(name) - && !isExtension(name) - && !(frameworkIsUnderTest && isFrameworkClass(name)); - } - - private boolean isFrameworkClass(String name) { - return name.startsWith("abbot.") || name.startsWith("junit.extensions.abbot."); + return bootstrapLoader.shouldDelegate(name) && !isExtension(name); } private boolean isExtension(String name) { @@ -123,14 +111,6 @@ private boolean isExtension(String name) { * @throws ClassNotFoundException if the class could not be found */ public Class findClass(String name) throws ClassNotFoundException { - if (isBootstrapClassRequiringReload(name)) { - try { - return bootstrapLoader.findClass(name); - } catch (ClassNotFoundException cnf) { - Log.warn(cnf); - } - } - // Look for extensions first in the framework class path (with a // special loader), then in the app class path. // Extensions *must* have the same class loader as the corresponding @@ -179,8 +159,7 @@ public void install() { eventQueue = new AppEventQueue(); eventQueue.install(); - Thread current = Thread.currentThread(); - installedThread = current; + installedThread = Thread.currentThread(); oldClassLoader = installedThread.getContextClassLoader(); installedThread.setContextClassLoader(this); } @@ -248,6 +227,7 @@ public void uninstall() { pop(); thread = null; } catch (EmptyStackException ese) { + // ignore } Log.debug("AppEventQueue uninstalled"); } @@ -258,29 +238,7 @@ public String toString() { } /** - * List of bootstrap classes we most definitely want to be loaded by this class loader, rather than any parent, or - * the bootstrap loader. - */ - private final String[] mustReloadPrefixes = { - "sun.applet.", // need the whole package, not just AppletViewer/Main - }; - - /** - * Does the given class absolutely need to be preloaded? - */ - private boolean isBootstrapClassRequiringReload(String name) { - for (int i = 0; i < mustReloadPrefixes.length; i++) { - if (name.startsWith(mustReloadPrefixes[i])) { - return true; - } - } - return false; - } - - /** - * Returns the path to the primary JRE classes, not including any extensions. This is primarily needed for loading - * sun.applet.AppletViewer/Main, since most other classes in the bootstrap path should only be loaded by the - * bootstrap loader. + * Returns the path to the primary JRE classes, not including any extensions. */ private static String getBootstrapPath() { return System.getProperty("sun.boot.class.path"); @@ -289,17 +247,13 @@ private static String getBootstrapPath() { /** * Provide access to bootstrap classes that we need to be able to reload. */ - private class BootstrapClassLoader extends NonDelegatingClassLoader { + private static class BootstrapClassLoader extends NonDelegatingClassLoader { public BootstrapClassLoader() { super(getBootstrapPath(), null); } protected boolean shouldDelegate(String name) { - // Exclude all bootstrap classes, except for those we know we - // *must* be reloaded on each run in order to have function - // properly (e.g. applet) - return !isBootstrapClassRequiringReload(name) - && !"abbot.script.AppletSecurityManager".equals(name); + return true; } } diff --git a/abbot/src/main/java/abbot/script/Appletviewer.java b/abbot/src/main/java/abbot/script/Appletviewer.java deleted file mode 100644 index 93fcf04..0000000 --- a/abbot/src/main/java/abbot/script/Appletviewer.java +++ /dev/null @@ -1,396 +0,0 @@ -package abbot.script; - -import abbot.Log; -import abbot.NoExitSecurityManager; -import abbot.finder.BasicFinder; -import abbot.finder.ComponentFinder; -import abbot.finder.ComponentSearchException; -import abbot.finder.Matcher; -import abbot.finder.matchers.ClassMatcher; -import abbot.i18n.Strings; -import abbot.tester.ComponentTester; -import java.applet.Applet; -import java.awt.Component; -import java.awt.Frame; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.File; -import java.io.FileOutputStream; -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.TreeMap; -import javax.swing.SwingUtilities; - -/** - * Provides applet launch capability. Usage:
- *

- * <applet code="..." [codebase="..."] [params="..."] [archive="..."]>
- *

- * The attributes are equivalent to those provided in the HTML - * applet tag. The params attribute is a - * comma-separated list of name=value pairs, which will be passed to the applet within the - * applet tag as param elements. - *

- * WARNING: Closing the appletviewer window from the window manager - * close button will result applet-spawned event dispatch threads being left running. To avoid this situation, always - * use the appletviewer Quit menu item or use the {@link #terminate()} method of this class. - */ -public class Appletviewer extends Launch { - - private String code; - private Map params; - private String codebase; - private String archive; - private String width; - private String height; - private ClassLoader appletClassLoader; - private Frame appletViewerFrame; - private transient SecurityManager oldSM; - private transient boolean terminating; - - private static final String DEFAULT_WIDTH = "100"; - private static final String DEFAULT_HEIGHT = "100"; - private static final String CLASS_NAME = "sun.applet.Main"; - private static final String METHOD_NAME = "main"; - private static final int LAUNCH_TIMEOUT = 30000; - - private static final String USAGE = - ""; - - private static void quitApplet(final Frame frame) { - new ComponentTester().selectAWTMenuItem(frame, "Applet|Quit"); - } - - private static Map patchAttributes(Map map) { - map.put(TAG_CLASS, CLASS_NAME); - map.put(TAG_METHOD, METHOD_NAME); - return map; - } - - public Appletviewer(Resolver resolver, Map attributes) { - super(resolver, patchAttributes(attributes)); - code = (String) attributes.get(TAG_CODE); - params = parseParams((String) attributes.get(TAG_PARAMS)); - codebase = (String) attributes.get(TAG_CODEBASE); - archive = (String) attributes.get(TAG_ARCHIVE); - width = (String) attributes.get(TAG_WIDTH); - height = (String) attributes.get(TAG_HEIGHT); - } - - public Appletviewer( - Resolver resolver, - String description, - String code, - Map params, - String codebase, - String archive, - String classpath) { - super(resolver, description, CLASS_NAME, METHOD_NAME, null, classpath, false); - this.code = code; - this.params = params; - this.codebase = codebase; - this.archive = archive; - } - - /** - * Run this step. Causes the appropriate HTML file to be written for use by appletviewer and its path used as the - * sole argument. - */ - @Override - public void runStep() throws Throwable { - File dir = new File(System.getProperty("user.dir")); - File htmlFile = File.createTempFile("abbot-applet", ".html", dir); - htmlFile.deleteOnExit(); - try { - FileOutputStream os = new FileOutputStream(htmlFile); - os.write(generateHTML().getBytes()); - os.close(); - setArguments(new String[] {"[" + htmlFile.getName() + "]"}); - super.runStep(); - // Wait for the applet to become visible - long start = System.currentTimeMillis(); - ComponentFinder finder = new BasicFinder(getResolver().getHierarchy()); - Matcher matcher = new ClassMatcher(Applet.class, true); - while (true) { - try { - Component c = finder.find(matcher); - appletViewerFrame = (Frame) SwingUtilities.getWindowAncestor(c); - addCloseListener(appletViewerFrame); - appletClassLoader = c.getClass().getClassLoader(); - break; - } catch (ComponentSearchException e) { - } - if (System.currentTimeMillis() - start > LAUNCH_TIMEOUT) { - throw new RuntimeException(Strings.get("step.appletviewer.launch_timed_out")); - } - Thread.sleep(200); - } - } finally { - htmlFile.delete(); - } - } - - private void addCloseListener(final Frame frame) { - // Workaround for lockup when applet is closed via WM close button and - // then relaunched. Can't figure out how to kill those extant threads. - // This avoids the lockup, but leaves threads around. - frame.addWindowListener( - new WindowAdapter() { - public void windowClosing(WindowEvent e) { - // Invoke the frame's quit menu item - quitApplet(frame); - } - }); - } - - protected String generateHTML() { - StringBuffer html = new StringBuffer(); - html.append(""); - Iterator iter = params.keySet().iterator(); - while (iter.hasNext()) { - String key = (String) iter.next(); - String value = (String) params.get(key); - html.append(""); - } - html.append(""); - return html.toString(); - } - - @Override - public void setTargetClassName(String name) { - if (CLASS_NAME.equals(name)) { - super.setTargetClassName(name); - } else { - throw new IllegalArgumentException(Strings.get("step.call.immutable_class")); - } - } - - @Override - public void setMethodName(String name) { - if (METHOD_NAME.equals(name)) { - super.setMethodName(name); - } else { - throw new IllegalArgumentException(Strings.get("step.call.immutable_method")); - } - } - - public void setCode(String code) { - this.code = code; - } - - public String getCode() { - return code; - } - - public void setCodebase(String codebase) { - this.codebase = codebase; - } - - public String getCodebase() { - return codebase; - } - - public void setArchive(String archive) { - this.archive = archive; - } - - public String getArchive() { - return archive; - } - - public String getWidth() { - return width != null ? width : DEFAULT_WIDTH; - } - - public void setWidth(String width) { - this.width = width; - try { - Integer.parseInt(width); - } catch (NumberFormatException e) { - this.width = null; - } - } - - public String getHeight() { - return height != null ? height : DEFAULT_HEIGHT; - } - - public void setHeight(String height) { - this.height = height; - try { - Integer.parseInt(height); - } catch (NumberFormatException e) { - this.height = null; - } - } - - public Map getParams() { - return params; - } - - public void setParams(Map params) { - this.params = params; - } - - protected Map parseParams(String attribute) { - Map map = new HashMap(); - if (attribute != null) { - String[] list = ArgumentParser.parseArgumentList(attribute); - for (int i = 0; i < list.length; i++) { - String p = list[i]; - int eq = p.indexOf("="); - map.put(p.substring(0, eq), p.substring(eq + 1)); - } - } - return map; - } - - public String[] getParamsAsArray() { - ArrayList list = new ArrayList(); - // Ensure we always get a consistent order - Iterator iter = new TreeMap(params).keySet().iterator(); - while (iter.hasNext()) { - String key = (String) iter.next(); - String value = (String) params.get(key); - list.add(key + "=" + value); - } - return (String[]) list.toArray(new String[list.size()]); - } - - public String getParamsAttribute() { - return ArgumentParser.encodeArguments(getParamsAsArray()); - } - - @Override - public Map getAttributes() { - Map map = super.getAttributes(); - map.put(TAG_CODE, getCode()); - if (params.size() > 0) { - map.put(TAG_PARAMS, getParamsAttribute()); - } - if (getCodebase() != null) { - map.put(TAG_CODEBASE, getCodebase()); - } - if (getArchive() != null) { - map.put(TAG_ARCHIVE, getArchive()); - } - if (!DEFAULT_WIDTH.equals(getWidth())) { - map.put(TAG_WIDTH, getWidth()); - } - if (!DEFAULT_HEIGHT.equals(getHeight())) { - map.put(TAG_HEIGHT, getHeight()); - } - - // don't need to store these - map.remove(TAG_CLASS); - map.remove(TAG_METHOD); - map.remove(TAG_THREADED); - map.remove(TAG_ARGS); - - return map; - } - - @Override - public String getDefaultDescription() { - String desc = Strings.get("step.appletviewer", new Object[] {getCode()}); - return desc; - } - - @Override - public String getUsage() { - return USAGE; - } - - @Override - public String getXMLTag() { - return TAG_APPLETVIEWER; - } - - public ClassLoader getContextClassLoader() { - // Maybe use codebase/archive to have an alternative classpath? - ClassLoader cl = super.getContextClassLoader(); - return appletClassLoader != null ? appletClassLoader : cl; - } - - protected void install() { - super.install(); - - // Appletviewer expects the security manager to be an instance of - // AppletSecurity. Use the custom class loader, *not* the one for the - // applet. - installAppletSecurityManager(super.getContextClassLoader()); - } - - /** - * Install a security manager if there is none; this is a workaround to prevent sun's applet viewer's security - * manager from preventing any classes from being loaded. - */ - private void installAppletSecurityManager(ClassLoader cl) { - oldSM = System.getSecurityManager(); - if (oldSM == null || (oldSM instanceof NoExitSecurityManager)) { - Log.debug("install security manager"); - // NOTE: the security manager *must* be loaded with the same class - // loader as the appletviewer. - try { - Class cls = Class.forName("abbot.script.AppletSecurityManager", true, cl); - Constructor ctor = cls.getConstructor(SecurityManager.class); - SecurityManager sm = (SecurityManager) ctor.newInstance(new Object[] {oldSM}); - System.setSecurityManager(sm); - } catch (Exception exc) { - Log.warn(exc); - } - } else { - Log.debug("old sm=" + oldSM); - } - } - - /** - * To properly terminate, we need to invoke AppletViewer's appletQuit() method (protected, but accessible). - */ - public void terminate() { - synchronized (this) { - // Avoid recursion, since we'll return here when the applet - // invokes System.exit. - if (terminating) { - return; - } - - terminating = true; - } - Frame frame = appletViewerFrame; - appletViewerFrame = null; - try { - // FIXME: figure out why closing the appletviewer window causes an - // EDT hangup. (maybe need to post this to the applet's event - // queue?) - // Also figure out who's creating all the extra EDTs and dispose of - // them properly, but it's probably not worth the effort. - if (frame != null) { - quitApplet(frame); - } - // Now clean up normally - super.terminate(); - if (oldSM != null) { - Log.debug("restore sm=" + oldSM); - System.setSecurityManager(oldSM); - } - appletClassLoader = null; - } finally { - synchronized (this) { - terminating = false; - } - } - } -} diff --git a/abbot/src/main/java/abbot/script/ComponentReference.java b/abbot/src/main/java/abbot/script/ComponentReference.java index 4e5657e..a1efff7 100644 --- a/abbot/src/main/java/abbot/script/ComponentReference.java +++ b/abbot/src/main/java/abbot/script/ComponentReference.java @@ -10,7 +10,6 @@ import abbot.tester.Robot; import abbot.util.AWT; import com.windowtester.runtime.util.StringComparator; -import java.applet.Applet; import java.awt.Component; import java.awt.Container; import java.awt.Dialog; @@ -52,12 +51,12 @@ * * The component reference ID may be used in scripts in place of the actual component to which this * reference refers. The conversion will be made when the actual component is needed. The ID is - * arbitrary, and may be changed in scripts to any unique string (just remember to change all - * references to the ID in other places in the script as well).

A number of optional tags are - * supported to provide an increasingly precise specification of the desired component:
+ * arbitrary and may be changed in scripts to any unique string (remember to change all references + * to the ID in other places in the script as well).

A number of optional tags are supported to + * provide an increasingly precise specification of the desired component:
*

*/ @@ -82,7 +81,7 @@ /* To extend, probably want to make a static function here that stores attributes and a lookup interface to read that attribute. Do this only if - it is directly needed. + it is directly necessary. Should the JRE class be used instead of a custom class? pros: doesn't save custom classes, which might change @@ -93,14 +92,14 @@ attributes and a lookup interface to read that attribute. Do this only if */ /* - Optimization note: All lookups are cached, so that at most we have to + Optimization note: All lookups are cached, so that at most we have to traverse the hierarchy once. Successful lookups are cached until the referenced component is GCd, removed from the hierarchy, or otherwise marked invalid. Unsuccessful lookups are cached for the duration of a particular lookup. These happen in several places. 1) when resolving a cref into a component (getComponent()) - 2) when a script is checking for existing references prior to creating a + 2) when a script is checking for existing references before creating a new one (getReference()). 3) when creating a new reference (ComponentReference(). 4) when looking for a matching, existing reference (matchExisting()). @@ -109,14 +108,14 @@ attributes and a lookup interface to read that attribute. Do this only if see also NOTES */ -public class ComponentReference implements XMLConstants, XMLifiable, Comparable { +public class ComponentReference + implements XMLConstants, XMLifiable, Comparable { public static final String SHARED_FRAME_ID = "shared frame"; // Matching weights for various attributes private static final int MW_NAME = 100; private static final int MW_ROOT = 25; - // private static final int MW_WEIGHTED = 50; private static final int MW_TAG = 50; private static final int MW_PARENT = 25; // private static final int MW_WINDOW = 25; @@ -128,15 +127,12 @@ public class ComponentReference implements XMLConstants, XMLifiable, Comparable private static final int MW_ICON = 25; private static final int MW_INDEX = 10; // private static final int MW_CLASS = 1; - // Pretty much for applets only, or other embedded frames + // Pretty much for embedded frames private static final int MW_PARAMS = 1; private static final int MW_DOCBASE = 1; - // Mostly for distinguishing between multiple components that would - // otherwise all match + // Mostly for distinguishing between multiple components that would otherwise all match private static final int MW_HORDER = 1; private static final int MW_VORDER = 1; - // private static final int MW_ENABLED = 1; - // private static final int MW_FOCUSED = 1; private static final int MW_SHOWING = 1; /** @@ -150,15 +146,16 @@ public class ComponentReference implements XMLConstants, XMLifiable, Comparable private final Map attributes = new HashMap<>(); // This helps component reference creation by an order of magnitude, // especially when dealing with ordered attributes. - private WeakReference cachedLookup; + private WeakReference cachedLookup; /** * This ThreadLocal allows us to keep track of unresolved components on a per-thread (basically * per-lookup) basis. */ - private static final ThreadLocal lookupFailures = - new ThreadLocal() { - protected synchronized Object initialValue() { + private static final ThreadLocal> lookupFailures = + new ThreadLocal<>() { + @Override + protected synchronized Map initialValue() { return new HashMap<>(); } }; @@ -167,9 +164,10 @@ protected synchronized Object initialValue() { * This ThreadLocal allows us to keep track of non-showing, resolved components on a per-thread * (basically per-lookup) basis. */ - private static final ThreadLocal nonShowingMatches = - new ThreadLocal() { - protected synchronized Object initialValue() { + private static final ThreadLocal> nonShowingMatches = + new ThreadLocal<>() { + @Override + protected synchronized Map initialValue() { return new HashMap<>(); } }; @@ -177,9 +175,10 @@ protected synchronized Object initialValue() { /** * Keep track of which ComponentReference ctor is the first one. */ - private static final ThreadLocal ownsFailureCache = - new ThreadLocal() { - protected synchronized Object initialValue() { + private static final ThreadLocal ownsFailureCache = + new ThreadLocal<>() { + @Override + protected synchronized Boolean initialValue() { return Boolean.TRUE; } }; @@ -240,10 +239,10 @@ private ComponentReference( boolean includeHierarchyAttributes, Map newReferences) { // This method may be called recursively (indirectly through - // Resolver.addComponent) in order to add references for parent + // Resolver.addComponent) to add references for parent // components. Make note of whether this instantiation needs // to clear the failure cache when it's done. - boolean cleanup = (Boolean) ownsFailureCache.get(); + boolean cleanup = ownsFailureCache.get(); ownsFailureCache.set(Boolean.FALSE); Log.debug("ctor: " + comp); @@ -296,19 +295,9 @@ private ComponentReference( setAttribute(TAG_ICON, icon); } - if (comp instanceof Applet) { - Applet applet = (Applet) comp; - setAttribute(TAG_PARAMS, encodeParams(applet)); - // 10/3/07 : kp - // recording on applet - get npe - // java.net.URL url = applet.getDocumentBase(); - java.net.URL url = null; - setAttribute(TAG_DOCBASE, "null"); - } - Container parent = resolver.getHierarchy().getParent(comp); if (null != parent) { - // Don't save window indices, they're not sufficiently reliable + // Don't save window indices, they're not reliable enough if (!(comp instanceof Window)) { int index = getIndex(parent, comp); if (index != -1) { @@ -355,7 +344,7 @@ private ComponentReference( // Set the cache immediately if (cacheOnCreation || AWT.isSharedInvisibleFrame(comp)) { Log.debug("Cacheing initial match"); - cachedLookup = new WeakReference(comp); + cachedLookup = new WeakReference<>(comp); } else { cachedLookup = null; } @@ -369,22 +358,7 @@ private ComponentReference( } public static int getIndex(Container parent, Component comp) { - if (comp instanceof Window) { - Window[] owned = ((Window) parent).getOwnedWindows(); - for (int i = 0; i < owned.length; i++) { - if (owned[i] == comp) { - return i; - } - } - } else { - Component[] children = parent.getComponents(); - for (int i = 0; i < children.length; ++i) { - if (children[i] == comp) { - return i; - } - } - } - return -1; + return Robot.getIndex(parent, comp); } public Component getComponent() @@ -400,7 +374,7 @@ public Component getComponent(Hierarchy hierarchy) throws ComponentNotFoundException, MultipleComponentsFoundException { try { - return findInHierarchy(null, hierarchy, 1, new HashMap<>()); + return findInHierarchy(hierarchy, 1, new HashMap<>()); } finally { // never called recursively, so we can clear the cache here getLookupFailures().clear(); @@ -415,7 +389,7 @@ private void addParent(Container parent, Map newRefe /** * Returns whether the given component is reachable from the root of the current hierarchy. - * Popups' transient elements may already have gone away, and will be unreachable. + * Popups' transient elements may already have gone away and will be unreachable. */ private boolean reachableInHierarchy(Component c) { Window w = AWT.getWindow(c); @@ -437,8 +411,7 @@ private void validate(Component comp, Map newReferen // unreachable from the root of the hierarchy, or if a lookup will // fail for other reasons, simply check for a match. // WARNING: this leaves a hole if the component actually needs an - // ORDER attribute, but the ORDER attribute is intended for applets - // only. + // ORDER attribute if (!reachableInHierarchy(comp)) { int wt = getMatchWeight(comp, newReferences); int exact = getExactMatchWeight(); @@ -452,7 +425,7 @@ private void validate(Component comp, Map newReferen } else { try { Log.debug("Finding in hierarchy (" + resolver.getHierarchy() + ")"); - findInHierarchy(null, resolver.getHierarchy(), getExactMatchWeight(), newReferences); + findInHierarchy(resolver.getHierarchy(), getExactMatchWeight(), newReferences); } catch (MultipleComponentsFoundException multiples) { try { // More than one match found, so add more information @@ -466,8 +439,7 @@ private void validate(Component comp, Map newReferen "Reverse lookup failed to uniquely match " + Robot.toString(comp) + ": " + e); } } catch (ComponentNotFoundException e) { - // This indicates a failure in the reference recording - // mechanism, and requires a fix. + // This indicates a failure in the reference recording mechanism and requires a fix. throw new Error( "Reverse lookup failed looking for " + Robot.toString(comp) @@ -479,24 +451,6 @@ private void validate(Component comp, Map newReferen } } - public static String getDescriptiveName(Component c) { - if (AWT.isSharedInvisibleFrame(c)) { - return Strings.get("component.default_frame"); - } - - String name = getName(c); - if (name == null) { - if ((name = getTitle(c)) == null) { - if ((name = getText(c)) == null) { - if ((name = getLabel(c)) == null) { - if ((name = getIconName(c)) == null) {} - } - } - } - } - return name; - } - public String getDescriptiveName() { String id = getAttribute(TAG_ID); if (id == null) { @@ -598,8 +552,8 @@ public ComponentReference getInvokerReference(Map ne * @throws InvalidScriptException invalid script * @deprecated */ - // This is only used when editing scripts, since we don't want to have to - // hunt down existing references + // This is only used when editing scripts since we don't want to have to hunt down existing + // references public void fromXML(String input) throws InvalidScriptException { try { Document doc = DocumentHelper.parseText(input); @@ -614,7 +568,7 @@ public void fromXML(String input) throws InvalidScriptException { } /** - * Parse settings from the given XML. Only overwrite the ID if useGivenID is set. + * Parse settings from the given XML. Only overwrite the ID if useGivenID is set. * * @throws InvalidScriptException if the given Element is not valid XML for a ComponentReference. */ @@ -647,6 +601,7 @@ private void fromXML(Element el, boolean useIDFromXML) throws InvalidScriptExcep } } + @Override public Element toXML() { Element el = DocumentHelper.createElement(TAG_COMPONENT); for (String key : new TreeMap<>(attributes).keySet()) { @@ -662,16 +617,19 @@ public Element toXML() { * @return editable string * @deprecated Used to be used to edit XML in a text editor. */ + @Override public String toEditableString() { return toXMLString(); } + @Override public boolean equals(Object obj) { return this == obj || (obj instanceof ComponentReference) && toXMLString().equals(((ComponentReference) obj).toXMLString()); } + @Override public String toString() { String id = getID(); String cname = getAttribute(TAG_CLASS); @@ -714,23 +672,23 @@ private Component bestMatch(Set set) throws MultipleComponentsFoundEx } // Preferring one enabled/focused state is dangerous to do: // An enabled component might be preferred over a disabled one, - // but it will fail if you're trying to examine state on the + // but it will fail if you're trying to examine the state on the // disabled component. Ditto for focused. } - String horder = getAttribute(TAG_HORDER); - if (horder != null) { + String hOrder = getAttribute(TAG_HORDER); + if (hOrder != null) { for (int i = 0; i < matches.length; i++) { String order = getOrder(matches[i], matches, true); - if (horder.equals(order)) { + if (hOrder.equals(order)) { weights[i] += MW_HORDER; } } } - String vorder = getAttribute(TAG_VORDER); - if (vorder != null) { + String vOrder = getAttribute(TAG_VORDER); + if (vOrder != null) { for (int i = 0; i < matches.length; i++) { String order = getOrder(matches[i], matches, false); - if (vorder.equals(order)) { + if (vOrder.equals(order)) { weights[i] += MW_VORDER; } } @@ -749,7 +707,7 @@ private Component bestMatch(Set set) throws MultipleComponentsFoundEx } } if (best.size() == 1) { - return (Component) best.get(0); + return best.getFirst(); } // Finally, see if any match the old cached value Component cache = getCachedLookup(resolver.getHierarchy()); @@ -771,7 +729,7 @@ private Component bestMatch(Set set) throws MultipleComponentsFoundEx * screen position. All components with the same effective value will have the same order. */ static String getOrder(Component original, Component[] matchList, boolean horizontal) { - Comparator c = horizontal ? HORDER_COMPARATOR : VORDER_COMPARATOR; + Comparator c = horizontal ? HORDER_COMPARATOR : VORDER_COMPARATOR; Component[] matches = matchList.clone(); Arrays.sort(matches, c); int order = 0; @@ -799,7 +757,7 @@ private void disambiguate( Log.debug("Attempting to disambiguate multiple matches"); Container parent = resolver.getHierarchy().getParent(original); boolean retryOnFailure = false; - String order = null; + String order; try { String cname = original.getClass().getName(); // Use the inner class name unless it's numeric (numeric values @@ -810,17 +768,14 @@ private void disambiguate( } else if (parent != null && getAttribute(TAG_PARENT) == null && !(original instanceof JPopupMenu)) { - Log.debug("Adding parent"); addParent(parent, newReferences); retryOnFailure = true; } else if (getAttribute(TAG_HORDER) == null && (order = getOrder(original, matches, true)) != null) { - Log.debug("Adding horder"); setAttribute(TAG_HORDER, order); retryOnFailure = true; } else if (getAttribute(TAG_VORDER) == null && (order = getOrder(original, matches, false)) != null) { - Log.debug("Adding vorder"); setAttribute(TAG_VORDER, order); retryOnFailure = true; } @@ -829,7 +784,7 @@ && getAttribute(TAG_PARENT) == null // Remove this cref and its ancestors from the failure // cache so we don't automatically fail getLookupFailures().remove(this); - findInHierarchy(null, resolver.getHierarchy(), getExactMatchWeight(), newReferences); + findInHierarchy(resolver.getHierarchy(), getExactMatchWeight(), newReferences); Log.debug("Success!"); } catch (MultipleComponentsFoundException multiples) { if (retryOnFailure) { @@ -917,7 +872,7 @@ private int getMatchWeight(Component comp, Map newRe weight += MW_PARENT; } // Don't detract on parent mismatch, since changing a parent is - // not that big a change (e.g. adding a scroll pane) + // not that big a change (e.g., adding a scroll pane) } // ROOT and PARENT are mutually exclusive else if (null != getAttribute(TAG_ROOT)) { @@ -996,25 +951,6 @@ else if (null != getAttribute(TAG_ROOT)) { // common. } - if (comp instanceof Applet) { - Applet applet = (Applet) comp; - String params = getAttribute(TAG_PARAMS); - if (null != params) { - String params2 = encodeParams(applet); - if (expressionMatch(params, params2)) { - weight += MW_PARAMS; - } - } - String docBase = getAttribute(TAG_DOCBASE); - if (null != docBase) { - java.net.URL url = null; - if (url != null) { - expressionMatch(docBase, url.toString()); - } - } - // No negative weighting here - } - if (Log.isClassDebugEnabled(ComponentReference.class)) { Log.debug( "Compared " + Robot.toString(comp) + " to " + toXMLString() + " weight is " + weight); @@ -1090,11 +1026,12 @@ private int getExactMatchWeight() { * the preferred Component. * * While there is a subtle difference between the two cases (when running - * a test it is expected that there will be some match, whereas when - * creating a new reference there may or may not be a match, based on the + * a test, it is expected that there will be some match. Whereas when + * creating a new reference, there may or may not be a match, based on the * current script contents), it is not a useful distinction. */ - private Component resolveComponent(Component preferred, Map newReferences) { + private Component resolveComponent( + Component preferred, Map newReferences) { // This call should be equivalent to getComponent(), but without // clearing the lookup failure cache on completion if (Log.isClassDebugEnabled(ComponentReference.class)) { @@ -1102,7 +1039,7 @@ private Component resolveComponent(Component preferred, Map newReferences) { } Component found = null; try { - found = findInHierarchy(null, resolver.getHierarchy(), 1, newReferences); + found = findInHierarchy(resolver.getHierarchy(), 1, newReferences); } catch (MultipleComponentsFoundException e) { Component[] list = e.getComponents(); for (Component component : list) { @@ -1140,7 +1077,7 @@ public static ComponentReference getReference( Resolver r, Component comp, Map newReferences) { Log.debug("Looking for a reference for " + Robot.toString(comp)); // Preserve the failure cache across both lookup and creation - boolean cleanup = (Boolean) ownsFailureCache.get(); + boolean cleanup = ownsFailureCache.get(); ownsFailureCache.set(Boolean.FALSE); // Allow the resolver to do cacheing if it needs to; otherwise we'd @@ -1162,27 +1099,23 @@ public static ComponentReference getReference( } public static ComponentReference matchExisting( - final Component comp, Collection existing) { + Component comp, Collection existing) { Log.debug("Matching " + Robot.toString(comp) + " against existing refs"); // This method might be called recursively (indirectly through - // Resolver.addComponent) in order to add references for parent + // Resolver.addComponent) to add references for parent // components. Make note of whether this level of invocation needs // to clear the failure cache when it's done. - boolean cleanup = (Boolean) ownsFailureCache.get(); + boolean cleanup = ownsFailureCache.get(); ownsFailureCache.set(Boolean.FALSE); ComponentReference match = null; Iterator iter = existing.iterator(); // Sort such that the best match comes first Map matches = - new TreeMap<>( - (Comparator) - (o1, o2) -> - ((ComponentReference) o2).getMatchWeight(comp) - - ((ComponentReference) o1).getMatchWeight(comp)); - Map empty = new HashMap<>(); + new TreeMap<>((o1, o2) -> o2.getMatchWeight(comp) - o1.getMatchWeight(comp)); + Map empty = new HashMap<>(); while (iter.hasNext()) { ComponentReference ref = iter.next(); if (comp == ref.getCachedLookup(ref.resolver.getHierarchy()) @@ -1261,47 +1194,12 @@ private static String getIconName(Component c) { } private static String getName(Component c) { - String name = AWT.hasDefaultName(c) ? null : c.getName(); - // Accessibility behaves like what we used to do with getTag. - // Not too helpful for our purposes, especially when the - // data on which the name is based might be dynamic. - /* - if (name == null) { - AccessibleContext context = c.getAccessibleContext(); - if (context != null) - name = context.getAccessibleName(); - } - */ - return name; - } - - /** - * Convert the given applet's parameters into a simple String. - */ - private String encodeParams(Applet applet) { - // TODO: is there some other way of digging out the full set of - // parameters that were passed the applet? b/c here we rely on the - // applet having been properly written to tell us about supported - // parameters. - StringBuilder sb = new StringBuilder(); - String[][] info = applet.getParameterInfo(); - if (info == null) { - // Default implementation of applet returns null - return "null"; - } - for (String[] strings : info) { - sb.append(strings[0]); - sb.append("="); - String param = applet.getParameter(strings[0]); - sb.append(param != null ? param : "null"); - sb.append(";"); - } - return sb.toString(); + return AWT.hasDefaultName(c) ? null : c.getName(); } public Component getCachedLookup(Hierarchy hierarchy) { if (cachedLookup != null) { - Component c = (Component) cachedLookup.get(); + Component c = cachedLookup.get(); // Discard if the component has been gc'd, is no longer in the // hierarchy, or is no longer reachable from a Window. if (c != null && hierarchy.contains(c) && AWT.getWindow(c) != null) { @@ -1318,13 +1216,8 @@ public Component getCachedLookup(Hierarchy hierarchy) { * hierarchy whose match weight exceeds the given minimum. If a valid cached lookup exists, that * is returned immediately. */ - // TODO: refactor this to extract the finder/lookup logic into a separate - // class. the ref should only store attributes. private Component findInHierarchy( - Container root, - Hierarchy hierarchy, - int weight, - Map newReferences) + Hierarchy hierarchy, int weight, Map newReferences) throws ComponentNotFoundException, MultipleComponentsFoundException { Component match; @@ -1341,30 +1234,9 @@ private Component findInHierarchy( Set set = new HashSet<>(); match = getCachedLookup(hierarchy); if (match != null) { - // This is always valid if (AWT.isSharedInvisibleFrame(match)) { return match; } - // TODO: always use the cached lookup; since TestHierarchy - // auto-disposes, only improperly disposed components will still - // match. Codify this behavior with an explicit test. - - // Normally, we'd always want to use the cached lookup, but there - // are instances where a component hierarchy may be used in a - // transient way, so a given reference may need to match more than - // one object without the first having been properly disposed. - // Consider a createDialog() method, which creates an identical - // dialog on each invocation, with an OK button. Every call of - // the method is semantically providing the same component, - // although the implementation may create a new one each time. If - // previous instances have not been properly disposed, we need a - // way to prefer a brand new instance over an old one. We do that - // by checking the cache window's showing state. - // A showing match will trump a non-showing one, - // but if there are multiple, non-showing matches, the cached - // lookup will win. - // We check the window, not the component itself, because some - // components hide their children. Window w = AWT.getWindow(match); if (w != null && (w.isShowing() || getNonShowingMatches().get(this) == match)) { Log.debug("Using cached lookup for " + getID() + " (hierarchy=" + hierarchy + ")"); @@ -1374,7 +1246,7 @@ private Component findInHierarchy( } } - weight = findMatchesInHierarchy(root, hierarchy, weight, set, newReferences); + findMatchesInHierarchy(null, hierarchy, weight, set, newReferences); Log.debug("Found " + set.size() + " matches for " + toXMLString()); if (set.size() == 1) { @@ -1398,7 +1270,7 @@ private Component findInHierarchy( // This provides significant speedup when many similar components are // in play. Log.debug("Cacheing match: " + Integer.toHexString(match.hashCode())); - cachedLookup = new WeakReference(match); + cachedLookup = new WeakReference<>(match); if (!match.isShowing()) { getNonShowingMatches().put(this, match); } @@ -1406,8 +1278,8 @@ private Component findInHierarchy( } /** - * Return the the set of all components under the given component's hierarchy (inclusive) which - * match the given reference. + * Return the set of all components under the given component's hierarchy (inclusive) which match + * the given reference. */ private int findMatchesInHierarchy( Component root, @@ -1471,10 +1343,12 @@ private static Map createAttributeMap(String[][] values) { try { x1 = c1.getLocationOnScreen().x; } catch (Exception e) { + // ignore } try { x2 = c2.getLocationOnScreen().x; } catch (Exception e) { + // ignore } return x1 - x2; }; @@ -1486,10 +1360,12 @@ private static Map createAttributeMap(String[][] values) { try { y1 = c1.getLocationOnScreen().y; } catch (Exception e) { + // ignore } try { y2 = c2.getLocationOnScreen().y; } catch (Exception e) { + // ignore } return y1 - y2; }; @@ -1546,13 +1422,11 @@ public int getIndex() { } } - public int compareTo(Object o) { - return getID().compareTo(((ComponentReference) o).getID()); + @Override + public int compareTo(ComponentReference o) { + return getID().compareTo(o.getID()); } - /** - * See javax.swing.JComponent.getBorderTitle. - */ private static String getBorderTitle(Border b) { String title = null; if (b instanceof TitledBorder) { @@ -1600,12 +1474,12 @@ private String getComponentWindowTitle(Component c) { return title; } - private static Map getLookupFailures() { - return (Map) lookupFailures.get(); + private static Map getLookupFailures() { + return lookupFailures.get(); } - private static Map getNonShowingMatches() { - return (Map) nonShowingMatches.get(); + private static Map getNonShowingMatches() { + return nonShowingMatches.get(); } public String getUniqueID(Map refs) { diff --git a/abbot/src/main/java/abbot/script/Script.java b/abbot/src/main/java/abbot/script/Script.java index eddf9b1..419464c 100644 --- a/abbot/src/main/java/abbot/script/Script.java +++ b/abbot/src/main/java/abbot/script/Script.java @@ -280,7 +280,7 @@ public void save(Writer writer) throws IOException { formatForSave = true; Element el = toXML(); formatForSave = false; - el.setName(TAG_AWTTESTSCRIPT); + el.setName(TAG_AWT_TEST_SCRIPT); Document doc = DocumentHelper.createDocument(el); doc.write(writer); @@ -617,15 +617,15 @@ public static boolean isScript(File file) { if (file.length() == 0) { return true; } - if (!file.exists() || !file.isFile() || file.length() < TAG_AWTTESTSCRIPT.length() * 2 + 5) { + if (!file.exists() || !file.isFile() || file.length() < TAG_AWT_TEST_SCRIPT.length() * 2 + 5) { return false; } try (InputStream is = new BufferedInputStream(new FileInputStream(file))) { - int len = XML_INFO.length() + TAG_AWTTESTSCRIPT.length() + 15; + int len = XML_INFO.length() + TAG_AWT_TEST_SCRIPT.length() + 15; byte[] buf = new byte[len]; is.read(buf, 0, buf.length); String str = new String(buf); - return str.contains(TAG_AWTTESTSCRIPT); + return str.contains(TAG_AWT_TEST_SCRIPT); } catch (Exception exc) { return false; } diff --git a/abbot/src/main/java/abbot/script/XMLConstants.java b/abbot/src/main/java/abbot/script/XMLConstants.java index 45ffadb..0e9ccfd 100644 --- a/abbot/src/main/java/abbot/script/XMLConstants.java +++ b/abbot/src/main/java/abbot/script/XMLConstants.java @@ -9,7 +9,7 @@ public interface XMLConstants { /** * Primary document tag for a test script. */ - String TAG_AWTTESTSCRIPT = "AWTTestScript"; + String TAG_AWT_TEST_SCRIPT = "AWTTestScript"; String TAG_LAUNCH = "launch"; @@ -20,15 +20,10 @@ public interface XMLConstants { String TAG_CLASSPATH = "classpath"; String TAG_THREADED = "threaded"; - String TAG_APPLETVIEWER = "appletviewer"; - String TAG_CODE = "code"; - String TAG_CODEBASE = "codebase"; - String TAG_ARCHIVE = "archive"; String TAG_TERMINATE = "terminate"; String TAG_COMPONENT = "component"; String TAG_ID = "id"; String TAG_NAME = "name"; - String TAG_WEIGHTED = "weighted"; String TAG_WINDOW = "window"; /** @@ -76,7 +71,6 @@ public interface XMLConstants { String TAG_AWT = "awt"; String TAG_VMARGS = "vmargs"; String TAG_WAIT = "wait"; - String TAG_EXPR = "expr"; String TAG_METHOD = "method"; String TAG_ARGS = "args"; String TAG_VALUE = "value"; @@ -84,8 +78,6 @@ public interface XMLConstants { String TAG_INVERT = "invert"; String TAG_TIMEOUT = "timeout"; String TAG_POLL_INTERVAL = "pollInterval"; - String TAG_STOP_ON_FAILURE = "stopOnFailure"; - String TAG_STOP_ON_ERROR = "stopOnError"; - // this is not actually used as a tag per se + // this is not used as a tag per se String TAG_COMMENT = "comment"; } diff --git a/abbot/src/main/java/abbot/tester/Robot.java b/abbot/src/main/java/abbot/tester/Robot.java index 3cb77ea..0b8d9eb 100644 --- a/abbot/src/main/java/abbot/tester/Robot.java +++ b/abbot/src/main/java/abbot/tester/Robot.java @@ -66,27 +66,27 @@ * Provide a higher level of abstraction for user input (A Better Robot). The Robot's operation may * be affected by the following properties:
*
abbot.robot.auto_delay

- * Set this to a value representing the millisecond count in between generated events. Usually just - * set to 100-200 if you want to slow down the playback to simulate actual user input. The default - * is zero delay.
+ * Set this to a value representing the millisecond count in between generated events. Usually set + * to 100-200 if you want to slow down the playback to simulate actual user input. The default is + * zero delay.
*
abbot.robot.mode

- * Set this to either "robot" or "awt" to designate the desired mode of event generation. "robot" - * uses java.awt.Robot to generate events, while "awt" stuffs events directly into the AWT event - * queue.
+ * Set this to either "robot" or "awt" to designate the desired mode of event generation. The + * "robot" uses java.awt.Robot to generate events, while "awt" stuffs events directly into the AWT + * event queue.
*
abbot.robot.event_post_delay

* This is the maximum number of ms it takes the system to post an AWT event in response to a * Robot-generated event. *
abbot.robot.default_delay

- * Base delay setting, acts as default value for the next two. + * Base delay setting acts as a default value for the next two. *
abbot.robot.popup_delay

* Set this to the maximum time to wait for a menu to appear or be generated. *
abbot.robot.component_delay

* Set this to the maximum time to wait for a Component to become available. *

* NOTE: Only use event queue synchronization (e.g. {@link #invokeAndWait(Runnable)} or - * {@link #waitForIdle()} when a subsequent robot-level action is being applied to the results of a - * prior action (e.g. focus, deiconify, menu selection). Otherwise, don't introduce a mandatory - * delay (e.g. use {@link #invokeLater(Runnable)}). + * {@link #waitForIdle()} when a later robot-level action is being applied to the results of a prior + * action (e.g., focus, deiconify, menu selection). Otherwise, don't introduce a mandatory delay + * (e.g., use {@link #invokeLater(Runnable)}). *

* NOTE: If a robot action isn't reproduced properly, you may need to introduce either additional * events or extra delay. Adding enforced delay for a given platform is usually preferable to @@ -108,14 +108,10 @@ public class Robot implements AWTConstants { public static int EM_AWT = 1; private static final Toolkit toolkit = Toolkit.getDefaultToolkit(); - // Max robot delay, in ms private static final int MAX_DELAY = 60000; - // TODO: verify this value for X11, etc.; ALT for w32, option for OSX - public static final int MOUSELESS_MODIFIER_MASK = InputEvent.ALT_DOWN_MASK; - public static final String MOUSELESS_MODIFIER = AWT.getKeyModifiers(MOUSELESS_MODIFIER_MASK); - protected static final boolean useScreenMenuBar() { - // Ideally we'd install a menu and check where it ended up, since the + protected static boolean useScreenMenuBar() { + // Ideally, we'd install a menu and check where it ended up, since the // property is read once at startup and ignored thereafter. return Platform.isOSX() && (Boolean.getBoolean("com.apple.macos.useScreenMenuBar") @@ -135,8 +131,7 @@ protected static final boolean useScreenMenuBar() { private static int eventPostDelay = Properties.getProperty("abbot.robot.event_post_delay", 100, 0, 1000); - protected static long IDLE_TIMEOUT = - Integer.getInteger("abbot.robot.idle_timeout", 10000).intValue(); + protected static long IDLE_TIMEOUT = Integer.getInteger("abbot.robot.idle_timeout", 10000); /** * Delay before failing to find a popup menu that should appear. @@ -284,8 +279,6 @@ public void mouseMove(int x, int y) { if (eventMode == EM_ROBOT) { Log.debug("ROBOT: Mouse move: (" + x + "," + y + ")"); robot.mouseMove(x, y); - } else { - // Can't stuff an AWT event for an arbitrary location } } @@ -306,7 +299,7 @@ public void mousePress(int buttons) { } public void mouseRelease() { - mouseRelease(MouseEvent.BUTTON1_MASK); + mouseRelease(MouseEvent.BUTTON1_DOWN_MASK); } public void mouseRelease(int buttons) { @@ -342,7 +335,7 @@ public void focus(Component comp) { /** * Use an explicit listener, since hasFocus is not always reliable. */ - private class FocusWatcher extends FocusAdapter { + private static class FocusWatcher extends FocusAdapter { public volatile boolean focused = false; @@ -382,14 +375,7 @@ public void focus(Component comp, boolean wait) { // NOTE: while it would be nice to have a robot method instead of // requesting focus, clicking to change focus may have // side effects - invokeAndWait( - comp, - new Runnable() { - @Override - public void run() { - comp.requestFocus(); - } - }); + invokeAndWait(comp, comp::requestFocus); try { if (wait) { long start = System.currentTimeMillis(); @@ -462,7 +448,7 @@ private void keyPress(int keycode, char keyChar) { mods |= AWT.keyCodeToMask(keycode); } postKeyEvent(KeyEvent.KEY_PRESSED, mods, keycode, KeyEvent.CHAR_UNDEFINED); - // Auto-generate KEY_TYPED events, as best we can + // Auto-generate KEY_TYPED events as best we can int mask = state.getModifiers(); if (keyChar == KeyEvent.CHAR_UNDEFINED) { KeyStroke ks = KeyStroke.getKeyStroke(keycode, mask); @@ -521,15 +507,12 @@ public void delay(int ms) { try { Thread.sleep(ms); } catch (InterruptedException ie) { + // ignore } } } - private static final Runnable EMPTY_RUNNABLE = - new Runnable() { - @Override - public void run() {} - }; + private static final Runnable EMPTY_RUNNABLE = () -> {}; /** * Check for a blocked event queue (symptomatic of an active w32 AWT popup menu). @@ -537,14 +520,13 @@ public void run() {} * @return whether the event queue is blocked. */ protected boolean queueBlocked() { - return postInvocationEvent(toolkit.getSystemEventQueue(), toolkit, 200); + return postInvocationEvent(toolkit.getSystemEventQueue(), 200); } - protected boolean postInvocationEvent(EventQueue eq, Toolkit toolkit, long timeout) { - class RobotIdleLock {} + protected boolean postInvocationEvent(EventQueue eq, long timeout) { Object lock = new RobotIdleLock(); synchronized (lock) { - eq.postEvent(new InvocationEvent(toolkit, EMPTY_RUNNABLE, lock, true)); + eq.postEvent(new InvocationEvent(Robot.toolkit, EMPTY_RUNNABLE, lock, true)); long start = System.currentTimeMillis(); try { // NOTE: on fast linux systems when showing a dialog, if we @@ -566,7 +548,7 @@ private void waitForIdle(EventQueue eq) { // NOTE: as of Java 1.3.1, robot.waitForIdle only waits for the // last event on the queue at the time of this invocation to be - // processed. We need better than that. Make sure the given event + // processed. We need it better than that. Make sure the given event // queue is empty when this method returns // We always post at least one idle event to allow any current event @@ -574,7 +556,7 @@ private void waitForIdle(EventQueue eq) { long start = System.currentTimeMillis(); int count = 0; do { - if (postInvocationEvent(eq, toolkit, IDLE_TIMEOUT)) { + if (postInvocationEvent(eq, IDLE_TIMEOUT)) { Log.warn( "Timed out waiting for posted invocation event: " + IDLE_TIMEOUT @@ -591,7 +573,7 @@ private void waitForIdle(EventQueue eq) { ++count; - // NOTE: this does not detect invocation events (i.e. what + // NOTE: this does not detect invocation events (e.g., what // gets posted with EventQueue.invokeLater), so if someone // is repeatedly posting one, we might get stuck. Not too // worried, since if a Runnable keeps calling invokeLater @@ -686,7 +668,7 @@ public void mouseMove(Component comp) { * Wait the given number of ms for the component to be showing and ready. Returns false if the * operation times out. */ - private boolean waitForComponent(Component c, long delay) { + private boolean notWaitForComponent(Component c, long delay) { if (!isReadyForInput(c)) { Log.debug("Waiting for component to show"); long start = System.currentTimeMillis(); @@ -713,12 +695,12 @@ private boolean waitForComponent(Component c, long delay) { + c.isShowing() + " win ready=" + tracker.isWindowReady(AWT.getWindow(c))); - return false; + return true; } sleep(); } } - return true; + return false; } /** @@ -736,7 +718,7 @@ private Component retargetMouseEvent(Component comp, int id, Point pt) { } public void mouseMove(Component comp, int x, int y) { - if (!waitForComponent(comp, componentDelay)) { + if (notWaitForComponent(comp, componentDelay)) { String msg = "Can't obtain position of component " + toString(comp); throw new ComponentNotShowingException(msg); } @@ -841,7 +823,7 @@ public void drag(Component src, int sx, int sy, int buttons) { } public void drop(Component target, int x, int y) { - // Delay between final move and drop to ensure drop ends. + // Delay between final move and drop to ensure the drop ends. int DROP_DELAY = Properties.getProperty("abbot.robot.drop_delay", Platform.isWindows() ? 200 : 0, 0, 60000); @@ -1003,8 +985,8 @@ public void keyStroke(char ch) { public void keyString(String str) { char[] ch = str.toCharArray(); - for (int i = 0; i < ch.length; i++) { - keyStroke(ch[i]); + for (char c : ch) { + keyStroke(c); } } @@ -1195,14 +1177,7 @@ public void selectAWTPopupMenuItem(Component invoker, String path) { protected void fireAccessibleAction(Component context, AccessibleAction action, String name) { if (action != null && action.getAccessibleActionCount() > 0) { - invokeLater( - context, - new Runnable() { - @Override - public void run() { - action.doAccessibleAction(0); - } - }); + invokeLater(context, () -> action.doAccessibleAction(0)); } else { String msg = Strings.get("tester.Robot.no_accessible_action", new String[] {name}); throw new ActionFailedException(msg); @@ -1282,7 +1257,7 @@ public void selectMenuItem(Component item) { return; } - // If our parent is a menu, activate it first, if it's not already. + // If our parent is a menu, activate it first if it's not already. if (parent instanceof javax.swing.JMenuItem) { if (parentPopup == null || !parentPopup.isShowing()) { Log.debug("Opening parent menu " + toString(parent)); @@ -1296,14 +1271,7 @@ public void selectMenuItem(Component item) { if (win != null) { // Make sure the window is in front, or its menus may be // obscured by another window. - invokeAndWait( - win, - new Runnable() { - @Override - public void run() { - win.toFront(); - } - }); + invokeAndWait(win, win::toFront); mouseMove(win); } } @@ -1325,14 +1293,14 @@ public void run() { // return if (isMenu) { JPopupMenu popup = ((javax.swing.JMenu) item).getPopupMenu(); - if (!waitForComponent(popup, popupDelay)) { + if (notWaitForComponent(popup, popupDelay)) { String msg = "Clicking on '" + ((javax.swing.JMenu) item).getText() + "' never produced a popup menu"; throw new ComponentMissingException(msg); } - // for OSX 1.4.1; isShowing set before popup is available + // for OSX 1.4.1; isShowing set before the popup is available if (subMenuDelay > autoDelay) { delay(subMenuDelay - autoDelay); } @@ -1392,15 +1360,8 @@ public Component showPopupMenu(Component invoker, int x, int y) { } public void activate(Window win) { - // ACTIVATE means window gets keyboard focus. - invokeAndWait( - win, - new Runnable() { - @Override - public void run() { - win.toFront(); - } - }); + // ACTIVATE means a window gets keyboard focus. + invokeAndWait(win, win::toFront); // For pointer-focus systems mouseMove(win); } @@ -1425,13 +1386,6 @@ public void close(Window w) { } catch (Exception e) { // ignore } - WindowEvent ev = new WindowEvent(w, WindowEvent.WINDOW_CLOSING); - // If the window contains an applet, send the event on the - // applet's queue instead to ensure a shutdown from the - // applet's context (assists AppletViewer cleanup). - Component applet = AWT.findAppletDescendent(w); - EventQueue eq = tracker.getQueue(applet != null ? applet : w); - eq.postEvent(ev); } } @@ -1454,14 +1408,7 @@ public void moveBy(Container comp, int dx, int dy) { mouseMove(comp, p.x, p.y); mouseMove(comp, p.x + dx, p.y + dy); } - invokeAndWait( - comp, - new Runnable() { - @Override - public void run() { - comp.setLocation(new Point(loc.x + dx, loc.y + dy)); - } - }); + invokeAndWait(comp, () -> comp.setLocation(new Point(loc.x + dx, loc.y + dy))); if (userMovable) { Point p = getMoveLocation(comp); mouseMove(comp, p.x, p.y); @@ -1502,14 +1449,7 @@ public void resizeBy(Container comp, int dx, int dy) { mouseMove(comp, p.x, p.y); mouseMove(comp, p.x + dx, p.y + dy); } - invokeAndWait( - comp, - new Runnable() { - @Override - public void run() { - comp.setSize(comp.getWidth() + dx, comp.getHeight() + dy); - } - }); + invokeAndWait(comp, () -> comp.setSize(comp.getWidth() + dx, comp.getHeight() + dy)); if (userResizable) { Point p = getResizeLocation(comp); mouseMove(comp, p.x, p.y); @@ -1520,7 +1460,7 @@ protected Point getIconifyLocation(Container c) { Dimension size = c.getSize(); Insets insets = c.getInsets(); // We know the exact layout of the window manager frames for w32 and - // OSX. Currently no way of detecting the WM under X11. Maybe we + // OSX. Currently, no way of detecting the WM under X11. Maybe we // could send a WM message (WM_ICONIFY)? Point loc = new Point(); loc.y = insets.top / 2; @@ -1547,14 +1487,7 @@ public void iconify(Frame frame) { if (loc != null) { mouseMove(frame, loc.x, loc.y); } - invokeLater( - frame, - new Runnable() { - @Override - public void run() { - frame.setState(Frame.ICONIFIED); - } - }); + invokeLater(frame, () -> frame.setState(Frame.ICONIFIED)); } public void deiconify(Frame frame) { @@ -1564,13 +1497,10 @@ public void deiconify(Frame frame) { public void normalize(Frame frame) { invokeLater( frame, - new Runnable() { - @Override - public void run() { - frame.setState(Frame.NORMAL); - if (Bugs.hasFrameDeiconifyBug()) { - frame.setVisible(true); - } + () -> { + frame.setState(Frame.NORMAL); + if (Bugs.hasFrameDeiconifyBug()) { + frame.setVisible(true); } }); } @@ -1582,31 +1512,25 @@ public void maximize(Frame frame) { } invokeLater( frame, - new Runnable() { - @Override - public void run() { - // If the maximize is unavailable, set to full screen size - // instead. - try { - final int MAXIMIZED_BOTH = 6; - Boolean b = - (Boolean) - Toolkit.class - .getMethod("isFrameStateSupported", int.class) - .invoke(toolkit, new Object[] {new Integer(MAXIMIZED_BOTH)}); - if (b.booleanValue() && !serviceMode) { - Frame.class - .getMethod("setExtendedState", int.class) - .invoke(frame, new Integer(MAXIMIZED_BOTH)); - } else { - throw new RuntimeException("Platform won't maximize"); - } - } catch (Exception e) { - Log.debug("Maximize not supported: " + e); - Rectangle rect = frame.getGraphicsConfiguration().getBounds(); - frame.setLocation(rect.x, rect.y); - frame.setSize(rect.width, rect.height); + () -> { + // If maximize is unavailable, set to full-screen size instead. + try { + final int MAXIMIZED_BOTH = 6; + Boolean b = + (Boolean) + Toolkit.class + .getMethod("isFrameStateSupported", int.class) + .invoke(toolkit, new Object[] {MAXIMIZED_BOTH}); + if (b && !serviceMode) { + Frame.class.getMethod("setExtendedState", int.class).invoke(frame, MAXIMIZED_BOTH); + } else { + throw new RuntimeException("Platform won't maximize"); } + } catch (Exception e) { + Log.debug("Maximize not supported: " + e); + Rectangle rect = frame.getGraphicsConfiguration().getBounds(); + frame.setLocation(rect.x, rect.y); + frame.setSize(rect.width, rect.height); } }); } @@ -1653,79 +1577,47 @@ public void sendEvent(AWTEvent event) { public static String getEventID(AWTEvent event) { // Optimize here to avoid field name lookup overhead - switch (event.getID()) { - case MouseEvent.MOUSE_MOVED: - return "MOUSE_MOVED"; - case MouseEvent.MOUSE_DRAGGED: - return "MOUSE_DRAGGED"; - case MouseEvent.MOUSE_PRESSED: - return "MOUSE_PRESSED"; - case MouseEvent.MOUSE_CLICKED: - return "MOUSE_CLICKED"; - case MouseEvent.MOUSE_RELEASED: - return "MOUSE_RELEASED"; - case MouseEvent.MOUSE_ENTERED: - return "MOUSE_ENTERED"; - case MouseEvent.MOUSE_EXITED: - return "MOUSE_EXITED"; - case KeyEvent.KEY_PRESSED: - return "KEY_PRESSED"; - case KeyEvent.KEY_TYPED: - return "KEY_TYPED"; - case KeyEvent.KEY_RELEASED: - return "KEY_RELEASED"; - case WindowEvent.WINDOW_OPENED: - return "WINDOW_OPENED"; - case WindowEvent.WINDOW_CLOSING: - return "WINDOW_CLOSING"; - case WindowEvent.WINDOW_CLOSED: - return "WINDOW_CLOSED"; - case WindowEvent.WINDOW_ICONIFIED: - return "WINDOW_ICONIFIED"; - case WindowEvent.WINDOW_DEICONIFIED: - return "WINDOW_DEICONIFIED"; - case WindowEvent.WINDOW_ACTIVATED: - return "WINDOW_ACTIVATED"; - case WindowEvent.WINDOW_DEACTIVATED: - return "WINDOW_DEACTIVATED"; - case ComponentEvent.COMPONENT_MOVED: - return "COMPONENT_MOVED"; - case ComponentEvent.COMPONENT_RESIZED: - return "COMPONENT_RESIZED"; - case ComponentEvent.COMPONENT_SHOWN: - return "COMPONENT_SHOWN"; - case ComponentEvent.COMPONENT_HIDDEN: - return "COMPONENT_HIDDEN"; - case FocusEvent.FOCUS_GAINED: - return "FOCUS_GAINED"; - case FocusEvent.FOCUS_LOST: - return "FOCUS_LOST"; - case HierarchyEvent.HIERARCHY_CHANGED: - return "HIERARCHY_CHANGED"; - case HierarchyEvent.ANCESTOR_MOVED: - return "ANCESTOR_MOVED"; - case HierarchyEvent.ANCESTOR_RESIZED: - return "ANCESTOR_RESIZED"; - case PaintEvent.PAINT: - return "PAINT"; - case PaintEvent.UPDATE: - return "UPDATE"; - case ActionEvent.ACTION_PERFORMED: - return "ACTION_PERFORMED"; - case InputMethodEvent.CARET_POSITION_CHANGED: - return "CARET_POSITION_CHANGED"; - case InputMethodEvent.INPUT_METHOD_TEXT_CHANGED: - return "INPUT_METHOD_TEXT_CHANGED"; - default: - return Reflector.getFieldName(event.getClass(), event.getID(), ""); - } + return switch (event.getID()) { + case MouseEvent.MOUSE_MOVED -> "MOUSE_MOVED"; + case MouseEvent.MOUSE_DRAGGED -> "MOUSE_DRAGGED"; + case MouseEvent.MOUSE_PRESSED -> "MOUSE_PRESSED"; + case MouseEvent.MOUSE_CLICKED -> "MOUSE_CLICKED"; + case MouseEvent.MOUSE_RELEASED -> "MOUSE_RELEASED"; + case MouseEvent.MOUSE_ENTERED -> "MOUSE_ENTERED"; + case MouseEvent.MOUSE_EXITED -> "MOUSE_EXITED"; + case KeyEvent.KEY_PRESSED -> "KEY_PRESSED"; + case KeyEvent.KEY_TYPED -> "KEY_TYPED"; + case KeyEvent.KEY_RELEASED -> "KEY_RELEASED"; + case WindowEvent.WINDOW_OPENED -> "WINDOW_OPENED"; + case WindowEvent.WINDOW_CLOSING -> "WINDOW_CLOSING"; + case WindowEvent.WINDOW_CLOSED -> "WINDOW_CLOSED"; + case WindowEvent.WINDOW_ICONIFIED -> "WINDOW_ICONIFIED"; + case WindowEvent.WINDOW_DEICONIFIED -> "WINDOW_DEICONIFIED"; + case WindowEvent.WINDOW_ACTIVATED -> "WINDOW_ACTIVATED"; + case WindowEvent.WINDOW_DEACTIVATED -> "WINDOW_DEACTIVATED"; + case ComponentEvent.COMPONENT_MOVED -> "COMPONENT_MOVED"; + case ComponentEvent.COMPONENT_RESIZED -> "COMPONENT_RESIZED"; + case ComponentEvent.COMPONENT_SHOWN -> "COMPONENT_SHOWN"; + case ComponentEvent.COMPONENT_HIDDEN -> "COMPONENT_HIDDEN"; + case FocusEvent.FOCUS_GAINED -> "FOCUS_GAINED"; + case FocusEvent.FOCUS_LOST -> "FOCUS_LOST"; + case HierarchyEvent.HIERARCHY_CHANGED -> "HIERARCHY_CHANGED"; + case HierarchyEvent.ANCESTOR_MOVED -> "ANCESTOR_MOVED"; + case HierarchyEvent.ANCESTOR_RESIZED -> "ANCESTOR_RESIZED"; + case PaintEvent.PAINT -> "PAINT"; + case PaintEvent.UPDATE -> "UPDATE"; + case ActionEvent.ACTION_PERFORMED -> "ACTION_PERFORMED"; + case InputMethodEvent.CARET_POSITION_CHANGED -> "CARET_POSITION_CHANGED"; + case InputMethodEvent.INPUT_METHOD_TEXT_CHANGED -> "INPUT_METHOD_TEXT_CHANGED"; + default -> Reflector.getFieldName(event.getClass(), event.getID(), ""); + }; } public static Class getCanonicalClass(Class refClass) { // Don't use classnames from anonymous inner classes... // Don't use classnames from platform LAF classes... String className = refClass.getName(); - while (className.indexOf("$") != -1 + while (className.contains("$") || className.startsWith("javax.swing.plaf") || className.startsWith("com.apple.mrj")) { refClass = refClass.getSuperclass(); @@ -1785,12 +1677,10 @@ public static String toString(Object obj) { } protected static String descriptiveClassName(Class cls) { - StringBuffer desc = new StringBuffer(simpleClassName(cls)); - Class coreClass = getCanonicalClass(cls); + StringBuilder desc = new StringBuilder(simpleClassName(cls)); + Class coreClass = getCanonicalClass(cls); String coreClassName = coreClass.getName(); - while (!coreClassName.startsWith("java.awt.") - && !coreClassName.startsWith("javax.swing.") - && !coreClassName.startsWith("java.applet.")) { + while (!coreClassName.startsWith("java.awt.") && !coreClassName.startsWith("javax.swing.")) { coreClass = coreClass.getSuperclass(); coreClassName = coreClass.getName(); } @@ -1802,7 +1692,7 @@ protected static String descriptiveClassName(Class cls) { } public static String toHierarchyPath(Component c) { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); Container parent = c.getParent(); if (parent != null) { buf.append(toHierarchyPath(parent)); @@ -1898,18 +1788,7 @@ public static String getDescriptiveName(Component c) { } private static String getName(Component c) { - String name = AWT.hasDefaultName(c) ? null : c.getName(); - // Accessibility behaves like what we used to do with getTag. - // Not too helpful for our purposes, especially when the - // data on which the name is based might be dynamic. - /* - if (name == null) { - AccessibleContext context = c.getAccessibleContext(); - if (context != null) - name = context.getAccessibleName(); - } - */ - return name; + return AWT.hasDefaultName(c) ? null : c.getName(); } private static String getTitle(Component c) { @@ -1999,8 +1878,8 @@ protected void postEvent(Component comp, AWTEvent ev) { if (eventMode == EM_AWT && AWT.isAWTPopupMenuBlocking()) { throw new Error("Event queue is blocked by an active AWT PopupMenu"); } - // Force an update of the input state, so that we're in synch - // internally. Otherwise we might post more events before this + // Force an update of the input state so that we're in synch + // internally. Otherwise, we might post more events before this // one gets processed and end up using stale values for those events. state.update(ev); EventQueue q = getEventQueue(comp); @@ -2109,4 +1988,6 @@ public static int getPreferredRobotAutoDelay() { } return 50; } + + private static class RobotIdleLock {} } diff --git a/abbot/src/main/java/abbot/tester/WindowTracker.java b/abbot/src/main/java/abbot/tester/WindowTracker.java index cf394f4..869cade 100644 --- a/abbot/src/main/java/abbot/tester/WindowTracker.java +++ b/abbot/src/main/java/abbot/tester/WindowTracker.java @@ -37,6 +37,7 @@ public class WindowTracker { private static class Holder { + public static final WindowTracker INSTANCE = new WindowTracker(); } @@ -83,8 +84,8 @@ public static WindowTracker getTracker() { } /** - * Create an instance of WindowTracker which will track all windows coming and going on the current and subsequent - * app contexts. WARNING: if an applet loads this class, it will only ever see stuff in its own app context. + * Create an instance of WindowTracker which will track all windows coming and going on the + * current and later app contexts. */ WindowTracker() { ContextTracker contextTracker = new ContextTracker(); @@ -110,6 +111,7 @@ public static WindowTracker getTracker() { try { robot = new java.awt.Robot(); } catch (AWTException e) { + // ignore } windowReadyTimer = new NamedTimer("Window Ready Timer", true); } @@ -129,9 +131,9 @@ private void scanExistingWindows(Window w) { } /** - * Returns whether the window is ready to receive OS-level event input. A window's "isShowing" flag may be set true - * before the WINDOW_OPENED event is generated, and even after the WINDOW_OPENED is sent the window peer is not - * guaranteed to be ready. + * Returns whether the window is ready to receive OS-level event input. A window's "isShowing" + * flag may be set true before the WINDOW_OPENED event is generated, and even after the + * WINDOW_OPENED is sent the window peer is not guaranteed to be ready. */ public boolean isWindowReady(Window w) { synchronized (openWindows) { @@ -146,14 +148,13 @@ public boolean isWindowReady(Window w) { } /** - * Return the event queue corresponding to the given component. In most cases, this is the same as - * Component.getToolkit().getSystemEventQueue(), but in the case of applets will bypass the AppContext and provide - * the real event queue. + * Return the event queue corresponding to the given component. In most cases, this is the same as + * Component.getToolkit().getSystemEventQueue(). */ public EventQueue getQueue(Component c) { - // Components above the applet in the hierarchy may or may not share - // the same context with the applet itself. - while (!(c instanceof java.applet.Applet) && c.getParent() != null) c = c.getParent(); + while (c.getParent() != null) { + c = c.getParent(); + } synchronized (contexts) { WeakReference ref = queues.get(c); EventQueue q = ref != null ? ref.get() : null; @@ -168,9 +169,9 @@ public EventQueue getQueue(Component c) { * Returns all known event queues. */ public Collection getEventQueues() { - HashSet set = new HashSet<>(); + HashSet set; synchronized (contexts) { - set.addAll(contexts.keySet()); + set = new HashSet<>(contexts.keySet()); for (WeakReference ref : queues.values()) { EventQueue q = ref.get(); if (q != null) { @@ -182,8 +183,8 @@ public Collection getEventQueues() { } /** - * Return all available root Windows. A root Window is one that has a null parent. Nominally this means a list - * similar to that returned by Frame.getFrames(), but in the case of an Applet may return a few Dialogs as well. + * Return all available root Windows. A root Window is one that has a null parent. Nominally + * this means a list similar to that returned by Frame.getFrames(). */ public Collection getRootWindows() { Set set = new HashSet<>(); @@ -197,15 +198,15 @@ public Collection getRootWindows() { } Frame[] frames = Frame.getFrames(); Collections.addAll(set, frames); - // Log.debug(String.valueOf(list.size()) + " total Frames"); return set; } /** - * Provides tracking of window visibility state. We explicitly add this on WINDOW_OPEN and remove it on - * WINDOW_CLOSE to avoid having to process extraneous ComponentEvents. + * Provides tracking of window visibility state. We explicitly add this on WINDOW_OPEN and remove + * it on WINDOW_CLOSE to avoid having to process extraneous ComponentEvents. */ private class WindowWatcher extends WindowAdapter implements ComponentListener { + public WindowWatcher(Window w) { w.addComponentListener(this); w.addWindowListener(this); @@ -217,7 +218,6 @@ public void componentShown(ComponentEvent e) { public void componentHidden(ComponentEvent e) { synchronized (openWindows) { - // Log.log("Marking " + e.getSource() + " hidden"); hiddenWindows.put((Window) e.getSource(), Boolean.TRUE); pendingWindows.remove(e.getSource()); } @@ -235,23 +235,16 @@ public void componentMoved(ComponentEvent e) {} } /** - * Whenever we get a window that's on a new event dispatch thread, take note of the thread, since it may correspond - * to a new event queue and AppContext. + * Whenever we get a window on a new event dispatch thread, take note of the thread, since it may + * correspond to a new event queue and AppContext. */ // FIXME what if it has the same app context? can we check? private class ContextTracker implements AWTEventListener { - public void eventDispatched(AWTEvent ev) { + public void eventDispatched(AWTEvent ev) { ComponentEvent event = (ComponentEvent) ev; Component comp = event.getComponent(); - // This is our sole means of accessing other app contexts - // (if running within an applet). We look for window events - // beyond OPENED in order to catch windows that have already - // opened by the time we start listening but which are not - // in the Frame.getFrames list (i.e. they are on a different - // context). Specifically watch for COMPONENT_SHOWN on applets, - // since we may not get frame events for them. - if (!(comp instanceof java.applet.Applet) && !(comp instanceof Window)) { + if (!(comp instanceof Window)) { return; } @@ -272,8 +265,7 @@ else if ((id >= WindowEvent.WINDOW_FIRST && id <= WindowEvent.WINDOW_LAST) } } } - // The context for root-level windows may change between - // WINDOW_OPENED and subsequent events. + synchronized (contexts) { WeakReference ref = queues.get(comp); if (ref != null && !comp.getToolkit().getSystemEventQueue().equals(ref.get())) { @@ -284,6 +276,7 @@ else if ((id >= WindowEvent.WINDOW_FIRST && id <= WindowEvent.WINDOW_LAST) } private class WindowReadyTracker implements AWTEventListener { + public void eventDispatched(AWTEvent e) { if (e.getID() == MouseEvent.MOUSE_MOVED || e.getID() == MouseEvent.MOUSE_DRAGGED) { Component c = (Component) e.getSource(); @@ -296,11 +289,7 @@ public void eventDispatched(AWTEvent e) { private void noteContext(Component comp) { EventQueue queue = comp.getToolkit().getSystemEventQueue(); synchronized (contexts) { - Map map = contexts.get(queue); - if (map == null) { - map = new WeakHashMap<>(); - contexts.put(queue, map); - } + Map map = contexts.computeIfAbsent(queue, k -> new WeakHashMap<>()); if (comp instanceof Window && comp.getParent() == null) { map.put(comp, Boolean.TRUE); } @@ -309,7 +298,6 @@ private void noteContext(Component comp) { } private void noteOpened(Component comp) { - // Log.log("Noting " + comp + " opened"); noteContext(comp); // Attempt to ensure the window is ready for input before recognizing // it as "open". There is no Java API for this, so we institute an @@ -317,7 +305,7 @@ private void noteOpened(Component comp) { if (comp instanceof Window) { new WindowWatcher((Window) comp); markWindowShowing((Window) comp); - // Native components don't receive events anyway... + // Native components don't receive events anyway if (comp instanceof FileDialog) { markWindowReady((Window) comp); } @@ -362,7 +350,6 @@ private void noteClosed(Component comp) { } } synchronized (openWindows) { - // Log.log("Marking " + comp + " closed"); openWindows.remove(comp); hiddenWindows.remove(comp); closedWindows.put(comp, Boolean.TRUE); @@ -371,7 +358,8 @@ private void noteClosed(Component comp) { } /** - * Mark the given Window as ready for input. Indicate whether any pending "mark ready" task should be canceled. + * Mark the given Window as ready for input. Indicate whether any pending "mark ready" task + * should be canceled. */ private void markWindowReady(Window w) { synchronized (openWindows) { @@ -380,7 +368,6 @@ private void markWindowReady(Window w) { // Make sure it's still on the pending list before we actually // mark it ready. if (pendingWindows.containsKey(w)) { - // Log.log("Noting " + w + " ready"); closedWindows.remove(w); hiddenWindows.remove(w); openWindows.put(w, Boolean.TRUE); @@ -390,7 +377,8 @@ private void markWindowReady(Window w) { } /** - * Indicate a window has set isShowing true and needs to be marked ready when it is actually ready. + * Indicate a window has set isShowing true and needs to be marked ready when it is actually + * ready. */ private void markWindowShowing(final Window w) { synchronized (openWindows) { @@ -405,7 +393,7 @@ private Insets getInsets(Container c) { return insets; } } catch (NullPointerException e) { - // FileDialog.getInsets() throws (1.4.2_07) + // ignore } return new Insets(0, 0, 0, 0); } @@ -415,7 +403,6 @@ private Insets getInsets(Container c) { /** * Actively check whether the given window is ready for input. * - * @param robot * @see #isWindowReady */ private void checkWindow(final Window w, java.awt.Robot robot) { @@ -439,12 +426,10 @@ private void checkWindow(final Window w, java.awt.Robot robot) { if (pendingWindows.get(w) == Boolean.TRUE && isEmptyFrame(w)) { // Force the frame to be large enough to receive events SwingUtilities.invokeLater( - new Runnable() { - public void run() { - int nw = Math.max(width, insets.left + insets.right + 3); - int nh = Math.max(height, insets.top + insets.bottom + 3); - w.setSize(nw, nh); - } + () -> { + int nw = Math.max(width, insets.left + insets.right + 3); + int nh = Math.max(height, insets.top + insets.bottom + 3); + w.setSize(nw, nh); }); } // At worst, time out and say the window is ready diff --git a/abbot/src/main/java/abbot/util/AWT.java b/abbot/src/main/java/abbot/util/AWT.java index dfe7657..996d0e7 100644 --- a/abbot/src/main/java/abbot/util/AWT.java +++ b/abbot/src/main/java/abbot/util/AWT.java @@ -13,7 +13,6 @@ import abbot.tester.AWTConstants; import abbot.tester.Robot; import com.windowtester.runtime.util.StringComparator; -import java.applet.Applet; import java.awt.AWTEvent; import java.awt.Button; import java.awt.Canvas; @@ -84,6 +83,7 @@ public class AWT { try { POPUP_TIMEOUT = Integer.parseInt(to); } catch (Exception e) { + // ignore } } } @@ -123,8 +123,8 @@ public static boolean hasDefaultName(Component c) { } /** - * Ensure the given action happens on the event dispatch thread. Any component modifications must be invoked this - * way. + * Ensure the given action happens on the event dispatch thread. Any component modifications must + * be invoked this way. */ public static void invokeAndWait(Runnable action) { if (EventQueue.isDispatchThread()) { @@ -139,8 +139,8 @@ public static void invokeAndWait(Runnable action) { } /** - * Ensure the given action happens on the event dispatch thread. Any component modifications must be invoked this - * way. Note that this is + * Ensure the given action happens on the event dispatch thread. Any component modifications must + * be invoked this way. Note that this is * not the same as EventQueue.invokeLater, since if the current * thread is the dispatch thread, the action is invoked immediately. */ @@ -181,8 +181,7 @@ private static List disable(Object root, List list) { } } else if (root instanceof MenuItem) { if (((MenuItem) root).isEnabled()) { - if (root instanceof Menu) { - Menu menu = (Menu) root; + if (root instanceof Menu menu) { for (int i = 0; i < menu.getItemCount(); i++) { disable(menu.getItem(i), list); } @@ -197,33 +196,26 @@ private static List disable(Object root, List list) { /** * Restore the enabled state. */ - public static void reenableHierarchy(final List enabled) { + public static void reEnableHierarchy(final List enabled) { invokeAndWait( - new Runnable() { - public void run() { - for (Object o : enabled) { - if (o instanceof Component) { - ((Component) o).setEnabled(true); - } else if (o instanceof MenuItem) { - ((MenuItem) o).setEnabled(true); - } + () -> { + for (Object o : enabled) { + if (o instanceof Component) { + ((Component) o).setEnabled(true); + } else if (o instanceof MenuItem) { + ((MenuItem) o).setEnabled(true); } } }); } /** - * Disable a component hierarchy starting at the given component. Returns a list of all components which used to be - * enabled, for use with reenableHierarchy. + * Disable a component hierarchy starting at the given component. Returns a list of all components + * that used to be enabled, for use with {@link #reEnableHierarchy(List). */ public static List disableHierarchy(final Component root) { final List list = new ArrayList<>(); - invokeAndWait( - new Runnable() { - public void run() { - disable(root, list); - } - }); + invokeAndWait(() -> disable(root, list)); return list; } @@ -238,8 +230,8 @@ public static boolean isOnMenuBar(MenuComponent mc) { } /** - * Returns the invoker, if any, of the given AWT menu component. Returns null if the menu component is not attached - * to anything, or if it is within a MenuBar hierarchy. + * Returns the invoker, if any, of the given AWT menu component. Returns null if the menu + * component is not attached to anything or if it is within a MenuBar hierarchy. */ public static Component getInvoker(MenuComponent mc) { if (isOnMenuBar(mc)) { @@ -253,34 +245,42 @@ public static Component getInvoker(MenuComponent mc) { } /** - * Returns the invoker, if any, of the given component. Returns null if the component is not on a popup of any - * sort. + * Returns the invoker, if any, of the given component. Returns null if the component is not on a + * popup of any sort. */ public static Component getInvoker(Component comp) { if (comp instanceof JPopupMenu) { return ((JPopupMenu) comp).getInvoker(); } + comp = comp.getParent(); - return comp != null ? getInvoker(comp) : null; + if (comp != null) { + return getInvoker(comp); + } + return null; } /** - * Similar to SwingUtilities.getWindowAncestor(), but returns the component itself if it is a Window, or the - * invoker's window if on a popup. + * Similar to SwingUtilities.getWindowAncestor(), but returns the component itself if it is a + * Window, or the invoker's window if on a popup. */ public static Window getWindow(Component comp) { - if (comp == null) { - return null; - } - if (comp instanceof Window) { - return (Window) comp; - } - if (comp instanceof MenuElement) { - Component invoker = getInvoker(comp); - if (invoker != null) { - return getWindow(invoker); + switch (comp) { + case null -> { + return null; + } + case Window window -> { + return window; + } + case MenuElement menuElement -> { + Component invoker = getInvoker(comp); + if (invoker != null) { + return getWindow(invoker); + } } + default -> {} } + return getWindow(hierarchy.getParent(comp)); } @@ -319,12 +319,12 @@ public static boolean isAWTTreeLockHeld(EventQueue eq) { // If it can't get the tree lock, then there is a popup active in the // current tree. // Any component can provide the tree lock - ThreadStateChecker checker = new ThreadStateChecker(frames[0].getTreeLock()); + ThreadStateChecker checker = new ThreadStateChecker(); try { synchronized (checker) { checker.start(); if (!checker.started) { - // avoid failure under heavy load + // avoid failure under heavy-load checker.wait(30000); if (!checker.started) { throw new Error("Popup checking thread never started"); @@ -358,7 +358,8 @@ public static void dismissAWTPopup() { } /** - * Returns whether the given MenuComponent is on a top-level AWT popup (that is, not under a MenuBar. + * Returns whether the given MenuComponent is on a top-level AWT popup that is, not under + * a MenuBar. */ public static boolean isOnPopup(MenuComponent mc) { MenuContainer parent = mc.getParent(); @@ -372,8 +373,8 @@ public static boolean isOnPopup(MenuComponent mc) { } /** - * Returns whether the given component is on a top-level popup. A top-level popup is one generated by a popup - * trigger, which means popups generated from a JMenu are not included. + * Returns whether the given component is on a top-level popup. A top-level popup is one + * generated by a popup trigger, which means popups generated from a JMenu are not included. */ public static boolean isOnPopup(Component comp) { boolean isWrapper = isTransientPopup(comp); @@ -383,8 +384,8 @@ public static boolean isOnPopup(Component comp) { } /** - * Returns whether the given component is a heavyweight popup, that is, a container for a JPopupMenu that is - * implemented with a heavyweight component (usually a Window). + * Returns whether the given component is a heavyweight popup, that is, a container for a + * JPopupMenu that is implemented with a heavyweight component (usually a Window). */ public static boolean isHeavyweightPopup(Component c) { if (c instanceof Window && !(c instanceof Dialog) && !(c instanceof Frame)) { @@ -392,10 +393,7 @@ public static boolean isHeavyweightPopup(Component c) { String cname = c.getClass().getName(); return ("###overrideRedirect###".equals(name) || "###focusableSwingPopup###".equals(name) - // These classes are known to be heavyweight popups - // javax.swing.DefaultPopupFactory$WindowPopup (1.3) || cname.contains("PopupFactory$WindowPopup") - // javax.swing.Popup.HeavyWeightWindow (1.4) || cname.contains("HeavyWeightWindow")); } return false; @@ -413,16 +411,16 @@ private static String getName(Component c) { } /** - * Returns whether the given component is a lightweight popup, that is, a container for a JPopupMenu that is - * implemented with a lightweight component (usually JPanel). + * Returns whether the given component is a lightweight popup, that is, a container for a + * JPopupMenu that is implemented with a lightweight component (usually JPanel). */ public static boolean isLightweightPopup(Component c) { - if (c instanceof JPanel) { + if (c instanceof JPanel panel) { Window w = SwingUtilities.getWindowAncestor(c); if (isHeavyweightPopup(w)) { return false; } - JPanel panel = (JPanel) c; + Container parent = panel.getParent(); if (parent instanceof JLayeredPane) { int layer = JLayeredPane.POPUP_LAYER; @@ -440,15 +438,13 @@ public static boolean isLightweightPopup(Component c) { * * @see javax.swing.RootPaneContainer#getContentPane */ - public static boolean isContentPane(Component c) { - if (c.getParent() instanceof JLayeredPane) { - JLayeredPane p = (JLayeredPane) c.getParent(); - if (p.getParent() instanceof JRootPane) { - return ((JRootPane) p.getParent()).getContentPane() == c; - } else { - int layer = JLayeredPane.FRAME_CONTENT_LAYER; - return p.getLayer(c) == layer && !(c instanceof JMenuBar); + public static boolean isContentPane(Component comp) { + if (comp.getParent() instanceof JLayeredPane parent) { + if (parent.getParent() instanceof JRootPane) { + return ((JRootPane) parent.getParent()).getContentPane() == comp; } + int layer = JLayeredPane.FRAME_CONTENT_LAYER; + return parent.getLayer(comp) == layer && !(comp instanceof JMenuBar); } return false; } @@ -458,10 +454,9 @@ public static boolean isContentPane(Component c) { * * @see javax.swing.JRootPane#getGlassPane */ - public static boolean isGlassPane(Component c) { - if (c.getParent() instanceof JRootPane) { - JRootPane p = (JRootPane) c.getParent(); - return p.getGlassPane() == c; + public static boolean isGlassPane(Component comp) { + if (comp.getParent() instanceof JRootPane parent) { + return parent.getGlassPane() == comp; } return false; } @@ -469,16 +464,16 @@ public static boolean isGlassPane(Component c) { /** * Return whether the given component is part of the transient wrapper around a popup. */ - public static boolean isTransientPopup(Component c) { - return isLightweightPopup(c) || isHeavyweightPopup(c); + public static boolean isTransientPopup(Component comp) { + return isLightweightPopup(comp) || isHeavyweightPopup(comp); } - private static boolean containsToolTip(Component c) { - if (c instanceof JToolTip) { + private static boolean containsToolTip(Component comp) { + if (comp instanceof JToolTip) { return true; } - if (c instanceof Container) { - Component[] kids = ((Container) c).getComponents(); + if (comp instanceof Container container) { + Component[] kids = container.getComponents(); for (Component kid : kids) { if (containsToolTip(kid)) { return true; @@ -491,16 +486,16 @@ private static boolean containsToolTip(Component c) { /** * Return whether the given component is part of the transient wrapper around a tooltip. */ - public static boolean isToolTip(Component c) { - return isTransientPopup(c) && containsToolTip(c); + public static boolean isToolTip(Component comp) { + return isTransientPopup(comp) && containsToolTip(comp); } /** * Return whether the given component is part of an internal frame's LAF decoration. */ - public static boolean isInternalFrameDecoration(Component c) { - Component parent = c.getParent(); - return (parent instanceof JInternalFrame && !(c instanceof JRootPane)) + public static boolean isInternalFrameDecoration(Component comp) { + Component parent = comp.getParent(); + return (parent instanceof JInternalFrame && !(comp instanceof JRootPane)) || (parent != null && (parent.getParent() instanceof JInternalFrame) && (!(parent instanceof JRootPane))); @@ -541,27 +536,26 @@ public static PopupMenu[] getPopupMenus(Component c) { Field field = Component.class.getDeclaredField("popups"); boolean accessible = field.isAccessible(); field.setAccessible(true); - Vector popups = (Vector) field.get(c); + Vector popups = (Vector) field.get(c); field.setAccessible(accessible); if (popups != null) { - return (PopupMenu[]) popups.toArray(new PopupMenu[0]); + return (PopupMenu[]) popups.toArray(new Object[0]); } return NO_POPUPS; } catch (NoSuchFieldException e) { - // not gonna happen throw new Error("No field named 'popups' in class Component"); } catch (IllegalAccessException e) { - // neither should this throw new Error("Can't access popup for component " + c); } } /** - * Returns all MenuItems matching the given label or path which are on PopupMenus on the given Component. + * Returns all MenuItems matching the given label or path which are on PopupMenus on the given + * Component. */ public static MenuItem[] findAWTPopupMenuItems(Component parent, String path) { PopupMenu[] popups = getPopupMenus(parent); - ArrayList list = new ArrayList<>(); + List list = new ArrayList<>(); for (PopupMenu popup : popups) { list.addAll(findMenuItems(popup, path, true)); } @@ -569,7 +563,8 @@ public static MenuItem[] findAWTPopupMenuItems(Component parent, String path) { } /** - * Returns all MenuItems matching the given label or path which are found in the given Frame's MenuBar. + * Returns all MenuItems matching the given label or path which are found in the given Frame's + * MenuBar. */ public static MenuItem[] findAWTMenuItems(Frame frame, String path) { MenuBar mb = frame.getMenuBar(); @@ -592,29 +587,12 @@ public static String getPath(MenuItem item) { } /** - * Returns a unique path to the given MenuItem. If on a PopupMenu, optionally include the PopupMenu name. + * Returns a unique path to the given MenuItem. If on a PopupMenu, optionally include the + * PopupMenu name. */ private static String getPath(MenuItem item, boolean includePopupName) { Component invoker = getInvoker(item); - MenuContainer root = invoker; - MenuContainer top; - if (invoker == null) { - // Find the top-most Menu above this MenuItem - top = item.getParent(); - while (top instanceof Menu && !(((Menu) top).getParent() instanceof MenuBar)) { - top = ((Menu) top).getParent(); - } - if (top == null) { - throw new RuntimeException("MenuItem is not attached to the hierarchy"); - } - root = ((Menu) top).getParent(); - } else { - // Find the containing PopupMenu - top = item.getParent(); - while (top instanceof Menu && !(((Menu) top).getParent() instanceof Component)) { - top = ((Menu) top).getParent(); - } - } + MenuContainer top = getMenuContainer(item, invoker); // Return a path to the item, starting at the first top level Menu String path = item.getLabel(); @@ -647,16 +625,38 @@ private static String getPath(MenuItem item, boolean includePopupName) { return path; } + private static MenuContainer getMenuContainer(MenuItem item, Component invoker) { + if (invoker == null) { + // Find the top-most Menu above this MenuItem + var top = item.getParent(); + while (top instanceof Menu && !(((Menu) top).getParent() instanceof MenuBar)) { + top = ((Menu) top).getParent(); + } + if (top == null) { + throw new RuntimeException("MenuItem is not attached to the hierarchy"); + } + return top; + } + + // Find the containing PopupMenu + var top = item.getParent(); + while (top instanceof Menu && !(((Menu) top).getParent() instanceof Component)) { + top = ((Menu) top).getParent(); + } + return top; + } + /** - * Returns all AWT menu items found with the given label; if matchPath is set then the MenuItem path is examined as - * well as the label. + * Returns all AWT menu items found with the given label; if matchPath is set, then the MenuItem + * path is examined as well as the label. */ private static Collection findMenuItems( MenuContainer mc, String path, boolean matchPath) { if (matchPath) { Log.debug("Searching for '" + path + "' on '" + mc); } - ArrayList list = new ArrayList<>(); + + List list = new ArrayList<>(); if (mc instanceof MenuBar) { for (int i = 0; i < ((MenuBar) mc).getMenuCount(); i++) { Menu menu = ((MenuBar) mc).getMenu(i); @@ -687,8 +687,8 @@ private static Collection findMenuItems( } /** - * Return the focus owner under the given Window. As of 1.4.x, components will report that they do not have focus if - * asked from a different AppContext than their own. Account for that here. + * Return the focus owner under the given Window. As of 1.4.x, components will report that they do + * not have focus if asked from a different AppContext than their own. Account for that here. */ public static Component getFocusOwner() { try { @@ -729,23 +729,6 @@ private static Component getFocusOwner(Window w) { return focus; } - // NOT Supported in Mac Java5+ - // /** For debugging purposes only. */ - // public static AppContext getAppContext(Component c) { - // try { - // Field field = Component.class.getDeclaredField("appContext"); - // boolean accessible = field.isAccessible(); - // field.setAccessible(true); - // AppContext appContext = (AppContext)field.get(c); - // field.setAccessible(accessible); - // return appContext; - // } - // catch(Exception e) { - // Log.warn(e); - // return null; - // } - // } - /** * WARNING: This uses 1.3/1.4 implementation details. */ @@ -761,8 +744,7 @@ public static boolean eventTypeEnabled(Component c, int id) { AWTEvent ev = new AWTEvent(c, id) {}; Method m = Component.class.getDeclaredMethod("eventEnabled", AWTEvent.class); m.setAccessible(true); - Boolean b = (Boolean) m.invoke(c, new Object[] {ev}); - return b.booleanValue(); + return (Boolean) m.invoke(c, new Object[] {ev}); } catch (Exception e) { Log.warn(e); return true; @@ -776,14 +758,11 @@ public static boolean isSharedInvisibleFrame(Component c) { return c == JOptionPane.getRootFrame(); } - public static boolean isAppletViewerFrame(Component c) { - return c.getClass().getName().equals("sun.applet.AppletViewer"); - } - private static final Matcher POPUP_MATCHER = new ClassMatcher(JPopupMenu.class, true); /** - * Returns the currently active popup menu, if any. If no popup is currently showing, returns null. + * Returns the currently active popup menu, if any. If no popup is currently showing returns + * null. */ public static JPopupMenu getActivePopupMenu() { try { @@ -794,8 +773,8 @@ public static JPopupMenu getActivePopupMenu() { } /** - * Find the currently active Swing popup menu, if any, waiting up to POPUP_TIMEOUT ms. Returns null if no popup - * found. + * Find the currently active Swing popup menu, if any, waiting up to POPUP_TIMEOUT ms. Returns + * null if no popup found. */ public static JPopupMenu findActivePopupMenu() { JPopupMenu popup = getActivePopupMenu(); @@ -808,6 +787,7 @@ public static JPopupMenu findActivePopupMenu() { try { Thread.sleep(100); } catch (Exception e) { + // ignore } } } @@ -815,8 +795,9 @@ public static JPopupMenu findActivePopupMenu() { } /** - * Returns the location of the given components in screen coordinates. Avoids lockup if an AWT popup menu is - * showing, which means it holds the AWT tree lock, which Component.getLocationOnScreen requires. + * Returns the location of the given components in screen coordinates. Avoids lockup if an AWT + * popup menu is showing, which means it holds the AWT tree lock, which + * Component.getLocationOnScreen requires. */ public static Point getLocationOnScreen(Component c) { if (isAWTTreeLockHeld()) { @@ -835,17 +816,16 @@ public static Point getLocationOnScreen(Component c) { loc.translate(ploc.x, ploc.y); } return loc; - } else { - return new Point(c.getLocationOnScreen()); } + return new Point(c.getLocationOnScreen()); } /** - * Return whether the given component is part of a transient dialog. This includes dialogs generated by - * JFileChooser, JOptionPane, JColorChooser, and ProgressMonitor.

Note that it is possible to use - * JOptionPane.createDialog to create a reusable dialog, so just because it's transient doesn't mean it will be - * disposed of when it is hidden.

Note that this won't detect transient Dialogs after their components have been - * reassigned to a new transient Dialog. + * Return whether the given component is part of a transient dialog. This includes dialogs + * generated by JFileChooser, JOptionPane, JColorChooser, and ProgressMonitor.

Note that it is + * possible to use JOptionPane.createDialog to create a reusable dialog, so just because it's + * transient doesn't mean it will be disposed of when it is hidden.

Note that this won't detect + * transient Dialogs after their components have been reassigned to a new transient Dialog. */ public static boolean isTransientDialog(Component c) { if (c instanceof Window) { @@ -869,19 +849,8 @@ public static boolean isTransientDialog(Component c) { } /** - * Returns the Applet descendent of the given Container, if any. - */ - public static Applet findAppletDescendent(Container c) { - try { - return (Applet) BasicFinder.getDefault().find(c, new ClassMatcher(Applet.class)); - } catch (ComponentSearchException e) { - return null; - } - } - - /** - * Return whether this is the tertiary button, considering primary to be button1 and secondary to be the popup - * trigger button. + * Return whether this is the tertiary button, considering primary to be button1 and secondary to + * be the popup trigger button. */ public static boolean isTertiaryButton(int mods) { return ((mods & AWTConstants.BUTTON_DOWN_MASK) != InputEvent.BUTTON1_DOWN_MASK) @@ -914,7 +883,6 @@ public static int getModifiers(String mods) { } private static String getModifiers(int flags, boolean isMouse) { - // On a mac, ALT+BUTTON1 means BUTTON2; META+BUTTON1 means BUTTON3 int macModifiers = getDefaultToolkit().getMenuShortcutKeyMaskEx() @@ -1035,13 +1003,12 @@ public static int maskToKeyCode(int mask) { // Try to lock the AWT tree lock; returns immediately if it can private static class ThreadStateChecker extends Thread { - public boolean started; - private final Object lock; - public ThreadStateChecker(Object lock) { + private boolean started; + + public ThreadStateChecker() { super("thread state checker"); setDaemon(true); - this.lock = lock; } @Override @@ -1050,8 +1017,6 @@ public void run() { started = true; notifyAll(); } - synchronized (lock) { - } } } } diff --git a/abbot/src/main/java/abbot/util/SingleThreadedEventListener.java b/abbot/src/main/java/abbot/util/SingleThreadedEventListener.java index bc55843..e920bc6 100644 --- a/abbot/src/main/java/abbot/util/SingleThreadedEventListener.java +++ b/abbot/src/main/java/abbot/util/SingleThreadedEventListener.java @@ -5,28 +5,22 @@ import java.awt.AWTEvent; import java.awt.event.AWTEventListener; import java.util.ArrayList; +import java.util.List; import javax.swing.SwingUtilities; /** - * Provide an AWTEventListener which ensures all events are handled on the event dispatch thread. This allows the - * recorders and other listeners to safely manipulate GUI objects without concern for event dispatch thread-safety. + * Provide an AWTEventListener which ensures all events are handled on the event dispatch thread. + * This allows the recorders and other listeners to safely manipulate GUI objects without concern + * for event dispatch thread-safety. *

- * Window.show generates WINDOW_OPENED (and possibly hierarchy and other events) to any listeners from whatever thread - * the method was invoked on. + * Window.show generates WINDOW_OPENED (and possibly hierarchy and other events) to any listeners + * from whatever thread the method was invoked on. *

- * NOTE: Applet runners may run several simultaneous event dispatch threads when displaying multiple applets - * simultaneously. If this listener is installed in the parent context of those dispatch threads, it will be invoked on - * each of those threads, possibly simultaneously. */ public abstract class SingleThreadedEventListener implements AWTEventListener { - private final ArrayList deferredEvents = new ArrayList(); - private final Runnable action = - new Runnable() { - public void run() { - processDeferredEvents(); - } - }; + private final List deferredEvents = new ArrayList<>(); + private final Runnable action = this::processDeferredEvents; /** * Event reception callback. @@ -59,29 +53,29 @@ public void eventDispatched(AWTEvent event) { */ protected void processDeferredEvents() { // Make a copy of the deferred events and empty the queue - ArrayList queue = new ArrayList(); + List queue; synchronized (deferredEvents) { // In the rare case where there are multiple simultaneous dispatch // threads, it's possible for deferred events to get posted while // another event is being processed. At most this will mean a few // events get processed out of order, but they will likely be from // different event dispatch contexts, so it shouldn't matter. - queue.addAll(deferredEvents); + queue = new ArrayList<>(deferredEvents); deferredEvents.clear(); } - while (queue.size() > 0) { + while (!queue.isEmpty()) { AWTEvent prev = null; Log.debug("processing deferred event"); // Process any events that were generated - prev = (AWTEvent) queue.get(0); - queue.remove(0); + prev = queue.getFirst(); + queue.removeFirst(); processEvent(prev); } } /** - * This method is not protected by any synchronization locks (nor should it be); in the presence of multiple - * simultaneous event dispatch threads, the listener must be threadsafe. + * This method is not protected by any synchronization locks (nor should it be); in the presence + * of multiple simultaneous event dispatch threads, the listener must be threadsafe. */ protected abstract void processEvent(AWTEvent event); } diff --git a/abbot/src/main/java/example/SimpleApplet.java b/abbot/src/main/java/example/SimpleApplet.java deleted file mode 100644 index 65d8d25..0000000 --- a/abbot/src/main/java/example/SimpleApplet.java +++ /dev/null @@ -1,96 +0,0 @@ -package example; - -import java.applet.Applet; -import java.awt.Button; -import java.awt.Component; -import java.awt.Dialog; -import java.awt.Frame; -import java.awt.Graphics; -import java.awt.Label; -import java.awt.TextField; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -/** - * A very simple AWT-based applet. See applet.xml and applet.hmlt.

- * - * <applet code="example.SimpleApplet" width=250 height> </applet> - * - * - * @author kelvinr@users.sourceforge.net, twall@users.sourceforge.net - */ -public class SimpleApplet extends Applet { - - String msg = "This is a simple applet"; - - public void init() { - Label push = new Label("Press a button"); - final Button hi = new Button("High"); - final Button lo = new Button("Low"); - final Button show = new Button("?"); - Component parent = this; - while (parent.getParent() != null) { - parent = parent.getParent(); - } - if (!(parent instanceof Frame)) { - parent = new Frame("Dummy Frame"); - } - final Dialog dialog = new Dialog((Frame) parent, "Dialog", true); - dialog.add(new Label("This is a dialog")); - dialog.addWindowListener( - new WindowAdapter() { - public void windowClosing(WindowEvent we) { - we.getWindow().hide(); - } - }); - - // Adds labels and buttons to applet window - add(push); - add(hi); - add(lo); - add(show); - add(new TextField("text here")); - - ActionListener al = - new ActionListener() { - public void actionPerformed(ActionEvent ae) { - if (ae.getSource() == hi) { - msg = "Up, up and away!"; - } else if (ae.getSource() == lo) { - msg = "How low can you go?"; - } else { - dialog.pack(); - dialog.show(); - } - repaint(); - } - }; - hi.addActionListener(al); - lo.addActionListener(al); - show.addActionListener(al); - - tagThread("applet init"); - javax.swing.SwingUtilities.invokeLater( - new Runnable() { - public void run() { - tagThread("example.SimpleApplet"); - } - }); - } - - private void tagThread(String tag) { - Thread thread = Thread.currentThread(); - String name = thread.getName(); - thread.setName(name + " (" + tag + ")"); - } - - public String getMessage() { - return msg; - } - - public void paint(Graphics g) { - g.drawString(getMessage(), 20, 120); - } -} diff --git a/com.windowtester.junit5/src/test/java/swing/samples/AppletDemo.java b/com.windowtester.junit5/src/test/java/swing/samples/AppletDemo.java deleted file mode 100644 index 3859cf1..0000000 --- a/com.windowtester.junit5/src/test/java/swing/samples/AppletDemo.java +++ /dev/null @@ -1,114 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012 Google, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Google, Inc. - initial API and implementation - *******************************************************************************/ -package swing.samples; - -// : c14:List.java -// -// From 'Thinking in Java, 3rd ed.' (c) Bruce Eckel 2002 -// www.BruceEckel.com. See copyright notice in CopyRight.txt. - -import java.awt.Color; -import java.awt.Container; -import java.awt.FlowLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.DefaultListModel; -import javax.swing.JApplet; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JList; -import javax.swing.JTextArea; -import javax.swing.border.Border; -import javax.swing.event.ListSelectionListener; - -public class AppletDemo extends JApplet { - - private final String[] flavors = { - "Chocolate", - "Strawberry", - "Vanilla Fudge Swirl", - "Mint Chip", - "Mocha Almond Fudge", - "Rum Raisin", - "Praline Cream", - "Mud Pie" - }; - - private final DefaultListModel lItems = new DefaultListModel<>(); - private final JList lst = new JList<>(lItems); - private final JTextArea t = new JTextArea(flavors.length, 20); - private final JButton b = new JButton("Add Item"); - - private int count = 0; - - private final ActionListener bl = - new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (count < flavors.length) { - lItems.add(0, flavors[count++]); - } else { - // Disable, since there are no more - // flavors left to be added to the List - b.setEnabled(false); - } - } - }; - - private final ListSelectionListener ll = - e -> { - if (e.getValueIsAdjusting()) { - return; - } - t.setText(""); - - List items = lst.getSelectedValuesList(); - for (String item : items) { - t.append(item + "\n"); - } - }; - - public void init() { - Container cp = getContentPane(); - t.setEditable(false); - cp.setLayout(new FlowLayout()); - // Create Borders for components: - Border brd = BorderFactory.createMatteBorder(1, 1, 2, 2, Color.BLACK); - lst.setBorder(brd); - t.setBorder(brd); - // Add the first four items to the List - for (int i = 0; i < 4; i++) { - lItems.addElement(flavors[count++]); - } - // Add items to the Content Pane for Display - cp.add(t); - cp.add(lst); - cp.add(b); - // Register event listeners - lst.addListSelectionListener(ll); - b.addActionListener(bl); - } - - public static void main(String[] args) { - run(new AppletDemo(), 250, 375); - } - - public static void run(JApplet applet, int width, int height) { - JFrame frame = new JFrame(); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.getContentPane().add(applet); - frame.setSize(width, height); - applet.init(); - applet.start(); - frame.setVisible(true); - } -}