/******************************************************************************* * Breakout Cave Survey Visualizer * * Copyright (C) 2014 James Edwards * * jedwards8 at fastmail dot fm * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *******************************************************************************/ package org.andork.ui.test; import java.awt.Component; import java.awt.Container; import java.awt.Rectangle; import java.awt.Window; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import java.util.Queue; import java.util.Set; import java.util.regex.Pattern; import javax.swing.AbstractButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JRadioButton; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.text.JTextComponent; import org.andork.awt.CheckEDT; import org.andork.collect.ArrayIterator; import org.andork.collect.CollectionUtils; import org.andork.collect.CompoundComparator; import org.andork.collect.EasyIterator; import org.andork.collect.FilteringIterator; import org.andork.collect.InverseComparator; import org.andork.ui.test.ConfigurableStringComparator.Option; import org.andork.util.Java7; /** * Helps you find components easily (and legibly) for automated GUI testing. * ComponentFinder is like a nested iterator chain; it iterates over components, * and fluent methods wrap it in other ComponentFinders that filter out * undesired results.<br> * <br> * To get a ComponentFinder, use {@link #only(Component)} or {@link #windows()}. * Then chain filter methods (e.g. {@link #ofType(Class)}) onto that call, and * finally get the results with {@link #next()}. * * @author andy.edwards * * @param <C> * the type of component to find */ public abstract class ComponentFinder<C extends Component> implements Iterable<C> { private static class AncestorFinder extends ComponentFinder<Component> { private ComponentFinder<?> wrapped; public AncestorFinder(ComponentFinder<?> wrapped) { super(); this.wrapped = wrapped; } @Override public Iterator<Component> iterator() { return new EasyIterator<Component>() { private Component next = null; private Iterator<? extends Component> wrappedIter = wrapped.iterator(); @Override protected Component nextOrNull() { while (next == null && wrappedIter.hasNext()) { next = wrappedIter.next().getParent(); } Component result = next; if (next != null) { next = next.getParent(); } return result; } }; } } public static class ComponentCenterDistanceComparator implements Comparator<Component> { Component referenceComp; int x, y; protected ComponentCenterDistanceComparator(Component referenceComp, int x, int y) { super(); this.referenceComp = referenceComp; this.x = x; this.y = y; } @Override public int compare(Component o1, Component o2) { Rectangle r1 = o1 instanceof JComponent ? ((JComponent) o1).getVisibleRect() : SwingUtilities.getLocalBounds(o1); Rectangle r2 = o2 instanceof JComponent ? ((JComponent) o2).getVisibleRect() : SwingUtilities.getLocalBounds(o2); r1 = SwingUtilities.convertRectangle(o1, r1, referenceComp); r2 = SwingUtilities.convertRectangle(o2, r2, referenceComp); int dx1 = r1.x + r1.width / 2 - x; int dy1 = r1.y + r1.height / 2 - y; int dx2 = r2.x + r2.width / 2 - x; int dy2 = r2.y + r2.height / 2 - y; int d1 = dx1 * dx1 + dy1 * dy1; int d2 = dx2 * dx2 + dy2 * dy2; int result = d1 - d2; if (result == 0) { result = r1.x - r2.x; } if (result == 0) { result = r1.y - r2.y; } return result; } } public static class ComponentCenterXComparator implements Comparator<Component> { protected ComponentCenterXComparator() { super(); } @Override public int compare(Component o1, Component o2) { Rectangle r1 = SwingUtilities.getLocalBounds(o1); Rectangle r2 = SwingUtilities.getLocalBounds(o2); r2 = SwingUtilities.convertRectangle(o2, r2, o1); return r1.x - r2.x; } } public static class ComponentCenterXDistanceComparator implements Comparator<Component> { Component referenceComp; int x; protected ComponentCenterXDistanceComparator(Component comp, int x) { super(); this.referenceComp = comp; this.x = x; } @Override public int compare(Component o1, Component o2) { Rectangle r1 = SwingUtilities.getLocalBounds(o1); Rectangle r2 = SwingUtilities.getLocalBounds(o2); r1 = SwingUtilities.convertRectangle(o1, r1, referenceComp); r2 = SwingUtilities.convertRectangle(o2, r2, referenceComp); int result = Math.abs(r1.x + r1.width / 2 - x) - Math.abs(r2.x + r2.width / 2 - x); return result == 0 ? r1.y - r2.y : result; } } public static class ComponentCenterYComparator implements Comparator<Component> { protected ComponentCenterYComparator() { super(); } @Override public int compare(Component o1, Component o2) { Rectangle r1 = SwingUtilities.getLocalBounds(o1); Rectangle r2 = SwingUtilities.getLocalBounds(o2); r2 = SwingUtilities.convertRectangle(o2, r2, o1); return r1.y - r2.y; } } public static class ComponentCenterYDistanceComparator implements Comparator<Component> { Component referenceComp; int y; protected ComponentCenterYDistanceComparator(Component comp, int y) { super(); this.referenceComp = comp; this.y = y; } @Override public int compare(Component o1, Component o2) { Rectangle r1 = SwingUtilities.getLocalBounds(o1); Rectangle r2 = SwingUtilities.getLocalBounds(o2); r1 = SwingUtilities.convertRectangle(o1, r1, referenceComp); r2 = SwingUtilities.convertRectangle(o2, r2, referenceComp); int result = Math.abs(r1.y + r1.height / 2 - y) - Math.abs(r2.y + r2.height / 2 - y); return result == 0 ? r1.y - r2.y : result; } } private static class DescendantFinder extends ComponentFinder<Component> { private ComponentFinder<?> wrapped; public DescendantFinder(ComponentFinder<?> wrapped) { super(); this.wrapped = wrapped; } @Override public Iterator<Component> iterator() { return new EasyIterator<Component>() { private final Set<Component> visited = new HashSet<Component>(); private final Queue<Component> queue = new LinkedList<Component>(); private Iterator<? extends Component> wrappedIter = wrapped.iterator(); @Override protected Component nextOrNull() { if (queue.isEmpty() && wrappedIter.hasNext()) { Component next = wrappedIter.next(); if (next instanceof Container) { for (Component comp : ((Container) next).getComponents()) { if (visited.add(comp)) { queue.add(comp); } } } } Component result = queue.poll(); if (result instanceof Container) { for (Component comp : ((Container) result).getComponents()) { if (visited.add(comp)) { queue.add(comp); } } } return result; } }; } } public static abstract class FilteringComponentFinder<C extends Component> extends ComponentFinder<C> { private final ComponentFinder<C> wrapped; protected FilteringComponentFinder(ComponentFinder<C> wrapped) { super(); this.wrapped = wrapped; } @Override public Iterator<C> iterator() { return new FilteringIterator<C>(wrapped.iterator()) { @Override protected boolean matches(C next) { return FilteringComponentFinder.this.matches(next); } }; } public abstract boolean matches(C comp); } private static class OwnedWindowFinder extends WindowFinder<Window> { private final Window target; private OwnedWindowFinder(Window target) { super(); this.target = target; } @Override public Iterator<Window> iterator() { return new ArrayIterator<Window>(target.getOwnedWindows()); } } /** * A {@code ComponentFinder} that finds one component (or none). It provides * a {@link #get()} method to get the component. * * @author andy.edwards * @param <C> * the component type being found */ public static class SingletonComponentFinder<C extends Component> extends ComponentFinder<C> { private C comp; protected SingletonComponentFinder(C comp) { this.comp = comp; } public C get() { return comp; } @Override public Iterator<C> iterator() { return new EasyIterator<C>() { C next = comp; @Override protected C nextOrNull() { C next = this.next; this.next = null; return next; } }; } } public static class SortingComponentFinder<C extends Component> extends ComponentFinder<C> { ComponentFinder<C> parent; Comparator<? super C> comparator; public SortingComponentFinder(ComponentFinder<C> parent, Comparator<? super C> comparator) { super(); this.parent = parent; this.comparator = comparator; } @Override public Iterator<C> iterator() { return CollectionUtils.toSortedArrayList(parent, comparator).iterator(); } } /** * A {@code ComponentFinder} that finds {@link Window}s. It provides some * additional methods to filter out certain windows. * * @author andy.edwards * * @param <W> * the type of window to find. */ public static abstract class WindowFinder<W extends Window> extends ComponentFinder<W> { /** * Wraps the given {@code ComponentFinder} with a new * {@code WindowFinder}, enabling you to use {@code WindowFinder}'s * filtering methods. * * @param componentFinder * the {@code ComponentFinder} to wrap. * @return a new {@code WindowFinder} wrapping {@code componentFinder}. */ public static <W extends Window> WindowFinder<W> decorate(final ComponentFinder<W> componentFinder) { return new WindowFinder<W>() { @Override public Iterator<W> iterator() { return componentFinder.iterator(); } }; } protected WindowFinder() { } /** * @return a {@code WindowFinder} that narrows the results down to owned * windows. */ public WindowFinder<W> owned() { return decorate(new FilteringComponentFinder<W>(this) { @Override public boolean matches(W comp) { return comp.getOwner() != null; } }); } /** * @return a {@code WindowFinder} that narrows the results down to * ownerless windows. */ public WindowFinder<W> ownerless() { return decorate(new FilteringComponentFinder<W>(this) { @Override public boolean matches(W comp) { return comp.getOwner() == null; } }); } @Override public WindowFinder<W> waitUntilFound() throws InterruptedException { super.waitUntilFound(); return this; } } /** * @param comp * the component to find ancestors of. * @return a {@code ComponentFinder} that finds all of {@code comp}'s{ * ancestors. */ public static ComponentFinder<?> ancestorsOf(Component comp) { return only(comp).ancestors(); } public static ComponentFinder<AbstractButton> buttonsIn(Component parent) { return descendantsOf(parent).ofType(AbstractButton.class); } public static ComponentFinder<JCheckBox> checkBoxesIn(Component parent) { return descendantsOf(parent).ofType(JCheckBox.class); } public static ComponentFinder<JComboBox> comboBoxesIn(Component parent) { return descendantsOf(parent).ofType(JComboBox.class); } /** * @param comp * the component to find descendants of. * @return a {@code ComponentFinder} that finds all of {@code comp}'s * descendants. */ public static ComponentFinder<?> descendantsOf(Component comp) { return only(comp).descendants(); } private static String getText(Component comp) { if (comp instanceof AbstractButton) { return ((AbstractButton) comp).getText(); } else if (comp instanceof JLabel) { return ((JLabel) comp).getText(); } else if (comp instanceof JTextComponent) { return ((JTextComponent) comp).getText(); } throw new IllegalArgumentException(comp.getClass() + " components don't have text"); } private static boolean hasText(Component comp) { return comp instanceof AbstractButton || comp instanceof JLabel || comp instanceof JTextComponent; } public static ComponentFinder<JLabel> jlabelsIn(Component parent) { return descendantsOf(parent).ofType(JLabel.class); } public static <C extends Component> SingletonComponentFinder<C> only(final C comp) { return new SingletonComponentFinder<C>(comp); } public static ComponentFinder<JRadioButton> radioButtonsIn(Component parent) { return descendantsOf(parent).ofType(JRadioButton.class); } public static TableFinder<JTable> tablesIn(Component parent) { return descendantsOf(parent).tables(); } public static ComponentFinder<JTextComponent> textComponentsIn(Component parent) { return descendantsOf(parent).ofType(JTextComponent.class); } public static WindowFinder<Window> windowsOwnedBy(Window window) { return new OwnedWindowFinder(window); } protected ComponentFinder() { } public ComponentFinder<C> above(final Component c1) { final Rectangle r1 = c1.getBounds(); return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C c2) { Rectangle r2 = c2.getBounds(); r2 = SwingUtilities.convertRectangle(c2, r2, c1); return r2.getMaxY() < r1.y; } }; } /** * Adds all components found to the given {@link Collection}. NOTE: this * method blocks waiting for the AWT event dispatch thread. * * @param collection * the collection to add the found components to. */ public void addAllTo(final Collection<? super C> collection) { new DoSwing() { @Override public void run() { CollectionUtils.addAll(collection, ComponentFinder.this); } }; } /** * Gets a list of all components found. NOTE: this method blocks waiting for * the AWT event dispatch thread. * * @param collection * the collection to add the found components to. * * @return a {@link List} of all components found. */ public List<C> all() { ArrayList<C> result = new ArrayList<C>(); addAllTo(result); return result; } /** * @return a {@code ComponentFinder} that finds all of this * {@code ComponentFinder}'s components' ancestors. */ public ComponentFinder<?> ancestors() { return new AncestorFinder(this); } public ComponentFinder<C> below(final Component c1) { final Rectangle r1 = c1.getBounds(); return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C c2) { Rectangle r2 = c2.getBounds(); r2 = SwingUtilities.convertRectangle(c2, r2, c1); return r2.y > r1.getMaxY(); } }; } public ComponentFinder<C> bottommost() { return sort(new InverseComparator<C>(new ComponentCenterYComparator())); } public ComponentFinder<C> closestTo(Component center) { return exclude(center).closestTo(center, center.getWidth() / 2, center.getHeight() / 2); } /** * @param referenceComp * the component to find the component closest to. * @return a {@code SingletonComponentFinder} that narrows the results to * the component that whose center is closest to {@code comp}'s * center, or {@code null} if this {@code ComponentFinder} has no * results. */ private ComponentFinder<C> closestTo(final Component referenceComp, final int x, final int y) { return sort(new ComponentCenterDistanceComparator(referenceComp, x, y)); } public ComponentFinder<C> closestToX(Component center) { return exclude(center).closestToX(center, center.getWidth() / 2); } private ComponentFinder<C> closestToX(final Component referenceComp, final int x) { return sort(new ComponentCenterXDistanceComparator(referenceComp, x)); } public ComponentFinder<C> closestToY(Component center) { return exclude(center).closestToY(center, center.getHeight() / 2); } private ComponentFinder<C> closestToY(final Component referenceComp, final int y) { return sort(new ComponentCenterYDistanceComparator(referenceComp, y)); } /** * @param text * a text to match (may be null). * @return a {@code ComponentFinder} that narrows the results to components * containing the given text. */ public ComponentFinder<C> containingText(final String text) { return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { if (hasText(comp)) { String compText = getText(comp); if (compText != null) { return compText.contains(text); } } return false; } }; } /** * @param text * a text to match (may be null). * @param options * the {@link Option}s for string comparison. * @return a {@code ComponentFinder} that narrows the results to components * containing the given text. */ public ComponentFinder<C> containingText(final String text, ConfigurableStringComparator.Option... options) { final ConfigurableStringComparator comparator = new ConfigurableStringComparator(options); return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { if (hasText(comp)) { String compText = getText(comp); if (compText != null) { return comparator.contains(compText, text); } } return false; } }; } /** * @return a {@code ComponentFinder} that finds all of this * {@code ComponentFinder}'s components' descendants. */ public ComponentFinder<?> descendants() { return new DescendantFinder(this); } /** * @return a {@code ComponentFinder} that narrows the results to * {@link Component#isEnabled() disabled} components. */ public ComponentFinder<C> disabled() { return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { return !comp.isEnabled(); } }; }; /** * @return a {@code ComponentFinder} that narrows the results to * {@link Component#isEnabled() enabled} components. */ public ComponentFinder<C> enabled() { return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { return comp.isEnabled(); } }; }; /** * Excludes the given component from the results. * * @param toExclude * the component to exclude. * @return a {@link ComponentFinder} that excludes the given component from * the results. */ public ComponentFinder<C> exclude(final Component toExclude) { return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { return comp != toExclude; } }; } /** * Excludes given components from the results. * * @param toExclude * the set of components to exclude. * @return a {@link ComponentFinder} that excludes the given components from * the results. */ public ComponentFinder<C> exclude(final Set<? super C> toExclude) { return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { return !toExclude.contains(comp); } }; } /** * Gets the first component found. If no component was found an exception * will be thrown. NOTE: This method blocks waiting for the AWT event * dispatch thread. * * @return the first component found. * @throws NoSuchElementException * if no components were found. */ public C first() { return new DoSwingR<C>() { @Override public C doRun() { return iterator().next(); } }.result(); } /** * Gets the first component found, or {@code null} if no components were * found.NOTE: This method blocks waiting for the AWT event dispatch thread. * * @return the first component found, or {@code null} if no components were * found. */ public C firstOrNull() { return new DoSwingR<C>() { @Override public C doRun() { Iterator<C> i = iterator(); if (i.hasNext()) { return i.next(); } return null; } }.result(); } /** * Gets the <b>i</b>th component found. NOTE: This method blocks waiting for * the AWT event dispatch thread. * * @param i * the index of the component to get * @return the {@code i}'th component found. * @throws IndexOutOfBoundsException * if i is >= the number of components found. */ public C get(final int i) { return new DoSwingR<C>() { @Override public C doRun() { int k = 0; for (C comp : ComponentFinder.this) { if (k++ == i) { return comp; } } throw new IndexOutOfBoundsException("Not enough components found for index: " + i); } }.result(); } /** * Determines if any components are found. NOTE: this method blocks waiting * for the AWT event dispatch thread. * * @return {@code true} if any components are found, {@code false} * otherwise. */ public boolean isAnyFound() { return new DoSwingR<Boolean>() { @Override public Boolean doRun() { return iterator().hasNext(); } }.result(); } public ComponentFinder<C> leftmost() { return sort(new ComponentCenterXComparator()); } public ComponentFinder<C> leftOf(final Component c1) { final Rectangle r1 = c1.getBounds(); return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C c2) { Rectangle r2 = c2.getBounds(); r2 = SwingUtilities.convertRectangle(c2, r2, c1); return r2.getMaxX() < r1.x; } }; } /** * @param name * the name to match (may be null). * @return a {@code ComponentFinder} that narrows the results to components * with the given name. */ public ComponentFinder<C> named(final String name) { return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { return Java7.Objects.equals(name, comp.getName()); } }; } /** * @param cls * the type to match. * @return a {@code ComponentFinder} that narrows the results to components * of the given type. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public <D extends Component> ComponentFinder<D> ofType(final Class<D> cls) { return new FilteringComponentFinder(this) { @Override public boolean matches(Component comp) { return cls.isInstance(comp); } }; } public ComponentFinder<C> ofType2(final Class<?> cls) { return new FilteringComponentFinder<C>(this) { @Override public boolean matches(Component comp) { return cls.isInstance(comp); } }; } /** * Gets the only component found. If there are more or less than one * components an exception will be thrown. NOTE: This method blocks waiting * for the AWT event dispatch thread. * * @return the only component found. * @throws NoSuchElementException * if no components were found. * @throws IllegalStateException * if more than one component was found. */ public C only() { return new DoSwingR<C>() { @Override public C doRun() { Iterator<C> i = iterator(); C result = i.next(); if (i.hasNext()) { throw new IllegalStateException("More than one component was found"); } return result; } }.result(); } /** * Prints all components found to {@link System#out}. */ public void printAll() { printAll(System.out); } /** * Prints all components found to the given {@link PrintStream} on * successive lines. * * @param out * the {@code PrintStream} to print to. */ public void printAll(final PrintStream out) { new DoSwing() { @Override public void run() { for (Component next : ComponentFinder.this) { out.println(next); } } }; } public ComponentFinder<C> rightmost() { return sort(new InverseComparator<C>(new ComponentCenterXComparator())); } public ComponentFinder<C> rightOf(final Component c1) { final Rectangle r1 = c1.getBounds(); return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C c2) { Rectangle r2 = c2.getBounds(); r2 = SwingUtilities.convertRectangle(c2, r2, c1); return r2.x > r1.getMaxX(); } }; } /** * @return a {@code ComponentFinder} that narrows the results to * {@link Component#isShowing() showing} components. */ public ComponentFinder<C> showing() { return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { return comp.isShowing(); } }; } /** * @param comparator * the comparator to sort with. * @return a {@code ComponentFinder} that sorts the results to using * {@code comparator}. If this {@code ComponentFinder} is also a * {@code SortingComponentFinder}, the result will sub-sort the * results of this sort. */ public SortingComponentFinder<C> sort(Comparator<? super C> comparator) { if (this instanceof SortingComponentFinder) { SortingComponentFinder<C> thisSorting = (SortingComponentFinder<C>) this; return new SortingComponentFinder<C>(thisSorting.parent, new CompoundComparator( thisSorting.comparator, comparator)); } else { return new SortingComponentFinder<C>(this, comparator); } } public TableFinder<JTable> tables() { return TableFinder.cast(ofType(JTable.class)); } public ComponentFinder<C> topmost() { return sort(new ComponentCenterYComparator()); } /** * Blocks the current thread (which must not be the AWT event dispatch * thread) until at least one component is found. However, the component may * cease to exist before/after this method returns. * * @return this {@link ComponentFinder}, for chaining. * @throws MustNotBeCalledOnEDTException * if this method was called on the EDT. * @throws InterruptedException * if the current thread was interrupted while waiting for the * component to be found. */ public ComponentFinder<C> waitUntilFound() throws InterruptedException { CheckEDT.checkNotEDT(); while (!isAnyFound()) { Thread.sleep(100); } return this; } /** * Blocks the current thread (which must not be the AWT event dispatch * thread) until no components are found. However, new components that would * be found may come into existence before/after this method returns. * * @return this {@link ComponentFinder}, for chaining. * @throws MustNotBeCalledOnEDTException * if this method was called on the EDT. * @throws InterruptedException * if the current thread was interrupted while waiting for no * components to be found. */ public ComponentFinder<C> waitUntilNotFound() throws InterruptedException { CheckEDT.checkNotEDT(); while (isAnyFound()) { Thread.sleep(100); } return this; } /** * @return a {@code ComponentFinder} that narrows the results to the * {@link Component#isShowing() focused} component. */ public ComponentFinder<C> withFocus() { return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { return comp.hasFocus(); } }; } /** * @param regex * a regular expression to match component names. * @return a {@code ComponentFinder} that narrows the results to components * that have a non-null {@link Component#getName() name} matching * the given regular expression. */ public ComponentFinder<C> withNameMatching(final String regex) { return new FilteringComponentFinder<C>(this) { Pattern pattern = Pattern.compile(regex); @Override public boolean matches(C comp) { return comp.getName() != null && pattern.matcher(comp.getName()).matches(); } }; } /** * @param regex * a regular expression to match component names. * @param flags * flags for {@link Pattern#compile(String, int)}. * @return a {@code ComponentFinder} that narrows the results to components * that have a non-null {@link Component#getName() name} matching * the given regular expression. */ public ComponentFinder<C> withNameMatching(final String regex, final int flags) { return new FilteringComponentFinder<C>(this) { Pattern pattern = Pattern.compile(regex, flags); @Override public boolean matches(C comp) { return comp.getName() != null && pattern.matcher(comp.getName()).matches(); } }; } /** * @param text * a text to match (may be null). * @return a {@code ComponentFinder} that narrows the results to components * with the given text. */ public ComponentFinder<C> withText(final String text) { return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { return hasText(comp) && Java7.Objects.equals(text, getText(comp)); } }; } /** * @param text * a text to match (may be null). * @param options * the {@link Option}s for string comparison. * @return a {@code ComponentFinder} that narrows the results to components * with the given text. */ public ComponentFinder<C> withText(final String text, ConfigurableStringComparator.Option... options) { final ConfigurableStringComparator comparator = new ConfigurableStringComparator(options); return new FilteringComponentFinder<C>(this) { @Override public boolean matches(C comp) { return hasText(comp) && comparator.equals(text, getText(comp)); } }; } /** * @param regex * a regular expression to match component text. * @return a {@code ComponentFinder} that narrows the results to components * that have a non-null text matching the given regular expression. */ public ComponentFinder<C> withTextMatching(final String regex) { return new FilteringComponentFinder<C>(this) { Pattern pattern = Pattern.compile(regex); @Override public boolean matches(C comp) { return hasText(comp) && getText(comp) != null && pattern.matcher(getText(comp)).matches(); } }; } /** * @param regex * a regular expression to match component text. * @param flags * flags for {@link Pattern#compile(String, int)}. * @return a {@code ComponentFinder} that narrows the results to components * that have a non-null text matching the given regular expression. */ public ComponentFinder<C> withTextMatching(final String regex, final int flags) { return new FilteringComponentFinder<C>(this) { Pattern pattern = Pattern.compile(regex, flags); @Override public boolean matches(C comp) { return hasText(comp) && getText(comp) != null && pattern.matcher(getText(comp)).matches(); } }; } }