001: package abbot.finder;
002:
003: import java.awt.*;
004: import java.awt.event.*;
005: import java.util.*;
006: import javax.swing.SwingUtilities;
007: import abbot.Log;
008: import abbot.util.*;
009:
010: /** Provide isolation of a Component hierarchy to limit consideration to only
011: those Components created during the lifetime of this Hierarchy instance.
012: Extant Components (and any subsequently generated subwindows) are ignored
013: by default.<p>
014: Implicitly auto-filters windows which are disposed (i.e. generate a
015: WINDOW_CLOSED event), but also implicitly un-filters them if they should
016: be shown again. Any Window explicitly disposed with
017: {@link #dispose(Window)} will be ignored permanently.<p>
018: */
019: public class TestHierarchy extends AWTHierarchy {
020:
021: // Map of components to ignore
022: private Map filtered = new WeakHashMap();
023: // Map of components implicitly filtered; these will be implicitly
024: // un-filtered if they are re-shown.
025: private Map transientFiltered = new WeakHashMap();
026:
027: private static boolean trackAppletConsole = Boolean
028: .getBoolean("abbot.applet.track_console");
029: /** Avoid GC of the weak reference. */
030: private AWTEventListener listener;
031:
032: /** Create a new TestHierarchy which does not contain any UI
033: * Components which might already exist.
034: */
035: public TestHierarchy() {
036: this (true);
037: }
038:
039: /** Create a new TestHierarchy, indicating whether extant Components
040: * should be omitted from the Hierarchy.
041: */
042: public TestHierarchy(boolean ignoreExisting) {
043: if (ignoreExisting)
044: ignoreExisting();
045: // Watch for introduction of transient dialogs so we can automatically
046: // filter them on dispose (WINDOW_CLOSED). Don't do anything when the
047: // component is simply hidden, since we can't tell whether it will be
048: // re-used.
049: listener = new TransientWindowListener();
050: }
051:
052: public boolean contains(Component c) {
053: return super .contains(c) && !isFiltered(c);
054: }
055:
056: /** Dispose of the given Window, but only if it currently exists within
057: * the hierarchy. It will no longer appear in this Hierarchy or be
058: * reachable in a hierarchy walk.
059: */
060: public void dispose(Window w) {
061: if (contains(w)) {
062: super .dispose(w);
063: setFiltered(w, true);
064: }
065: }
066:
067: /** Make all currently extant components invisible to this Hierarchy,
068: * without affecting their current state.
069: */
070: public void ignoreExisting() {
071: Iterator iter = getRoots().iterator();
072: while (iter.hasNext()) {
073: setFiltered((Component) iter.next(), true);
074: }
075: }
076:
077: /** Returns all available root Windows, excluding those which have been
078: * filtered.
079: */
080: public Collection getRoots() {
081: Collection s = super .getRoots();
082: s.removeAll(filtered.keySet());
083: return s;
084: }
085:
086: /** Returns all sub-components of the given Component, omitting those
087: * which are currently filtered.
088: */
089: public Collection getComponents(Component c) {
090: if (!isFiltered(c)) {
091: Collection s = super .getComponents(c);
092: // NOTE: this only removes those components which are directly
093: // filtered, not necessarily those which have a filtered ancestor.
094: s.removeAll(filtered.keySet());
095: return s;
096: }
097: return EMPTY;
098: }
099:
100: private boolean isWindowFiltered(Component c) {
101: Window w = AWT.getWindow(c);
102: return w != null && isFiltered(w);
103: }
104:
105: /** Returns true if the given component will not be considered when
106: * walking the hierarchy. A Component is filtered if it has explicitly
107: * been filtered via {@link #setFiltered(Component,boolean)}, or if
108: * any <code>Window</code> ancestor has been filtered.
109: */
110: public boolean isFiltered(Component c) {
111: if (c == null)
112: return false;
113: if ("sun.plugin.ConsoleWindow".equals(c.getClass().getName()))
114: return !trackAppletConsole;
115: return filtered.containsKey(c)
116: || ((c instanceof Window) && isFiltered(c.getParent()))
117: || (!(c instanceof Window) && isWindowFiltered(c));
118: }
119:
120: /** Indicates whether the given component is to be included in the
121: Hierarchy. If the component is a Window, recursively applies the
122: action to all owned Windows.
123: */
124: public void setFiltered(Component c, boolean filter) {
125: // Never filter the shared frame
126: if (AWT.isSharedInvisibleFrame(c)) {
127: Iterator iter = getComponents(c).iterator();
128: while (iter.hasNext()) {
129: setFiltered((Component) iter.next(), filter);
130: }
131: } else {
132: if (filter) {
133: filtered.put(c, Boolean.TRUE);
134: } else {
135: filtered.remove(c);
136: }
137: transientFiltered.remove(c);
138: if (c instanceof Window) {
139: Window[] owned = ((Window) c).getOwnedWindows();
140: for (int i = 0; i < owned.length; i++) {
141: setFiltered(owned[i], filter);
142: }
143: }
144: }
145: }
146:
147: /** Provides for automatic filtering of auto-generated Swing dialogs. */
148: private class TransientWindowListener implements AWTEventListener {
149: private class DisposeAction implements Runnable {
150: private Window w;
151:
152: public DisposeAction(Window w) {
153: this .w = w;
154: }
155:
156: public void run() {
157: // If the window was shown again since we queued this action,
158: // it will have removed the window from the transientFiltered
159: // set, and we shouldn't filter.
160: if (transientFiltered.containsKey(w)) {
161: setFiltered(w, true);
162: Log.debug("window " + w.getName() + " filtered");
163: } else {
164: Log.debug("cancel dispose of " + w.getName());
165: }
166: }
167: }
168:
169: public TransientWindowListener() {
170: // Add a weak listener so we don't leave a listener lingering
171: // about.
172: long mask = WindowEvent.WINDOW_EVENT_MASK
173: | ComponentEvent.COMPONENT_EVENT_MASK;
174: new WeakAWTEventListener(this , mask);
175: }
176:
177: public void eventDispatched(AWTEvent e) {
178: if (e.getID() == WindowEvent.WINDOW_OPENED
179: || (e.getID() == ComponentEvent.COMPONENT_SHOWN && e
180: .getSource() instanceof Window)) {
181: Window w = (Window) e.getSource();
182: Log.debug("window " + w.getName() + " open/shown");
183: if (transientFiltered.containsKey(w)) {
184: Log.debug("un-filter window " + w.getName());
185: setFiltered(w, false);
186: }
187: // Catch new sub-windows of filtered windows (i.e. dialogs
188: // generated by a test harness UI).
189: else if (isFiltered(w.getParent())) {
190: Log.debug("Parent is filtered, filter "
191: + w.getName());
192: setFiltered(w, true);
193: }
194: } else if (e.getID() == WindowEvent.WINDOW_CLOSED) {
195: final Window w = (Window) e.getSource();
196: // *Any* window disposal should result in the window being
197: // ignored, at least until it is again displayed.
198: if (!isFiltered(w)) {
199: transientFiltered.put(w, Boolean.TRUE);
200: // Filter this window only *after* any handlers for this
201: // event have finished.
202: Log.debug("queueing dispose of " + w.getName());
203: SwingUtilities.invokeLater(new DisposeAction(w));
204: }
205: }
206: }
207: }
208: }
|