/*==========================================================================*\ | $Id: GObjectFilter.java,v 1.4 2011/06/05 22:47:01 aallowat Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2007-2010 Virginia Tech | | This file is part of the Student-Library. | | The Student-Library is free software; you can redistribute it and/or | modify it under the terms of the GNU Lesser General Public License as | published by the Free Software Foundation; either version 3 of the | License, or (at your option) any later version. | | The Student-Library 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 Lesser General Public License for more details. | | You should have received a copy of the GNU Lesser General Public License | along with the Student-Library; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package student.testingsupport; import acm.graphics.GFillable; import acm.graphics.GLabel; import acm.graphics.GLine; import acm.graphics.GObject; import acm.graphics.GPoint; import java.awt.Color; import java.awt.Rectangle; //------------------------------------------------------------------------- /** * This class Represents a filter or query that can be used to describe * a {@link GObject} when searching. Note that the methods and fields * in this class are designed specifically to support a natural, readable, * boolean expression "mini-language" for use in describing a single * {@link GObject} (or group of {@link GObject}s) by its (or their) * properties. As a result, it does violate some conventions regarding * the use of public fields (although note that all here are immutable) * and occasionally even the naming conventions for constants (e.g., * <code>where</code>). However, breaking these conventions is necessary * in this class in order to support the more natural syntax for filter * expressions, and so was deemed a better design choice. * <p> * Client classes that wish to use these filters should add the * following static import directive: * </p> * <pre> * import static student.testingsupport.GObjectFilter.ClientImports.*; * </pre> * <p> * Note that the {@link student.GraphicTestCase} class already re-exports the * items defined in the {@link ClientImports} nested class, so GUI test * cases should <em>not</em> include the static import. * </p> * <p> * The expressions that you can create with this class are designed to * represent "filters" or boolean predicates that can be applied to a * GObject, returning true if the GObject "matches" the filter or false * if the component does not match. * </p> * <p> * Often, a filter object is created solely for the purpose of passing * the filter into some other operation, such as a search operation. For * example, the student.GraphicTestCase class provides a * {@link student.GraphicTestCase#getGObject(Class,GObjectFilter) getGObject()} * method that takes a filter as a parameter. For the examples below, we * will use <code>getGObject()</code> as the context, specifying each * filter as an argument value in a call to that method. * </p> * <p> * The basic principles for using this class are as follows: * </p> * <ul> * <li><p>Never try to create a GObjectFilter object directly. Instead, * always write something that looks like a boolean expression, and * that starts with the operator <code>where</code>:</p> * <pre> * GRect rect = getGObject(GRect.class, where.locationIs(25, 25)); * </pre></li> * <li><p>The basic properties you can check with filters include: * <code>locationIs()</code>, <code>textIs()</code>, * <code>visibilityIs()</code>, <code>filledIs()</code>, * <code>widthIs()</code>, and <code>heightIs()</code>. They are * all used the same way:</p> * <pre> * GLabel name = getGObject(GLabel.class, where.textIs("name")); * GRect rect = getGObject(JLabel.class, where.locationIs(25, 25)); * </pre></li> * <li><p>You can combine filters using logical "and" as necessary:</p> * <pre> * GRect rect = getGObject(GRect, * where.locationIs(25, 25).and.visibilityIs(true).and.filledIs(true)); * </pre></li> * <li><p>You can also use "or":</p> * <pre> * GRect rect = getGObject(GRect, * where.locationIs(25, 25).or.visibilityIs(true).or.filledIs(true)); * </pre></li> * <li><p>Operators like "and" and "or" are interpreted strictly left to * right. There is <b>no precedence</b>, because of the way Java interprets * dot notation.</p> * <pre> * GRect rect = getGObject(GRect, * where.locationIs(25, 25).or.visibilityIs(true).and.filledIs(true)); * // means ((location = (25, 25) or visibility = true) and filled = true) * // note that the left operator is always evaluated first! * </pre> * <p>If you want to force a different order of evaluation * than strictly left-to-right, then use parentheses by writing the * appropriate operator as <code>and()</code> or <code>or()</code>. Just * be sure to start the new expression inside the parentheses with * <code>where</code>:</p> * <pre> * GRect rect = getGObject(GRect.class, * where.locationIs(25, 25).or(where.visibilityIs(true).and.filledIs(true))); * // now means (location = (25, 25) or (visibility = true and filled = true)) * // because of the extra parentheses used * </pre></li> * <li><p>Finally, you can even use "not" (logical negation), but it is * called like a method, so parentheses (and thus a leading <code>where) * are <em>always required</em> to make the intended extent of the negation * clear:</p> * <pre> * GRect rect = getGObject(GRect.class, * where.locationIs(25, 25).and.not(where.visibilityIs(true).or.filledIs(true))); * </pre></li> * </ul> * * @author Stephen Edwards * @author Last changed by $Author: aallowat $ * @version $Revision: 1.4 $, $Date: 2011/06/05 22:47:01 $ */ public abstract class GObjectFilter { //~ Instance/static variables ............................................. private String description; //~ Constructor ........................................................... // ---------------------------------------------------------- /** * Creates a new filter object. This constructor is not public, since * all filters are expected to be created using operators rather than * by calling new. * @param description A string description of this filter, used in * {@link #toString()}. */ protected GObjectFilter (String description) { this.description = description; } //~ Public Fields ......................................................... // These fields are public to afford a more natural syntax, although they // can never be manipulated since they are final and have no mutators. // They are instance fields instead of static fields, because that is // necessary for their semantics. // ---------------------------------------------------------- /** * The "and" operator for combining filters, designed to be used in * expressions like <code>where.nameIs("...").and.enabledIs(true)</code>. * This operator is implemented as a public field so that the simple * <code>.and.</code> notation can be used as a connective between * filters. If you want to use parentheses for grouping to define * the right argument, see {@link #and(GObjectFilter)} instead. */ public final BinaryOperator and = new BinaryOperator() { // ---------------------------------------------------------- @Override protected boolean combine(boolean leftResult, boolean rightResult) { return leftResult && rightResult; } // ---------------------------------------------------------- @Override protected String description( String leftDescription, String rightDescription) { return "(" + leftDescription + " AND " + rightDescription + ")"; } }; // ---------------------------------------------------------- /** * The "or" operator for combining filters, designed to be used in * expressions like <code>where.locationIs(25, 25).or.textIs("def")</code>. * This operator is implemented as a public field so that the simple * <code>.or.</code> notation can be used as a connective between * filters. If you want to use parentheses for grouping to define * the right argument, see {@link #or(GObjectFilter)} instead. */ public final BinaryOperator or = new BinaryOperator() { // ---------------------------------------------------------- @Override protected boolean combine(boolean leftResult, boolean rightResult) { return leftResult || rightResult; } // ---------------------------------------------------------- @Override protected String description( String leftDescription, String rightDescription) { return "(" + leftDescription + " OR " + rightDescription + ")"; } }; //~ Public Methods ........................................................ // ---------------------------------------------------------- /** * This base class represents an operator used to create a query. * As the base class for all operators, it defines the primitive * query operations supported for all {@link GObject} objects, * each of which can be combined using any operator. */ public static abstract class Operator { // ---------------------------------------------------------- /** * Create a filter that checks that a GObject contains * the given point. * @param point the required point * @return A new filter that succeeds only on GObjects that * contain the point. */ public GObjectFilter cointainsPoint(GPoint point) { return applySelfTo(GObjectFilter.containsPoint(point)); } // ---------------------------------------------------------- /** * Create a filter that checks that a GObject contains * the given point. * @param x The required x coordinate, relative to the * GObject's parent. * @param y The required y coordinate, relative to the * GObject's parent. * @return A new filter that succeeds only on GObjects that * contain the point (x, y). */ public GObjectFilter cointainsPoint(double x, double y) { return applySelfTo(GObjectFilter.containsPoint(new GPoint(x, y))); } // ---------------------------------------------------------- /** * Create a filter that checks that a GObject is within 50 * pixels of the given point. * @param point The required point. * @return A new filter that succeeds only on GObjects that are * located with 50 pixels of point. */ public GObjectFilter isNear(GPoint point) { return applySelfTo(GObjectFilter.isNear(point)); } // ---------------------------------------------------------- /** * Create a filter that checks that a GObject is within 50 * pixels of the given point. * @param x The required x coordinate, relative to the * GObject's parent. * @param y The required y coordinate, relative to the * GObject's parent. * @return A new filter that succeeds only on GObjects that are * located within 50 pixels of the point (x, y). */ public GObjectFilter isNear(double x, double y) { return applySelfTo(GObjectFilter.isNear(new GPoint(x, y))); } // ---------------------------------------------------------- /** * Create a filter that checks that a GObject is within * distance pixels of the given point. * @param point The required point. * @param distance The distance used to judge whether the * GObject is near the given point * @return A new filter that succeeds only on GObjects that are * within distance pixels of the given point. */ public GObjectFilter isNear(GPoint point, double distance) { return applySelfTo(GObjectFilter.isNear(point, distance)); } // ---------------------------------------------------------- /** * Create a filter that checks that a GObject is within * distance pixels of the given point. * @param x The required x coordinate, relative to the * GObject's parent. * @param y The required y coordinate, relative to the * GObject's parent * @param distance The distance used to judge whether the * GObject is near the given point * @return A new filter that succeeds only on GObjects that are * within distance pixels of the point (x, y). */ public GObjectFilter isNear(double x, double y, double distance) { return applySelfTo( GObjectFilter.isNear(new GPoint(x, y), distance)); } // ---------------------------------------------------------- /** * Create a filter that checks that a GOBject is a line * that starts at point. * @param point The required point. * @return A new filter that succeeds only on GObjects * that are lines starting at point. */ public GObjectFilter lineStartPointIs(GPoint point) { return applySelfTo(GObjectFilter.lineStartPointIs(point)); } // ---------------------------------------------------------- /** * Create a filter that checks that a GPoint is a line * that starts at the point (x, y). * @param x The required x coordinate, relative to the * GObject's parent. * @param y The required y coordinate, relative to the * GObject's parent * @return A new filter that succeeds only on GObjects * that are lines starting at the point (x, y). */ public GObjectFilter lineStartPointIs(double x, double y) { return applySelfTo( GObjectFilter.lineStartPointIs(new GPoint(x, y))); } // ---------------------------------------------------------- /** * Create a filter that checks that a GOBject is a line * that ends at point. * @param point The required point. * @return A new filter that succeeds only on GObjects * that are lines ending at point. */ public GObjectFilter lineEndPointIs(GPoint point) { return applySelfTo(GObjectFilter.lineEndPointIs(point)); } // ---------------------------------------------------------- /** * Create a filter that checks that a GPoint is a line * that ends at the point (x, y). * @param x The required x coordinate, relative to the * GObject's parent. * @param y The required y coordinate, relative to the * GObject's parent * @return A new filter that succeeds only on GObjects * that are lines endin at the point (x, y). */ public GObjectFilter lineEndPointIs(double x, double y) { return applySelfTo(GObjectFilter.lineEndPointIs(new GPoint(x, y))); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's size. * @param width The required width. * @param height The required height. * @return A new filter that succeeds only on GObjects that have * the given size. */ public GObjectFilter sizeIs(final double width, final double height) { return applySelfTo(GObjectFilter.sizeIs(width, height)); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's size. * @param maxWidth The required width. * @param maxHeight The required height. * @return A new filter that succeeds only on GObjects that are * eqal to or smaller than the given size. */ public GObjectFilter sizeIsWithin( final double maxWidth, final double maxHeight) { return applySelfTo( GObjectFilter.sizeIsWithin(maxWidth, maxHeight)); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's location relative to * its parent. * @param x The required x-coordinate, relative to the * GObject's parent. * @param y The required y-coordinate, relative to the * GObject's parent. * @return A new filter that succeeds only on GObjects that have * the given location. */ public GObjectFilter locationIs(final double x, final double y) { return locationIs(new GPoint(x, y)); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's location relative to * its parent. * @param point The required point, relative to the * GObject's parent. * * @return A new filter that succeeds only on GObjects that have * the given location. */ public GObjectFilter locationIs(final GPoint point) { return applySelfTo(GObjectFilter.locationIs(point)); } // ---------------------------------------------------------- /** * Create a filter that checks whether a GObject's location (its * top left corner) lies within a specific rectangle. * @param region A rectangle defining a region in the GObject's * parent. * @return A new filter that succeeds only on GObjects that have * a location within the given region. */ public GObjectFilter isLocatedWithin(final Rectangle region) { return applySelfTo(GObjectFilter.isLocatedWithin(region)); } // ---------------------------------------------------------- /** * Create a filter that checks whether a GObject's bounding box * (as returned by {@link GObject#getBounds()}) lies within a * specific rectangle--that is, whether the entire GObject's area, * rather than just its top left corner, lies within the specified * region. * @param region A rectangle defining a region in the GObject's * parent. * @return A new filter that succeeds only on GObjects that * lie entirely within the given region, as determined by * {@link Rectangle#contains(Rectangle)}. */ public GObjectFilter isContainedWithin(final Rectangle region) { return applySelfTo(GObjectFilter.isContainedWithin(region)); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's x-coordinate. * @param x The required x-coordinate, relative to the * GObject's parent. * @return A new filter that succeeds only on GObjects that have * the given x-coordinate. */ public GObjectFilter xLocationIs(final double x) { return applySelfTo(GObjectFilter.xLocationIs(x)); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's y-coordinate. * @param y The required y-coordinate, relative to the * GObject's parent. * @return A new filter that succeeds only on GObjects that have * the given y-coordinate. */ public GObjectFilter yLocationIs(final double y) { return applySelfTo(GObjectFilter.yLocationIs(y)); } // ---------------------------------------------------------- /** * Create a filter that checks the text of a GObject by calling the * GObject's <code>getText()</code> method. * @param text The text to look for * @return A new filter that succeeds only on GObjects where * <code>getText()</code> returns the specified text. */ public GObjectFilter textIs(final String text) { return applySelfTo(GObjectFilter.textIs(text)); } // ---------------------------------------------------------- /** * Create a filter that checks the class of a GObject. * @param aClass The required class to check for (any subclass will * also match). * @return A new filter that only succeeds on instances of the * given class. */ public GObjectFilter typeIs(final Class<? extends GObject> aClass) { return applySelfTo(GObjectFilter.typeIs(aClass)); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's width. * @param value The width to look for. * @return A new filter that succeeds only on GObjects that have * the given width. */ public GObjectFilter widthIs(final double value) { return applySelfTo(GObjectFilter.widthIs(value)); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's height. * @param value The height to look for. * @return A new filter that succeeds only on GObjects that have * the given height. */ public GObjectFilter heightIs(final double value) { return applySelfTo(GObjectFilter.heightIs(value)); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's color. * @param color The required Color. * @return A new filter that succeeds only on GObjects that are * Color color. */ public GObjectFilter colorIs(final Color color) { return applySelfTo(GObjectFilter.colorIs(color)); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's fill color. * @param color The required Color. * @return A new filter that succeeds only on GObjects that are * filled with Color color. */ public GObjectFilter fillColorIs(final Color color) { return applySelfTo(GObjectFilter.fillColorIs(color)); } // ---------------------------------------------------------- /** * Create a filter that checks whether a GObject is filled. * @param filled The required boolean value. * @return A new filter that succeeds only on GObjects whose * filled value is equal to filled. */ public GObjectFilter filledIs(final boolean filled) { return applySelfTo(GObjectFilter.filledIs(filled)); } // ---------------------------------------------------------- /** * Create a filter that checks a GObject's visibilty. * @param visibility The required boolean value. * @return A new filter that succeeds only on GObjects whos * visibility value is equal to visibility. */ public GObjectFilter visibilityIs(final boolean visibility) { return applySelfTo(GObjectFilter.visibilityIs(visibility)); } // ---------------------------------------------------------- /** * Concrete subclasses must override this to implement an * operation on the filter being passed in to transform it into * another filter. * @param otherFilter The argument to transform (second argument, * for binary operators) * @return A new compound filter that includes the given argument * as one subfilter, after applying this operator to it. */ protected abstract GObjectFilter applySelfTo( final GObjectFilter otherFilter); } // ---------------------------------------------------------- /** * A non-static subclass for binary operators that implicitly * captures the outer filter to which it belongs, using it as * the first/left argument to the operator. */ public abstract class BinaryOperator extends Operator { // ---------------------------------------------------------- /** * The "not" operator for negating an existing filter, when you * want to use parentheses to group its righthand argument. This * method is designed to be used in expressions like * <code>where.nameIs("abc").and.not(enabledIs(true).or.hasFocusIs(true))</code>. * If you wish to use the <code>.not.</code> notation instead, leaving * off the parentheses, see {@link BinaryOperator#not}. * * @param otherFilter The filter to negate * @return A new filter that represents a combination of the left * filter with "NOT otherFilter". */ public GObjectFilter not(final GObjectFilter otherFilter) { return applySelfTo(primitiveNot(otherFilter)); } // ---------------------------------------------------------- /** * Implements a composite filter based on a binary operation, * where the "left"/"first" filter is the parent from which this * class was created, and the "right"/"second" filter is the * argument supplied to this operation. * @param otherFilter The argument to transform (second argument, * for binary operators) * @return A new compound filter that represents a combination * of the first and second filters. */ @Override protected GObjectFilter applySelfTo(final GObjectFilter otherFilter) { return new GObjectFilter(description( GObjectFilter.this.toString(), otherFilter.toString())) { public boolean test(GObject gobj) { return combine(GObjectFilter.this.test(gobj), otherFilter.test(gobj)); } }; } // ---------------------------------------------------------- /** * Concrete subclasses must override this to implement the * appropriate logic for combining the results of the two filters * being combined. * @param leftResult The boolean result of the left filter * @param rightResult The boolean result of the right filter * @return The result of this combined filter. */ protected abstract boolean combine( boolean leftResult, boolean rightResult); // ---------------------------------------------------------- /** * Concrete subclasses must override this to implement the * appropriate logic for building a description of this filter * based on the descriptions of the two filters * being combined. * @param leftDescription The description of the left filter * @param rightDescription The description of the right filter * @return The description of this combined filter. */ protected abstract String description( String leftDescription, String rightDescription); } // ---------------------------------------------------------- /** * Get a string representation of this filter. * @return A string representation of this filter. */ public String toString() { return description; } // ---------------------------------------------------------- /** * The "and" operator for combining filters, when you want to use * parentheses to group its righthand argument. This method is designed * to be used in expressions like * <code>where.textIs("abc").and(visibility(true).or.filledIs(true))</code>. * If you wish to use the <code>.and.</code> notation instead, leaving * off the parentheses, see {@link BinaryOperator#and(GObjectFilter)}. * * @param otherFilter The second argument to "and". * @return A new filter object that represents "this AND otherFilter". */ public final GObjectFilter and(final GObjectFilter otherFilter) { final GObjectFilter self = this; GObjectFilter f = new GObjectFilter("(" + this + " AND " + otherFilter + ")") { public boolean test(GObject gobj) { return self.test(gobj) && otherFilter.test(gobj); } }; return f; } // ---------------------------------------------------------- /** * The "or" operator for combining filters, when you want to use * parentheses to group its righthand argument. This method is designed * to be used in expressions like * <code>where.textIs("abc").or(visibilityIs(true).and.filledIs(true))</code>. * If you wish to use the <code>.or.</code> notation instead, leaving * off the parentheses, see {@link BinaryOperator#or(GObjectFilter)}. * * @param otherFilter The second argument to "or". * @return A new filter object that represents "this OR otherFilter". */ public final GObjectFilter or(final GObjectFilter otherFilter) { final GObjectFilter self = this; GObjectFilter f = new GObjectFilter("(" + this + " OR " + otherFilter + ")") { public boolean test(GObject gobj) { return self.test(gobj) || otherFilter.test(gobj); } }; return f; } // ---------------------------------------------------------- /** * Evaluate whether a GObject matches this filter. This operation is * intended to be overridden by each subclass to implement the actual * check that a specific kind of filter performs. * @param gobj The GObject to check * @return true if the GObject matches this filter */ public abstract boolean test(GObject gobj); // ---------------------------------------------------------- /** * This class represents the "where" operator that is used to begin * a filter expression. Client classes that wish to support filter * syntax should declare a final field (static or instance) like * this: * <pre> * public static final GObjectFilter.WhereOperator where = * new GObjectFilter.WhereOperator(); * </pre> */ public static class ClientImports { // ---------------------------------------------------------- /** * This object represents the "where" operator that is used to begin * a filter expression. */ public static final Operator where = new Operator() { // ---------------------------------------------------------- @Override protected GObjectFilter applySelfTo(GObjectFilter filter) { return filter; } }; // ---------------------------------------------------------- /** * The "not" operator for negating an existing filter, when the not * operation is at the very beginning of the expression. This * method is designed to be used in expressions like * <code>not(where.visibilityIs(true).or.filledIs(true))</code>. * * @param otherFilter The filter to negate * @return A new filter that represents a combination of the left * filter with "NOT otherFilter". */ public static GObjectFilter not(final GObjectFilter otherFilter) { return primitiveNot(otherFilter); } } //~ Private Methods/Declarations .......................................... // ---------------------------------------------------------- private static final GObjectFilter containsPoint(final GPoint point) { GObjectFilter f = new GObjectFilter( "containsPoint (" + point.getX() + ", " + point.getY() + " )") { public boolean test(GObject gobj) { return (gobj.contains(point)); } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter isNear(final GPoint point) { GObjectFilter f = new GObjectFilter( "isNear (" + point.getX() + ", " + point.getY() + ")") { public boolean test(GObject gobj) { double distance = Math.sqrt(Math.pow(point.getX() - gobj.getX(), 2) + Math.pow(point.getX() - gobj.getX(), 2)); return distance < 50; } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter isNear( final GPoint point, final double distance) { GObjectFilter f = new GObjectFilter("within " + distance + " pixels of (" + point.getX() + ", " + point.getY() + ")") { public boolean test(GObject gobj) { double d = Math.sqrt(Math.pow(point.getX() - gobj.getX(), 2) + Math.pow(point.getX() - gobj.getX(), 2)); return d < distance; } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter lineStartPointIs(final GPoint point) { GObjectFilter f = new GObjectFilter( "line starts at (" + point.getX() + ", " + point.getY() + ")") { public boolean test(GObject gobj) { return (gobj instanceof GLine) && ((GLine)gobj).getStartPoint().equals(point); } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter lineEndPointIs(final GPoint point) { GObjectFilter f = new GObjectFilter( "line ending at (" + point.getX() + ", " + point.getY() + ")") { public boolean test(GObject gobj) { return (gobj instanceof GLine) && ((GLine)gobj).getEndPoint().equals(point); } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter sizeIs( final double width, final double height) { GObjectFilter f = new GObjectFilter( "size = (" + width + ", " + height + ")") { public boolean test(GObject gobj) { return gobj.getWidth() == width && gobj.getHeight() == height; } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter sizeIsWithin( final double maxWidth, final double maxHeight) { GObjectFilter f = new GObjectFilter( "sizeIsWithin(" + maxWidth + ", " + maxHeight + ")") { public boolean test(GObject gobj) { return gobj.getWidth() <= maxWidth && gobj.getHeight() <= maxHeight; } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter locationIs(final GPoint point) { GObjectFilter f = new GObjectFilter( "location = (" + point.getX() + ", " + point.getY() + ")") { public boolean test(GObject gobj) { GPoint loc = gobj.getLocation(); return loc.getX() == point.getX() && loc.getY() == point.getY(); } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter isLocatedWithin(final Rectangle region) { GObjectFilter f = new GObjectFilter("isLocatedWithin(" + region + ")") { @Override public boolean test(GObject gobj) { return region.contains(gobj.getLocation().toPoint()); } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter isContainedWithin( final Rectangle region) { GObjectFilter f = new GObjectFilter("isLocatedWithin(" + region + ")") { @Override public boolean test(GObject gobj) { return region.contains(gobj.getBounds().toRectangle()); } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter xLocationIs(final double x) { GObjectFilter f = new GObjectFilter("xLocation = " + x) { @Override public boolean test(GObject gobj) { return gobj.getX() == x; } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter yLocationIs(final double y) { GObjectFilter f = new GObjectFilter("yLocation = " + y) { @Override public boolean test(GObject gobj) { return gobj.getX() == y; } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter textIs(final String text) { GObjectFilter f = new GObjectFilter("text = \"" + text + "\"") { public boolean test(GObject gobj) { if (gobj instanceof GLabel) { return (text == null) ? text == ((GLabel)gobj).getLabel() : text.equals(((GLabel)gobj).getLabel()); } else { return false; } } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter widthIs(final double width) { GObjectFilter f = new GObjectFilter("width = " + width) { public boolean test( GObject gobj ) { return gobj.getWidth() == width; } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter heightIs(final double height) { GObjectFilter f = new GObjectFilter("height = " + height) { public boolean test( GObject gobj ) { return gobj.getHeight() == height; } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter colorIs(final Color color) { GObjectFilter f = new GObjectFilter("color = " + color) { public boolean test( GObject gobj ) { return gobj.getColor().equals(color); } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter fillColorIs(final Color color) { GObjectFilter f = new GObjectFilter("fillColor = " + color) { public boolean test( GObject gobj ) { return gobj instanceof GFillable && ((GFillable)gobj).getFillColor().equals(color); } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter typeIs(final Class<? extends GObject> c) { GObjectFilter f = new GObjectFilter("type = " + c.getSimpleName()) { public boolean test(GObject gobj) { return c.isAssignableFrom(gobj.getClass()); } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter filledIs(final boolean filled) { GObjectFilter f = new GObjectFilter("filled = " + filled) { public boolean test(GObject gobj) { return gobj instanceof GFillable && ((GFillable)gobj).isFilled() == filled; } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter visibilityIs(final boolean visibility) { GObjectFilter f = new GObjectFilter("visibility = " + visibility) { public boolean test(GObject gobj) { return gobj.isVisible(); } }; return f; } // ---------------------------------------------------------- private static final GObjectFilter primitiveNot( final GObjectFilter otherFilter) { return new GObjectFilter("(NOT " + otherFilter + ")") { public boolean test(GObject gobj) { return !otherFilter.test(gobj); } }; } }