/*******************************************************************************
* 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 com.windowtester.runtime.swt.internal.finder.legacy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import org.eclipse.swt.widgets.Decorations;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Widget;
import abbot.Platform;
import abbot.finder.swt.Hierarchy;
import abbot.finder.swt.Matcher;
import abbot.finder.swt.MultiMatcher;
import abbot.finder.swt.MultipleWidgetsFoundException;
import abbot.finder.swt.SWTHierarchy;
import com.windowtester.internal.debug.IRuntimePluginTraceOptions;
import com.windowtester.internal.debug.TraceHandler;
import com.windowtester.runtime.swt.internal.display.DisplayExec;
import com.windowtester.runtime.swt.internal.settings.TestSettings;
/**
* Provides basic widget lookup, examining each widget in turn.
* Searches all widgets of interest in a given hierarchy. Unlike Abbot <code>abbot.finder.swt.BasicFinder</code>,
* it does not throw exceptions on match failures. Instead, <code>WidgetFinder</code>
* returns a <code>MatchResult</code>.
* <p>
* ANOTHER! copy of BasicFinder. This one differs in the following ways:
* <br>
* (1) It does not throw exceptions in case widgets are not found -- returning a
* <code>MatchResult</code> object instead.
*
*
* @deprecated
*
*/
public class WidgetFinder {
//match type constants
public static final int MATCH = 0;
public static final int WIDGET_NOT_FOUND = 1;
public static final int MULTIPLE_WIDGETS_FOUND = 3;
//search constants
public static final int DFS = 1; // depth-first search
public static final int BFS = 2; // breadth-first search
//number of times to retry a widget find
//public static final int MAX_FIND_RETRIES = 10;
//how long to wait between retries (in ms)
//public static final int RETRY_INTERVAL = 500;
//the search type (DFS|BFS)
private int _searchType = DFS; //default
//debug variable to test how many comparisons are made
private int _dbComparisons;
/**
* A hierarchy rooted by a given widget.
*/
private class SingleWidgetHierarchy implements Hierarchy {
private final ArrayList _list = new ArrayList();
private final Hierarchy _hierarchy;
public SingleWidgetHierarchy(Widget root) {
_hierarchy = new SWTHierarchy(root.getDisplay());
_list.add(root);
}
public Collection getRoots() {
return _list;
}
public Collection getWidgets(Widget c) {
return _hierarchy.getWidgets(c);
}
public Widget getParent(Widget c) {
return _hierarchy.getParent(c);
}
public boolean contains(Widget c) {
return _hierarchy.contains(c);
}
public void dispose(Decorations w) {
_hierarchy.dispose(w);
}
}
/**
* Encapsulates match test results.
*/
public static class MatchResult {
/** the matched widget */
private Widget _widget;
/** matched widgets (multiple match case) */
private Collection _widgets;
/** the type of result (match by default) */
private int _type = MATCH;
/**
* Create a match result of the given type.
* @param type
*/
public MatchResult(int type) {
_type = type;
}
/**
* Create a match result for the given widget.
* @param w
*/
public MatchResult(Widget w) {
_widget = w;
}
/**
* Create a widget not found match result.
*/
static MatchResult notFound() {
return new MatchResult(WIDGET_NOT_FOUND);
}
/**
* Create a multiple widgets found match result.
* @param found
*/
static MatchResult multipleFound(Collection found) {
MatchResult result = new MatchResult(MULTIPLE_WIDGETS_FOUND);
result.setFound(found);
return result;
}
/**
* Create a found match result.
*/
static MatchResult match(Widget w) {
return new MatchResult(w);
}
/**
* Get the found widget.
* @return the found widget or <code>null</code> if none was found.
*/
public Widget getWidget() {
return _widget;
}
/**
* Get the found widgets. (Note this will only be set in the case
* where multiple widgets are found.)
* TODO: this is to be refactored post 2.0
* @return the found widgets in the multiple match case
*/
public Collection getWidgets() {
return _widgets;
}
public void setFound(Collection found) {
_widgets = new ArrayList();
_widgets.addAll(found); //note: defensive copy
}
public int getType() {
return _type;
}
}
/** Find a Widget, using the given Matcher to determine whether a given
widget in the hierarchy under the given root is the desired
one.
*/
public MatchResult find(Widget root, Matcher m) {
assertNotNull(root, "root must not be null");
assertNotNull(m, "matcher must not be null");
_dbComparisons = 0;
return find(new SingleWidgetHierarchy(root), m);
}
public MatchResult find(Widget root, Matcher m, int tries) {
assertNotNull(root, new RootWidgetIsNullError());
assertNotNull(m, "matcher must not be null");
_dbComparisons = 0;
return find(new SingleWidgetHierarchy(root), m, tries);
}
private MatchResult find(Hierarchy h, Matcher m, int maxTries) {
MatchResult result = find0(h, m);
//if it wasn't a match retry
int tries = 0;
while (result.getType() != MATCH && tries++ < maxTries) {
TraceHandler.trace(IRuntimePluginTraceOptions.BASIC, "WidgetFinder failed to find widget (" + m + ") retrying [" + tries + "/" + maxTries +"]");
// try {
// throw new RuntimeException();
// } catch(Throwable t) {
// t.printStackTrace();
// }
pause(getFinderRetryInterval());
result = find0(h, m);
}
return result;
}
/** Find a Widget, using the given Matcher to determine whether a given
widget in the hierarchy in the given display is the desired
one.
*/
public MatchResult find(Display display, Matcher m) {
return find(display, m, getMaxFinderRetries());
}
public MatchResult find(Display display, Matcher m, int maxRetries) {
assertNotNull(display, "display must not be null");
assertNotNull(m, "matcher must not be null");
_dbComparisons = 0;
return find(new SWTHierarchy(display), m, maxRetries);
}
public MatchResult find(Hierarchy h, Matcher m) {
return find(h, m, getMaxFinderRetries());
}
protected MatchResult find0(final Hierarchy h, final Matcher m) {
final Set found = new HashSet();
/* The list is used in parallel with the set for multimatchers who need
* to know in what order elements were added to the list */
final java.util.List foundList = new ArrayList();
final Collection roots = h.getRoots();
if (Platform.isOSX()) { // Mac testing -- this is a hack
Display display = null;
if (roots.size() > 0) {
Object x = roots.iterator().next();
if (x instanceof Widget) {
Widget w = (Widget) x;
if (!w.isDisposed())
display = w.getDisplay();
}
}
// this will work even if display is null as long as MenuWatcher is a singleton
//roots.addAll(MenuWatcher.getInstance(display).getOpenMenus());
}
if (_searchType == DFS) {
DisplayExec.sync(new Runnable(){
public void run() {
Iterator iter = roots.iterator();
while (iter.hasNext()) {
findMatches(h, m, (Widget) iter.next(), found, foundList);
}
}
});
} else if (_searchType == BFS) {
LinkedList searchQ = new LinkedList(roots);
while (searchQ.size() > 0) {
Widget current = (Widget) searchQ.removeFirst();
_dbComparisons++;
if (m.matches(current)) {
if (!(m instanceof MultiMatcher)) {
return MatchResult.match(current);
}
found.add(current);
if (m instanceof MultiMatcher) {
foundList.add(current);
}
}
searchQ.addAll(h.getWidgets(current));
}
}
if (found.size() == 0) {
return MatchResult.notFound();
} else if (found.size() > 1) {
Widget[] list = (Widget[]) foundList.toArray(new Widget[foundList
.size()]);
if (!(m instanceof MultiMatcher)) {
return MatchResult.multipleFound(found);
}
try {
MatchResult.match(((MultiMatcher) m).bestMatch(list));
} catch (MultipleWidgetsFoundException e) {
return MatchResult.multipleFound(found);
}
}
return MatchResult.match((Widget) found.iterator().next());
}
protected void findMatches(Hierarchy h, Matcher m, Widget w, Set found,
java.util.List foundList) {
// Matcher should never return multiple or throw MultipleWidgetsFoundException
// !pq: actually DO want this behavior!
// if (found.size() == 1 && !(m instanceof MultiMatcher))
// return;
if (_searchType == DFS) {
Iterator iter = h.getWidgets(w).iterator();
while (iter.hasNext()) {
findMatches(h, m, (Widget) iter.next(), found, foundList);
// if (!searchAll && found.size() > 0) return;
}
_dbComparisons++; // for debug purposes: keep track of the number of comparisons done
// if (!searchAll && found.size() > 0) return;
if (m.matches(w)) {
found.add(w);
if (m instanceof MultiMatcher) {
foundList.add(w);
}
}
}
}
//MOVING
/////////////////////////////////////////////////////////////////////////////////
//
// Accessors
//
/////////////////////////////////////////////////////////////////////////////////
//number of times to retry a widget find
private static int getMaxFinderRetries() {
return TestSettings.getInstance().getFinderRetries();
}
private static int getFinderRetryInterval() {
return TestSettings.getInstance().getFinderRetryInterval();
}
/////////////////////////////////////////////////////////////////////////////////
//
// Contract helper.
//
/////////////////////////////////////////////////////////////////////////////////
private void assertNotNull(Object o, String msg) {
if (o == null)
throw new AssertionError(msg);
}
private void assertNotNull(Object o, Error ex) {
if (o == null)
throw ex;
}
///////////////////////////////////////////////////////////////////////////
//
// Timing
//
///////////////////////////////////////////////////////////////////////////
private static void pause(int ms) {
try { Thread.sleep(ms); } catch(InterruptedException ie) { }
}
}