package abbot.finder; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.SwingUtilities; import javax.swing.JPopupMenu; import abbot.Log; import abbot.util.*; /** Provide isolation of a Component hierarchy to limit consideration to only those Components created during the lifetime of this Hierarchy instance. Extant Components (and any subsequently generated subwindows) are ignored by default.<p> Implicitly auto-filters windows which are disposed (i.e. generate a WINDOW_CLOSED event), but also implicitly un-filters them if they should be shown again. Any Window explicitly disposed with {@link #dispose(Window)} will be ignored permanently.<p> */ public class TestHierarchy extends AWTHierarchy { // Map of components to ignore private Map filtered = new WeakHashMap(); // Map of components implicitly filtered; these will be implicitly // un-filtered if they are re-shown. private Map transientFiltered = new WeakHashMap(); private static boolean trackAppletConsole = Boolean.getBoolean("abbot.applet.track_console"); /** Avoid GC of the weak reference. */ private AWTEventListener listener; /** Create a new TestHierarchy which does not contain any UI * Components which might already exist. */ public TestHierarchy() { this(true); } /** Create a new TestHierarchy, indicating whether extant Components * should be omitted from the Hierarchy. */ public TestHierarchy(boolean ignoreExisting) { if (ignoreExisting) ignoreExisting(); // Watch for introduction of transient dialogs so we can automatically // filter them on dispose (WINDOW_CLOSED). Don't do anything when the // component is simply hidden, since we can't tell whether it will be // re-used. listener = new TransientWindowListener(); } public boolean contains(Component c) { return super.contains(c) && !isFiltered(c); } /** Dispose of the given Window, but only if it currently exists within * the hierarchy. It will no longer appear in this Hierarchy or be * reachable in a hierarchy walk. */ public void dispose(Window w) { if (contains(w)) { super.dispose(w); setFiltered(w, true); } } /** Make all currently extant components invisible to this Hierarchy, * without affecting their current state. */ public void ignoreExisting() { Iterator iter = getRoots().iterator(); while (iter.hasNext()) { setFiltered((Component)iter.next(), true); } } /** Returns all available root Windows, excluding those which have been * filtered. */ public Collection getRoots() { Collection s = super.getRoots(); s.removeAll(filtered.keySet()); return s; } /** Returns all sub-components of the given Component, omitting those * which are currently filtered. */ public Collection getComponents(Component c) { if (!isFiltered(c)) { Collection s = super.getComponents(c); // NOTE: this only removes those components which are directly // filtered, not necessarily those which have a filtered ancestor. s.removeAll(filtered.keySet()); return s; } return EMPTY; } private boolean isWindowFiltered(Component c) { Window w = AWT.getWindow(c); return w != null && isFiltered(w); } /** Returns true if the given component will not be considered when * walking the hierarchy. A Component is filtered if it has explicitly * been filtered via {@link #setFiltered(Component,boolean)}, or if * any <code>Window</code> ancestor has been filtered. */ public boolean isFiltered(Component c) { if (c == null) return false; if ("sun.plugin.ConsoleWindow".equals(c.getClass().getName())) return !trackAppletConsole; return filtered.containsKey(c) || ((c instanceof Window) && isFiltered(c.getParent())) || (!(c instanceof Window) && isWindowFiltered(c)); } /** Indicates whether the given component is to be included in the Hierarchy. If the component is a Window, recursively applies the action to all owned Windows. */ public void setFiltered(Component c, boolean filter) { if (AWT.isSharedInvisibleFrame(c)) { Iterator iter = getComponents(c).iterator(); while (iter.hasNext()) { setFiltered((Component)iter.next(), filter); } } else { if (filter) { filtered.put(c, Boolean.TRUE); transientFiltered.remove(c); } else { filtered.remove(c); } if (c instanceof Window) { Window[] owned = ((Window)c).getOwnedWindows(); for (int i=0;i < owned.length;i++) { setFiltered(owned[i], filter); } } } } /** Provides for automatic filtering of auto-generated Swing dialogs. */ private class TransientWindowListener implements AWTEventListener { private class DisposeAction implements Runnable { private Window w; public DisposeAction(Window w) { this.w = w; } public void run() { setFiltered(w, true); transientFiltered.put(w, Boolean.TRUE); Log.debug("window " + w.getName() + " filtered"); } } public TransientWindowListener() { // Add a weak listener so we don't leave a listener lingering // about. long mask = WindowEvent.WINDOW_EVENT_MASK | ComponentEvent.COMPONENT_EVENT_MASK; new WeakAWTEventListener(this, mask); } public void eventDispatched(AWTEvent e) { if (e.getID() == WindowEvent.WINDOW_OPENED || (e.getID() == ComponentEvent.COMPONENT_SHOWN && e.getSource() instanceof Window)) { Window w = (Window)e.getSource(); if (transientFiltered.containsKey(w)) { setFiltered(w, false); } // Catch new sub-windows of filtered windows (i.e. dialogs // generated by a test harness UI). else if (isFiltered(w.getParent())) { setFiltered(w, true); } } else if (e.getID() == WindowEvent.WINDOW_CLOSED) { final Window w = (Window)e.getSource(); // *Any* window disposal should result in the window being // ignored, at least until it is again displayed. if (!isFiltered(w)) { // Filter only *after* any handlers for this event have // finished. Log.debug("queueing dispose of " + w.getName()); SwingUtilities.invokeLater(new DisposeAction(w)); } } } } }