/*******************************************************************************
* 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.finder.swt;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Decorations;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
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 abbot.finder.swt.WidgetFinder;
import abbot.finder.swt.WidgetNotFoundException;
import com.windowtester.internal.debug.IRuntimePluginTraceOptions;
import com.windowtester.internal.debug.TraceHandler;
import com.windowtester.internal.runtime.util.StringUtils;
import com.windowtester.runtime.util.TestMonitor;
/** Provides basic widget lookup, examining each widget in turn.
Searches all widgets of interest in a given hierarchy.
!pq: copied and modified --- rub: need to recognize multiple widget found
scenarios and BasicFinder does not...
*/
public class BasicFinder2 implements WidgetFinder {
SWTHierarchy hierarchy;
public static final int DFS = 1; // depth-first search
public static final int BFS = 2; // breadth-first search
private static BasicFinder2 DEFAULT = null;
private int searchType;
private int dbComparisons; /* debug variable to test how many comparisons are made */
private List _listeners = new ArrayList();
private SearchScopeHelper _searchScopeHelper;
//used to bound retries
private int _attempts;
public static BasicFinder2 getDefault() {
if (DEFAULT==null) {
DEFAULT = new BasicFinder2(new SWTHierarchy(Display.getDefault()));
// TODO: check whether this is the right default
}
return DEFAULT;
}
private class SingleWidgetHierarchy implements Hierarchy {
//private Widget root;
private ArrayList list = new ArrayList();
public SingleWidgetHierarchy(Widget root) {
//this.root = root;
list.add(root);
}
public SingleWidgetHierarchy(Composite root) {
//this.root = root;
list.add(root);
}
public Collection getRoots() {
return list;
}
public Collection getWidgets(Widget c) {
return getHierarchy().getWidgets(c);
}
public Widget getParent(Widget c) {
return getHierarchy().getParent(c);
}
public boolean contains(Widget c) {
return getHierarchy().contains(c);
// TODO: find a way to find widgets at all levels in SWT
// && SwingUtilities.isDescendingFrom(c, root);
}
public void dispose(Decorations w) { getHierarchy().dispose(w); }
}
public BasicFinder2(SWTHierarchy h) {
this (h, DFS);
}
/**Create a new finder specifying the hierarchy that should be
* used and the search type to be used (breadth-first or depth-first).
* Note that choosing the search type does not make sense if the matcher
* is an instance MultiMatcher, since in this case the entire hierarchy
* has to be searched anyway.
*
* @param h Widget hierarchy to use
* @param searchAll If true, return only unique matches and throw WidgetNotFound
* or MultipleWidgetFound exceptions; otherwise, return the
* first match found or a WidgetNotFoundException
* @param searchType Type of search (BFS or DFS)
*/
public BasicFinder2 (SWTHierarchy h, int searchType) {
hierarchy = h;
this.searchType = searchType;
//if we're running in a testcase, add a special exception handler
if (TestMonitor.getInstance().isTestRunning())
addExceptionListener(new ModalShellClosingExceptionListener(hierarchy.getDisplay()));
_searchScopeHelper = new SearchScopeHelper(h);
}
private void addExceptionListener(IExceptionListener listener) {
_listeners.add(listener);
}
public Hierarchy getHierarchy() {
return hierarchy;
}
/**
* Convenience method that makes getting a widget's parent possible through
* the finder
* @param widget The widget whose parent should be found
* @return The parent of the given widget
*/
public Widget getWidgetParent(Widget widget) {
return hierarchy.getParent(widget);
}
public Widget find(Composite root, Matcher m)
throws WidgetNotFoundException, MultipleWidgetsFoundException {
return find((Widget)root,m);
}
/** 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 Widget find(Widget root, Matcher m)
throws WidgetNotFoundException, MultipleWidgetsFoundException {
dbComparisons = 0;
Hierarchy h = root != null
? new SingleWidgetHierarchy(root) : getHierarchy();
return find(h, m);
}
/** Find a Widget, using the given Matcher to determine whether a given
widget in the hierarchy used by this WidgetFinder is the desired
one.
*/
public Widget find(Matcher m)
throws WidgetNotFoundException, MultipleWidgetsFoundException {
dbComparisons = 0;
_attempts = 0;
return findInShellScope(m);
}
private Widget findInShellScope(Matcher m) throws WidgetNotFoundException, MultipleWidgetsFoundException {
/*
* SPIKE: root search at active shell or in shell identified by
* a ShellComponentMatcher
*/
Shell shell = _searchScopeHelper.getShellSearchScope(m);
return findInShellScope0(shell, m);
}
private Widget findInShellScope0(Shell shell, Matcher m) throws WidgetNotFoundException, MultipleWidgetsFoundException {
Hierarchy h = new SingleWidgetHierarchy(shell);
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 */
java.util.List foundList = new ArrayList();
Collection roots = h.getRoots();
if (searchType==DFS) {
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)) {
// Matcher should never return multiple or throw MultipleWidgetsFoundException
if (!(m instanceof MultiMatcher)) {
return current;
}
found.add(current);
if (m instanceof MultiMatcher) {
foundList.add(current);
}
}
searchQ.addAll(h.getWidgets(current));
}
}
//update attempt number and possibly try again
if (found.size()!= 1 && _attempts++ < com.windowtester.runtime.swt.internal.widgets.finder.SWTWidgetFinder.getMaxFinderRetries()) {
TraceHandler.trace(IRuntimePluginTraceOptions.BASIC, "BasicFinder failed to find widget (" + m + ") retrying [" + _attempts + "/" + com.windowtester.runtime.swt.internal.widgets.finder.SWTWidgetFinder.getMaxFinderRetries() +"]");
pause(com.windowtester.runtime.swt.internal.widgets.finder.SWTWidgetFinder.getFinderRetryInterval());
return findInShellScope(m);
}
if (found.size() == 0) {
String msg = "Widget NOT Found:\n" + m.toString();
informExceptionListeners("WidgetNotFound");
throw new WidgetNotFoundException(msg);
}
else if (found.size() > 1) {
Widget[] list = (Widget[])
foundList.toArray(new Widget[foundList.size()]);
if (!(m instanceof MultiMatcher)) {
String msg = "MULTIPLE Widgets Found:\n" + m.toString();
informExceptionListeners("MultipleWidgetsFound");
throw new MultipleWidgetsFoundException(msg, list);
}
return ((MultiMatcher)m).bestMatch(list);
}
return (Widget)found.iterator().next();
}
protected Widget find(Hierarchy h, Matcher m)
throws WidgetNotFoundException, MultipleWidgetsFoundException {
//reset attempts
_attempts = 0;
return find0(h, m);
}
protected Widget find0(Hierarchy h, Matcher m)
throws WidgetNotFoundException, MultipleWidgetsFoundException {
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 */
java.util.List foundList = new ArrayList();
Collection roots = h.getRoots();
if (searchType==DFS) {
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)) {
// Matcher should never return multiple or throw MultipleWidgetsFoundException
if (!(m instanceof MultiMatcher)) {
return current;
}
found.add(current);
if (m instanceof MultiMatcher) {
foundList.add(current);
}
}
searchQ.addAll(h.getWidgets(current));
}
}
//update attempt number and possibly try again
if (found.size()!= 1 && _attempts++ < com.windowtester.runtime.swt.internal.widgets.finder.SWTWidgetFinder.getMaxFinderRetries()) {
TraceHandler.trace(IRuntimePluginTraceOptions.BASIC, "BasicFinder failed to find widget (" + m + ") retrying [" + _attempts + "/" + com.windowtester.runtime.swt.internal.widgets.finder.SWTWidgetFinder.getMaxFinderRetries() +"]");
pause(com.windowtester.runtime.swt.internal.widgets.finder.SWTWidgetFinder.getFinderRetryInterval());
return find0(h, m);
}
if (found.size() == 0) {
String msg = "Widget NOT Found:" + StringUtils.NEW_LINE + m.toString();
informExceptionListeners("WidgetNotFound");
throw new WidgetNotFoundException(msg);
}
else if (found.size() > 1) {
Widget[] list = (Widget[])
foundList.toArray(new Widget[foundList.size()]);
if (!(m instanceof MultiMatcher)) {
informExceptionListeners("MultipleWidgetsFound");
String msg = "MULTIPLE Widgets Found:" + StringUtils.NEW_LINE + m.toString();
throw new MultipleWidgetsFoundException(msg, list);
}
return ((MultiMatcher)m).bestMatch(list);
}
return (Widget)found.iterator().next();
}
private void informExceptionListeners(String description) {
for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) {
((IExceptionListener)iter.next()).preException(description);
}
}
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);
}
// if (!searchAll) {
// return;
// }
}
}
}
public void dbPrintWidgets() {
if (hierarchy instanceof SWTHierarchy) {
((SWTHierarchy)hierarchy).dbPrintWidgets();
}
}
public void printWidgets() {
dbPrintWidgets();
}
///////////////////////////////////////////////////////////////////////////
//
// Timing
//
///////////////////////////////////////////////////////////////////////////
private static void pause(int ms) {
try { Thread.sleep(ms); } catch(InterruptedException ie) { }
}
}