/*******************************************************************************
* 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.abbot.matcher;
import org.eclipse.swt.widgets.Widget;
import abbot.finder.matchers.swt.CompositeMatcher;
import abbot.finder.swt.Matcher;
import com.windowtester.runtime.swt.internal.finder.WidgetLocatorService;
/**
*
* A matcher that matches widgets first against a target matcher (which might match
* a given class and possibly name or label) and then by checking that parent criteria are met.
* <p>
* HierarchyMatchers are handy in making matches based on a widget's location in the
* widget hierarchy.
*
* For instance, to match a Text widget contained in the Group labeled "guests"
* in a Shell called "Party Planner", we might write a HierarchyMatcher like this:<p>
*
* <pre>
* new HierarchyMatcher(Text.class,
* new HierarchyMatcher(Group.class, "guests",
* new NameMatcher("Party Planner", Shell.class)));
*
* </pre>
*
* Where containment is not enough, we can augment with indexes. For example,
* suppose we want the second Text widget (indexes are zero-indexed):
*
* <pre>
* - group: -------------------
* | Text Text Text |
* ----------------------------
*
* new HierarchyMatcher(Text.class, 1,
* new HierarchyMatcher(Group.class, "group"));
* </pre>
*
* <b>Note:</b> using the 0-index for an "only child" will not have the desired effect. In fact, it will fail.
* Only-child matches should NOT specify an index value (though they could use -1).
* <p>
* In other words, <code>new HierarchyMatcher(Text.class, 0, new HierarchyMatcher(Group.class))</code>, will match the first Text
* child of a Group but only in the event that that Text has siblings. To match a Text only-child of a group,
* use a matcher constructs like this <code>new HierarchyMatcher(Text.class, Group.class)</code> (or possibly
* like this <code>new HierarchyMatcher(Text.class, -1, Group.class)</code>).
*
*/
public class HierarchyMatcher implements Matcher {
/** A matcher composed from target class and name info */
private final Matcher _matcher;
/** A matcher to check parent criteria. */
private final Matcher _parentMatcher;
/** The index that identifies the target with respect to its siblings in
* the parent's list of children.
*/
private int _index;
/** The default index value for an unspecified index */
private static final int DEFAULT_INDEX = -1;
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Constructors
//
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Create an instance.
* @param targetMatcher - a matcher to check target criteria.
* @param parentMatcher - a matcher to check parent criteria.
*/
public HierarchyMatcher(Matcher targetMatcher, Matcher parentMatcher) {
this(targetMatcher, DEFAULT_INDEX, parentMatcher);
}
/**
* Create an instance.
* @param targetMatcher - a matcher to check target criteria.
* @param index - index that locates child with respect to its parent (in the parent's
* list of children).
* @param parentMatcher - a matcher to check parent criteria.
*/
public HierarchyMatcher(Matcher targetMatcher, int index, Matcher parentMatcher) {
_matcher = targetMatcher;
_index = index;
_parentMatcher = parentMatcher;
}
//convenience constructors
/**
* Create an instance.
* @param cls - the class of the widget in question.
* @param nameOrLabel - the widget's name or label.
* @param parentMatcher - a matcher to check parent criteria.
*/
public HierarchyMatcher(Class cls, String nameOrLabel, Matcher parentMatcher) {
this(cls, nameOrLabel, DEFAULT_INDEX, parentMatcher);
}
/**
* Create an instance.
* @param cls - the class of the widget in question.
* @param nameOrLabel - the widget's name or label.
* @param index - the index of the widget in question.
* @param parentMatcher - a matcher to check parent criteria.
*/
public HierarchyMatcher(Class cls, String nameOrLabel, int index, Matcher parentMatcher) {
this(new CompositeMatcher(new Matcher[] { new ExactClassMatcher(cls),
new NameOrLabelMatcher(nameOrLabel) }),
index, parentMatcher);
}
/**
* Create an instance.
* @param cls - the class of the widget in question.
* @param parentMatcher - a matcher to check parent criteria.
*/
public HierarchyMatcher(Class cls, Matcher parentMatcher) {
this(cls, DEFAULT_INDEX, parentMatcher);
}
/**
* Create an instance.
* @param cls - the class of the widget in question.
* @param index - the index of the widget in question.
* @param parentMatcher - a matcher to check parent criteria.
*/
public HierarchyMatcher(Class cls, int index, Matcher parentMatcher) {
this(new ExactClassMatcher(cls), index, parentMatcher);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Matching
//
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Check for a match by first checking if the class (and possibly name or label)
* match and then checking that parent criteria are met.
* @see abbot.finder.swt.Matcher#matches(org.eclipse.swt.widgets.Widget)
*/
public boolean matches(Widget widget) {
//is this log-worthy?
if (widget == null)
return false;
/*
* various fast-fail optimizations
*/
//check target matcher
if (!_matcher.matches(widget))
return false; //fast fail
//if target matches, turn to parent
//first, short-circuit if there is no parent matcher
if (_parentMatcher == null)
return true;
//next, check parent matcher
WidgetLocatorService infoService = new WidgetLocatorService();
Widget parent = infoService.getParent(widget);
if (parent == null)
return false; //if there is no parent, but there is a matcher, return false
if (!_parentMatcher.matches(parent))
return false; //fail if parent does not match
//lastly, check index
return infoService.getIndex(widget, parent) == _index;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Accessors
//
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Get the matcher identifying the target of this match.
*/
public Matcher getTargetMatcher() {
return _matcher;
}
/**
* Get the matcher identifying the parent of the target of this match.
*/
public Matcher getParentMatcher() {
return _parentMatcher;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Debugging
//
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "Hierarchy matcher (" + _matcher + ", " + _parentMatcher + ")";
}
}