/******************************************************************************* * 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.internal.swing.locator; import java.awt.Component; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.swing.AbstractButton; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import abbot.finder.AWTHierarchy; import abbot.finder.ComponentFinder; import abbot.finder.Hierarchy; import abbot.finder.Matcher; import com.windowtester.runtime.swing.SwingWidgetLocator; /*** * To build the WidgetLocator based on the hierarchy of the widget. * First, try a simple WidgetLocator, like WidgetLocator(cls). If there * is more than one match with this locator, then elaborate by adding * parent info. Do this till we get a unique WidgetLocator, or else * return null, to indicate failure. * * Only the active window is considered when building the WidgetLocator. * * based on com.windowtester.swt.locator.ScopedWidgetIdentifierBuilder * */ public class ScopedComponentIdentifierBuilder implements IWidgetIdentifierStrategy { /** For use in checking for unique matches */ private final ComponentFinder _finder = BasicFinder2.getDefault(); /** For use in elaboration (created once per call it identify) */ private Hierarchy _hierarchy = AWTHierarchy.getDefault(); /** * Generates a <code>WidgetLocator</code> that uniquely identifies this widget * relative to the current widget hierarchy. If no uniquely identifying locator is found * <code>null</code> is returned. * */ public SwingWidgetLocator identify(Component w) { // get locator describing the target widget itself SwingWidgetLocator locator = getLocator(w); // get the top level frame/dailog for the component // WidgetLocator scope = findTopLevelScope(w); // locator.setParentInfo(scope); //note: it can be null Matcher matcher = MatcherFactory.getMatcher(locator); // elaborate until done (notice: null locator indicates a failure) // Note: not going to look only in active shell, since the find has not // been implemented this way. // while(!isUniquelyIdentifying(matcher, _activeWindow) && locator != null) { while(!isUniquelyIdentifying(matcher) && locator != null) { locator = elaborate(locator, w); if (locator != null) matcher = MatcherFactory.getMatcher(locator); } return locator; } /** * Create an (unelaborated) info object for this widget. * @param w - the widget to describe. * @return an info object that describes the widget. */ private SwingWidgetLocator getLocator(Component w) { if (w == null) { return null; } /** * CCombos require special treatment as the chevron is a button and receives the click event. * Instead of that button, we want to be identifying the combo itself (the button's parent). * TODO!pq: is this true for JCombos? */ if (w instanceof JButton) { Component parent = _hierarchy.getParent(w); if (parent instanceof JComboBox) w = parent; } // TODO : implement this functionality //WidgetLocator locator = checkForLabeledLocatorCase(w); return WidgetLocatorFactory.getInstance().create(w); } /** * Find top-level scope (Frame) -- might be <code>null</code>. */ /* private WidgetLocator findTopLevelScope(Component w) { WidgetLocator rootLocator = null; Collection roots = _hierarchy.getRoots(); boolean found = false; Iterator it = roots.iterator(); while (it.hasNext() && !found){ Object o = it.next(); // if (o instanceof java.awt.Container){ if (((Container)o).isAncestorOf(w)){ found = true; if (o instanceof Frame) rootLocator = new WidgetLocator(Frame.class,((Frame)o).getTitle()); else if (o instanceof Dialog) rootLocator = new WidgetLocator(Dialog.class,((Dialog)o).getTitle()); } } return rootLocator; } */ /** * Does this macther uniquely identify a widget in this Hierarchy * TODO: limit search to active window */ // private boolean isUniquelyIdentifying(Matcher matcher, Window window) { private boolean isUniquelyIdentifying(Matcher matcher) { /* try { _finder.find(matcher); return true; } catch (ComponentNotFoundException e) { System.out.println("Component not found exception"); e.printStackTrace(); // do nothing, return false } catch (MultipleComponentsFoundException e) { // do nothing, return false System.out.println("multiple Components found exception"); e.printStackTrace(); }*/ int result = ((BasicFinder2)_finder).findAll(matcher); if ( result == -1) return false; else return true; } /** * Takes a WidgetLocator object and elaborates on it until is uniquely identifying. * If no uniquely identifying locator can be inferred, a <code>null</code> value * is returned. * */ private SwingWidgetLocator elaborate(SwingWidgetLocator info, Component w) { //a pointer to the original locator for returning (in the success case) SwingWidgetLocator root = info; boolean elaborated = false; SwingWidgetLocator parentInfo = null; while(!elaborated) { //get parent info of the current (top-most) locator //note[!pq]: we need this cast but it's safe since swing locators //can only contain other swing locators parentInfo = (SwingWidgetLocator) info.getParentInfo(); //get the parent of the current (top-most) widget in the target's hierarchy Component parent = _hierarchy.getParent(w); /* * if the parent is null at this point, we've failed to elaborate and we * need to just return */ if (parent == null) { System.out.println("Failed, returning null"); return null; } //if the parent is a scope locator, connect to it //if (isScopeLocator(parentInfo)) { // handleScopeLocatorCase(info, parentInfo, w, parent); // elaborated = true; //if the parentinfo is null, create a new parent and attach it //} else if (parentInfo == null) { if (parentInfo == null) { info.setParentInfo(getLocator(parent)); setIndex(info, w, parent); elaborated = true; } /* * setup for next iteration */ w = parent; info = parentInfo; } return root; } /** * Get this widget's index relative to its parent widget. * <p>Note that indexes only matter in the case where there is at least one sibling * that matches the target widget exactly (by class and name/label). Other cases * return -1. * @param w - the widget * @param parent - the parent widget * @return an index, or -1 if is the only child * FIXME: return 0 in only-child case */ public int getIndex(Component w, Component parent) { List children = getChildren(parent, w.getClass()); int count = 0; //the match counter int index = -1; //the index of our target widget //only child case... if (children.size() == 1) return index; for (Iterator iter = children.iterator(); iter.hasNext();) { Component child = (Component)iter.next(); //using exact matches... if (child.getClass().isAssignableFrom(w.getClass()) && w.getClass().isAssignableFrom(child.getClass())) { //also check for nameOrLabelMatch if (nameAndOrLabelDataMatch(w, child)) ++count; } if (child == w) index = count-1; //indexes are zero-indexed } return (count > 1) ? index : -1; //throw new IllegalStateException("unfound child"); } /** * Get the children (of a particular class) of a given parent widget. * @param parent - the parent widget * @param cls - the class of child widgets of interest * @return a list of children */ public List getChildren(Component parent, Class cls) { Collection children = _hierarchy.getComponents(parent); //prune non-exact class matches List pruned = new ArrayList(); for (Iterator iter = children.iterator(); iter.hasNext();) { Object child = iter.next(); Class childClass = child.getClass(); if (cls.isAssignableFrom(childClass) && childClass.isAssignableFrom(cls)) pruned.add(child); } return pruned; } private boolean nameAndOrLabelDataMatch(Component c1, Component c2){ String name1 = c1.getName(); String name2 = c2.getName(); if ((name1 != null) || (name2 != null)){ if (name1 == null) return name2 == null; return name1.equals(name2); } String text1 = getWidgetText(c1); String text2 = getWidgetText(c2); if (text1 == null) return text2 == null; return text1.equals(text2); } /** * Set the index for this locator that describes the given widget relative to the given parent. */ private void setIndex(SwingWidgetLocator locator, Component currentWidget, Component widgetParent) { int index = getIndex(currentWidget,widgetParent); if (index != SwingWidgetLocator.UNASSIGNED) locator.setIndex(index); } /** * Check to see if the given locator is a scope locator. */ /* private boolean isScopeLocator(WidgetLocator locator) { return (locator.getTargetClass()== Frame.class) || (locator.getTargetClass()== Dialog.class); } */ /** * Handle case where parent locator is a scoping locator. */ /* private void handleScopeLocatorCase(WidgetLocator currentTopLocator, WidgetLocator scopeLocator, Component currentWidget, Component widgetParent) { //1. create a new parent WidgetLocator newParent = getLocator(widgetParent); //attatch it to our old top locator currentTopLocator.setParentInfo(newParent); setIndex(currentTopLocator, currentWidget, widgetParent); int scopeRelativeIndex = getIndex(currentWidget, scopeLocator); if (scopeRelativeIndex != WidgetLocator.UNASSIGNED) newParent.setIndex(scopeRelativeIndex); newParent.setParentInfo(scopeLocator); } */ public int getIndex(Component w, SwingWidgetLocator scopeLocator) { //TODO: decide whether we want frame/dialog relative indexes return SwingWidgetLocator.UNASSIGNED; } /** * Extract the text from the given widget. * @param w - the widget in question * @return the widget's text */ public String getWidgetText(Component w) { if (w instanceof AbstractButton) { return (((AbstractButton)w).getText()); } if (w instanceof JLabel) { return (((JLabel)w).getText()); } //fall through .... return null; } }