/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-2008, Open Source Geospatial Foundation (OSGeo) * * This 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; * version 2.1 of the License. * * This 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. */ package org.geotools.filter; import java.awt.Color; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.geotools.factory.CommonFactoryFinder; import org.geotools.filter.visitor.AbstractSearchFilterVisitor; import org.geotools.filter.visitor.DefaultFilterVisitor; import org.geotools.filter.visitor.DuplicatingFilterVisitor; import org.geotools.util.Converters; import org.geotools.util.Utilities; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.And; import org.opengis.filter.BinaryLogicOperator; import org.opengis.filter.ExcludeFilter; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory2; import org.opengis.filter.Id; import org.opengis.filter.IncludeFilter; import org.opengis.filter.Not; import org.opengis.filter.Or; import org.opengis.filter.PropertyIsBetween; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsGreaterThan; import org.opengis.filter.PropertyIsGreaterThanOrEqualTo; import org.opengis.filter.PropertyIsLessThan; import org.opengis.filter.PropertyIsLessThanOrEqualTo; import org.opengis.filter.PropertyIsLike; import org.opengis.filter.PropertyIsNotEqualTo; import org.opengis.filter.PropertyIsNull; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Function; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.spatial.BBOX; import org.opengis.filter.spatial.Beyond; import org.opengis.filter.spatial.Contains; import org.opengis.filter.spatial.Crosses; import org.opengis.filter.spatial.DWithin; import org.opengis.filter.spatial.Disjoint; import org.opengis.filter.spatial.Equals; import org.opengis.filter.spatial.Intersects; import org.opengis.filter.spatial.Overlaps; import org.opengis.filter.spatial.Touches; import org.opengis.filter.spatial.Within; /** * Utility class for working with Filters & Expression. * <p> * To get the full benefit you will need to create an instanceof * this Object (supports your own custom FilterFactory!). Additional * methods to help create expressions are available. * </p> * <p> * Example use: * <pre><code> * Filters filters = new Filters( factory ); * filters.duplicate( original ); * </code></pre> * The above example creates a copy of the provided Filter, * the factory provided will be used when creating the duplicated * content. * </p> * <h3>Expression</h3> * <p> * Expressions form an interesting little semi scripting language, * intended for queries. A interesting Feature of Filter as a language * is that it is not strongly typed. This utility class many helper * methods that ease the transition from Strongly typed Java to the more * relaxed setting of Expression where most everything can be a string. * </p> * <pre><code> * double sum = Filters.number( Object ) + Filters.number( Object ); * </code></pre> * The above example will support the conversion of many things into a format * suitable for addition - the complete list is something like: * <ul> * <li>Any instance of Number * <li>"1234" - aka Integer * <li>"#FFF" - aka Integer * <li>"123.0" - aka Double * </ul> * A few things (like Geometry and "ABC") will not be considered addative. * </p> * In general the scope of these functions should be similar to that * allowed by the XML Atomic Types, aka those that can be seperated by * whitespace to form a list. * </p> * <p> * We do our best to be forgiving, any Java class which takes a String as * a constructor can be tried, and toString() assumed to be the inverse. This * lets many things (like URL and Date) function without modification. * </p> * * @author Jody Garnett (LISAsoft) * @since GeoTools 2.2 * @version 8.0 * @source $URL$ */ public class Filters { /** <code>NOTFOUND</code> indicates int value was unavailable */ public static final int NOTFOUND = -1; /** * Private implementation used to handle static methods. Because this is a private instance we * do not have to override setFilterFactory; nobody will be messing with the factory and * breaking things for everyone. * <p> * Alternative; each static method can use CommonFactoryFinder in order to always make * use of the current globally configured results. */ private static Filters STATIC = new Filters(); /** * Set to true to start throwing exceptions when org.geotools.filter.Filter is used. */ private static final boolean STRICT = false; org.opengis.filter.FilterFactory2 ff; /** Create Filters helper object using global FilterFactory provided by CommonFactoryFinder */ public Filters() { this(CommonFactoryFinder.getFilterFactory2(null)); } /** Create a Filters helper using the provided FilterFactory */ public Filters(org.opengis.filter.FilterFactory2 factory) { ff = factory; } public void setFilterFactory(org.opengis.filter.FilterFactory2 factory) { ff = factory; } /** * Safe version of FilterFactory *and* that is willing to combine * filter1 and filter2 correctly in the even either of them is already * an And filter. * * @param ff * @param filter1 * @param filter2 * @return And */ public static Filter and( org.opengis.filter.FilterFactory ff, Filter filter1, Filter filter2 ){ ArrayList<Filter> list = new ArrayList<Filter>(2); if( filter1 == null ){ // ignore } else if( filter1 instanceof And){ And some = (And) filter1; list.addAll( some.getChildren() ); } else { list.add( filter1 ); } if( filter2 == null ){ // ignore } else if( filter2 instanceof And){ And more = (And) filter2; list.addAll( more.getChildren() ); } else { list.add( filter2 ); } if( list.size() == 0 ){ return Filter.EXCLUDE; } else if( list.size() == 1 ){ return list.get(0); } else { return ff.and( list ); } } /** * Safe version of FilterFactory *or* that is willing to combine * filter1 and filter2 correctly in the even either of them is already * an Or filter. * * @param ff * @param filter1 * @param filter2 * @return */ public static Filter or( org.opengis.filter.FilterFactory ff, Filter filter1, Filter filter2 ){ ArrayList<Filter> list = new ArrayList<Filter>(); if( filter1 == null ){ // ignore } else if( filter1 instanceof Or){ Or some = (Or) filter1; list.addAll( some.getChildren() ); } else { list.add( filter1 ); } if( filter2 == null){ // ignore } else if( filter2 instanceof Or){ Or more = (Or) filter2; list.addAll( more.getChildren() ); } else { list.add( filter2 ); } if( list.size() == 0 ){ return Filter.EXCLUDE; } else if( list.size() == 1 ){ return list.get(0); } else { return ff.or( list ); } } /** * Safely visit the provided filter. * <p> * This method handles the case of: * <ul> * <li>Filter.INCLUDES: will call FilterVisitor2 method if available * <li>Filter.EXCLUDES: will call FilterVisitor2 method if available * <li>org.geotools.filter.Filter: will visit * </ul> * Please note that when called with a strict *org.opengis.filter.Filter* this * method will fail with a ClassCastException * * @param filter * @param visitor * @deprecated Please update your code to a org.opengis.filter.FilterVisitor */ public static void accept( org.opengis.filter.Filter filter, FilterVisitor visitor ){ if( filter == Filter.EXCLUDE ){ if( visitor instanceof FilterVisitor2 ){ ((FilterVisitor2)visitor).visit( (ExcludeFilter) Filter.EXCLUDE ); } return; } else if( filter == Filter.INCLUDE ){ if( visitor instanceof FilterVisitor2 ){ ((FilterVisitor2)visitor).visit( (IncludeFilter) Filter.INCLUDE); } return; } if( filter instanceof org.geotools.filter.Filter ){ ((org.geotools.filter.Filter) filter).accept( visitor ); } else { if( STRICT ){ // don't even try .. throw new ClassCastException("Please update your code to a org.opengis.filter.FilterVisitor"); } // Copy the provided filter into the old org.geotools.filter.Filter api FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null); DuplicatingFilterVisitor xerox = new DuplicatingFilterVisitor( ff ); org.geotools.filter.Filter copy = (org.geotools.filter.Filter) filter.accept( xerox, ff ); // Visit the resulting copy copy.accept(visitor); } } /** * Deep copy the filter. * <p> * Filter objects are mutable, when copying a rich * data structure (like SLD) you will need to duplicate * the Filters referenced therein. * </p> */ public Filter duplicate( Filter filter ){ DuplicatingFilterVisitor xerox = new DuplicatingFilterVisitor( ff ); Filter copy = (Filter) filter.accept( xerox, ff ); return copy; } /** * Utility method used to transition to geoapi filter. * <p> * This utility method is designed to help people port their * code quickly, an instanceof check is much preferred. * </p> * Example:<pre><code> * BEFORE: filter.getFilterType() == FilterType.GEOMETRY_CONTAINS * QUICK: Filters.getFilterType( filter ) == FilterType.GEOMETRY_CONTAINS * AFTER: filter instanceof Contains * </code></pre> * @param filter * @deprecated please use instanceof checks */ public static short getFilterType( org.opengis.filter.Filter filter ){ if( filter == org.opengis.filter.Filter.EXCLUDE ) return FilterType.ALL; if( filter == org.opengis.filter.Filter.INCLUDE ) return FilterType.NONE; if( filter instanceof org.geotools.filter.Filter){ return ((org.geotools.filter.Filter)filter).getFilterType(); } if( filter instanceof PropertyIsBetween ) return FilterType.BETWEEN; if( filter instanceof PropertyIsEqualTo ) return FilterType.COMPARE_EQUALS; if( filter instanceof PropertyIsGreaterThan ) return FilterType.COMPARE_GREATER_THAN; if( filter instanceof PropertyIsGreaterThanOrEqualTo ) return FilterType.COMPARE_GREATER_THAN_EQUAL; if( filter instanceof PropertyIsLessThan) return FilterType.COMPARE_LESS_THAN; if( filter instanceof PropertyIsLessThanOrEqualTo ) return FilterType.COMPARE_LESS_THAN_EQUAL; if( filter instanceof PropertyIsNotEqualTo ) return FilterType.COMPARE_NOT_EQUALS; if( filter instanceof Id ) return FilterType.FID; if( filter instanceof BBOX ) return FilterType.GEOMETRY_BBOX; if( filter instanceof Beyond) return FilterType.GEOMETRY_BEYOND; if( filter instanceof Contains ) return FilterType.GEOMETRY_CONTAINS; if( filter instanceof Crosses ) return FilterType.GEOMETRY_CROSSES; if( filter instanceof Disjoint ) return FilterType.GEOMETRY_DISJOINT; if( filter instanceof DWithin) return FilterType.GEOMETRY_DWITHIN; if( filter instanceof Equals) return FilterType.GEOMETRY_EQUALS; if( filter instanceof Intersects) return FilterType.GEOMETRY_INTERSECTS; if( filter instanceof Overlaps) return FilterType.GEOMETRY_OVERLAPS; if( filter instanceof Touches) return FilterType.GEOMETRY_TOUCHES; if( filter instanceof Within) return FilterType.GEOMETRY_WITHIN; if( filter instanceof PropertyIsLike) return FilterType.LIKE; if( filter instanceof And) return FilterType.LOGIC_AND; if( filter instanceof Not) return FilterType.LOGIC_NOT; if( filter instanceof Or ) return FilterType.LOGIC_OR; if( filter instanceof PropertyIsNull) return FilterType.NULL; if( filter instanceof Filter){ return 0; } return 0; } /** * Obtain the provided Expression as an integer. * <p> * This method is quickly used to safely check Literal expressions. * * @param expr * @return int value of first Number, or NOTFOUND */ public static int asInt( Expression expr ) { if( expr == null ) return NOTFOUND; try { Integer number = expr.evaluate( null, Integer.class ); if( number == null ){ return NOTFOUND; } return number; } catch( NullPointerException npe ){ return NOTFOUND; // well that was not unexpected } /* Number number = (Number) asType(expr, Number.class); if (number != null) { return number.intValue(); } //look for a string String string = (String) asType(expr,String.class); if (string != null) { //try parsing into a integer try { return Integer.parseInt(string); } catch(NumberFormatException e) {} } //no dice return NOTFOUND; */ } /** * Obtain the provided Expression as a String. * <p> * This method only reliably works when the Expression is a Literal. * * @param expr * * @return Expression as a String, or null */ public static String asString(Expression expr) { if( expr == null ) return null; try { return expr.evaluate( null, String.class ); } catch( NullPointerException npe){ // must be a more complicated expression than a literal return null; } } /** * Obtain the provided Expression as a double. * @param expr * @return int value of first Number, or Double.NaN */ public static double asDouble(Expression expr) { if( expr == null ) { return Double.NaN; } try { Double number = expr.evaluate(null, Double.class ); if( number == null ) { return Double.NaN; } return number.doubleValue(); } catch( NullPointerException npe){ // must be a more complicated expression than a literal return Double.NaN; } } /** * Navigate through the expression searching for something that can be a TYPE. * <p> * This will work even with dynamic expression that would normally require a * feature. It works especially well when the Expression is a Literal * literal (which is usually the case). * </p> * * <p> * If you have a specific Feature, please do this: * <pre><code> * Color value = expr.evaualte( feature, Color.class ); * return value instanceof Color ? (Color) value : null; * </code></pre> * </p> * * @param expr This only really works for down casting literals to a value * @param Target type * * @return expr smunched into indicated type * @deprecated This is not a good idea; use expr.evaulate( null, TYPE ) */ public static <T> T asType(Expression expr, Class<T> TYPE) { if (expr == null) { return null; } if( STRICT ){ return expr.evaluate(null, TYPE ); } else if (expr instanceof Literal) { Literal literal = (Literal) expr; return (T) literal.evaluate(null, TYPE ); } else if (expr instanceof Function) { Function function = (Function) expr; List<Expression> params = function.getParameters(); if ( params != null && params.size() != 0 ) { for (int i = 0; i < params.size(); i++) { Expression e = (Expression) params.get(i); T value = asType(e, TYPE); if (value != null) { return value; } } } } else { try { // this is a bad idea, not expected to work much T value = expr.evaluate(null, TYPE ); if (TYPE.isInstance(value)) { return value; } } catch (NullPointerException expected) { return null; // well that was not unexpected } catch (Throwable ignore) { // I did say that was a bad idea } } return null; // really need a Feature to acomplish this one } /** * Treat provided value as a Number, used for math opperations. * <p> * This function allows for the non stongly typed Math Opperations * favoured by the Expression standard. * </p> * <p> * Able to hanle: * <ul> * <li>null - to NaN * <li>Number * <li>String - valid Integer and Double encodings * </ul> * * </p> * @param value * @return double or Double.NaN; * @throws IllegalArgumentException For non numerical among us -- like Geometry */ public static double number(Object value) { if( value == null ) return Double.NaN; if( value instanceof Number ){ Number number = (Number) value; return number.doubleValue(); } if( value instanceof String ){ String text = (String) value; try { Number number = (Number) gets( text, Number.class ); return number.doubleValue(); } catch (Throwable e) { throw new IllegalArgumentException("Unable to decode '"+text+"' as a number" ); } } if( value instanceof Expression ){ throw new IllegalArgumentException("Cannot deal with un evaulated Expression"); } throw new IllegalArgumentException("Unable to evaulate "+value.getClass()+" in a numeric context"); } /** * Used to upcovnert a "Text Value" into the provided TYPE. * <p> * Used to tread softly on the Java typing system, because * Filter/Expression is not strongly typed. Values in in * Expression land are often not the the real Java Objects * we wish they were - it is reall a small, lax, query * language and Java objects need a but of help getting * through. * <p> * </p> * A couple notes: * <ul> * <li>Usual trick of reflection for a Constructors that * supports a String parameter is used as a last ditch effort. * </li> * <li>will do its best to turn Object into the indicated Class * <li>will be used for ordering literals against attribute values * are calculated at runtime (like Date.) * </ul> * Remember Strong typing is for whimps who know what they are * doing ahead of time. Real programmers let their program * learn at runtime... :-) * </p> * * @param text * @param TYPE * @throws open set of Throwable reflection for TYPE( String ) */ public static <T> T gets(String text, Class<T> TYPE) throws Throwable { if (text == null) { return null; } if (TYPE == String.class) { return TYPE.cast(text); } if (TYPE == Integer.class) { return TYPE.cast(Integer.decode(text)); } if (TYPE == Double.class) { return TYPE.cast(Double.valueOf(text)); } if (TYPE == Number.class) { try { return TYPE.cast(Double.valueOf(text)); } catch (NumberFormatException ignore) { } return TYPE.cast(Integer.decode(text)); } if (TYPE == Color.class) { return TYPE.cast(new Color(Integer.decode(text).intValue())); } // fallback try converters Object value = Converters.convert(text, TYPE); if (value != null) { return TYPE.cast(value); } // Original fall back position of reflection against constructor try { Constructor<T> create = TYPE.getConstructor(new Class[] { String.class }); return create.newInstance(new Object[] { text }); } catch (SecurityException e) { // hates you } catch (NoSuchMethodException e) { // nope } catch (IllegalArgumentException e) { // should not occur } catch (InstantiationException e) { // should not occur, perhaps the class was abstract? // eg. Number.class is a bad idea } catch (IllegalAccessException e) { // hates you } catch (InvocationTargetException e) { // should of worked but we got a real problem, // an actual problem throw e.getCause(); } return null; // give up } /** * Convert provided number to a suitable text representation * <p> * Examples: * <ul> * <li>Filters.puts( 3.14 ) => "3.14"</li> * <li>Filters.puts( 1.0 ) => "1"</li> * </ul> * @param number * @return text representation */ public static String puts(double number) { if (Math.rint(number) == number) { return Integer.toString((int) number); } return Double.toString(number); } /** * Inverse of eval, used to softly type supported * types into Text for use as literals. * <p> * This method has been superseeded by Converters * which offers a more general and open ended solution. * </p> * @return String representation of provided object */ public static String puts(Object obj) { if (obj == null){ return null; } if (obj instanceof String){ return (String) obj; } if (obj instanceof Color) { Color color = (Color) obj; return puts(color); } if (obj instanceof Number) { Number number = (Number) obj; return puts(number.doubleValue()); } String text = Converters.convert( obj, String.class ); if( text != null ){ return text; } return obj.toString(); } /** * Inverse of eval, used to softly type supported types into Text for use as literals. * <p> * This method has been superseeded by Converters which offers a more general and open ended * solution. * </p> * * @param color * @return String representation of provided color. */ public static String puts(Color color) { String redCode = Integer.toHexString(color.getRed()); String greenCode = Integer.toHexString(color.getGreen()); String blueCode = Integer.toHexString(color.getBlue()); if (redCode.length() == 1) redCode = "0" + redCode; if (greenCode.length() == 1) greenCode = "0" + greenCode; if (blueCode.length() == 1) blueCode = "0" + blueCode; return "#" + redCode + greenCode + blueCode; } // // FilterUtils from Eric Sword // // static boolean isLogicFilter(Filter filter) { // return (isGroupFilter(filter) || (filter instanceof Not)); // } /** * Returns true if the given filter can contain more than one subfilter. Only And and Or filters match this now. * @param filter * @return */ // static boolean isGroupFilter(Filter filter) { // //Note: Can't use BinaryLogicOperator here because the Not implementation also inherits from it. // return ( (filter instanceof And) || (filter instanceof Or)); // } /** * Removes the targetFilter from the baseFilter if the baseFilter is a group filter (And or * Or),recursing into any sub-logic filters to find the targetFilter if necessary. * <ul> * <li>If the targetFilter equals the baseFilter, then Filter.INCLUDE is returned to indicate * that no filters are left.</li> * <li>If the targetFilter does not equal the base filter, no change is made and the baseFilter * is returned.</li> * <li>If removing the targetFilter would leave only a single term within the baseFilter, then the single * remaining term is returned instead of the (now invalid) baseFilter. * </li>If the last item is removed from an Or statement then Filter.EXCLUDE is return * </li>If the last item is removed from an And statement then Filter.INCLUDE is returned * </ul> * * @param baseFilter * @param targetFilter * @return */ public Filter remove(Filter baseFilter, Filter targetFilter) { return remove(baseFilter, targetFilter, true); } public static Filter removeFilter( Filter baseFilter, Filter targetFilter ){ return STATIC.remove(baseFilter, targetFilter); } /** * Removes the targetFilter from the baseFilter if the baseFilter is a group filter (And or Or). See * {@link #removeFilter(org.opengis.filter.Filter, org.opengis.filter.Filter)} for details, except this method * includes the option to not recurse into child filters. * @param baseFilter * @param targetFilter * @param recurse true if the method should descend into child group filters looking for the target * @return */ public Filter remove(Filter baseFilter, final Filter targetFilter, boolean recurse) { if (baseFilter == null){ //return null if nothing to start with return baseFilter; } if (targetFilter == null){ //similarly, just return the existing one if the target is null return baseFilter; } if (baseFilter.equals(targetFilter)){ //if they are the same filter, return Filter.INCLUDE to signify no filters left return Filter.INCLUDE; } if( !(baseFilter instanceof BinaryLogicOperator)){ return baseFilter; // nothing to traverse } if (recurse) { DuplicatingFilterVisitor remove = new DuplicatingFilterVisitor() { public Object visit(Or filter, Object extraData) { List<Filter> newChildren = children(filter, targetFilter, extraData); if (newChildren.isEmpty()) { // every time you remove a filter from an Or // expression you get less stuff, so removing the last is ... return Filter.EXCLUDE; } else if (newChildren.size() == 1) { return newChildren.get(0); } else { return getFactory(extraData).or(newChildren); } } public Object visit(And filter, Object extraData) { List<Filter> newChildren = children(filter, targetFilter, extraData); if (newChildren.isEmpty()) { // every time you remove a filter from an And // filter you get more stuff, so removing the last is ... return Filter.INCLUDE; } else if (newChildren.size() == 1) { return newChildren.get(0); } else { return getFactory(extraData).and(newChildren); } } private List<Filter> children(BinaryLogicOperator filter, final Filter targetFilter, Object extraData) { List<Filter> children = filter.getChildren(); List<Filter> newChildren = new ArrayList<Filter>(); for (Iterator<Filter> iter = children.iterator(); iter.hasNext();) { Filter child = iter.next(); if( targetFilter.equals(child)){ continue; // skip this one } if (child != null) { Filter newChild = (Filter) child.accept(this, extraData); newChildren.add(newChild); } } return newChildren; } }; return (Filter) baseFilter.accept(remove, ff); } else { BinaryLogicOperator blo = (BinaryLogicOperator) baseFilter; List<Filter> children = blo.getChildren(); if (children == null ){ children = Collections.emptyList(); } List<Filter> copy = new ArrayList<Filter>( children.size() ); for( Filter filter : children ){ if( targetFilter.equals(filter) ){ continue; // skip this one } copy.add( filter ); } if( copy.isEmpty() ){ if( baseFilter instanceof And){ // every time you remove a filter from an And // filter you get more stuff, so removing the last is ... return Filter.INCLUDE; } else if( baseFilter instanceof Or){ // every time you remove a filter from an Or // expression you get less stuff, so removing the last is ... return Filter.EXCLUDE; } else { return Filter.EXCLUDE; } } else if (copy.size() == 1){ return copy.get(0); } else if (baseFilter instanceof And){ return ff.and( children ); } else if (baseFilter instanceof Or){ return ff.or( children ); } else { return Filter.INCLUDE; } } } public static Filter removeFilter(Filter baseFilter, Filter targetFilter, boolean recurse) { return STATIC.remove(baseFilter, targetFilter, recurse ); } /** * Uses FilterAttributeExtractor to return the list of all mentioned attribute names. * <p> * You can use this method to quickly build up the set of any mentioned attribute names. * * @param filter * @return Set of propertyNames */ public Set<String> attributeNames( Filter filter ){ if( filter == null ){ return Collections.emptySet(); } FilterAttributeExtractor extractor = new FilterAttributeExtractor(); filter.accept( extractor, new HashSet<String>() ); return extractor.getAttributeNameSet(); } /** * Traverses the filter and returns any encountered property names. * <p> * The feature type is supplied as contexts used to lookup expressions in cases where the * attributeName does not match the actual name of the type. * </p> */ public static String[] attributeNames(Filter filter, final SimpleFeatureType featureType) { if (filter == null) { return new String[0]; } FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType); filter.accept(attExtractor, null); String[] attributeNames = attExtractor.getAttributeNames(); return attributeNames; } /** * Traverses the filter and returns any encountered property names. * <p> * The feature type is supplied as contexts used to lookup expressions in cases where the * attributeName does not match the actual name of the type. * </p> */ public static Set<PropertyName> propertyNames(Filter filter, final SimpleFeatureType featureType) { if (filter == null) { return Collections.emptySet(); } FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType); filter.accept(attExtractor, null); Set<PropertyName> propertyNames = attExtractor.getPropertyNameSet(); return propertyNames; } /** * Traverses the filter and returns any encountered property names. */ public static Set<PropertyName> propertyNames(Filter filter) { return propertyNames(filter, null); } /** * Traverses the expression and returns any encountered property names. * <p> * The feature type is supplied as contexts used to lookup expressions in cases where the * attributeName does not match the actual name of the type. * </p> */ public static Set<PropertyName> propertyNames(Expression expression, final SimpleFeatureType featureType) { if (expression == null) { return Collections.emptySet(); } FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType); expression.accept(attExtractor, null); Set<PropertyName> propertyNames = attExtractor.getPropertyNameSet(); return propertyNames; } /** * Traverses the expression and returns any encountered property names. */ public static Set<PropertyName> propertyNames(Expression expression) { return propertyNames(expression, null); } /** * True if the filter makes use of propertyName * <p> * Note this is a simple test and is faster than calling * <code>attributeNames( filter ).contains( name )</code> * @param filter * @param property - name of the property to look for * @return */ static boolean uses(Filter filter, final String propertyName ) { if (filter == null) { return false; } class SearchFilterVisitor extends AbstractSearchFilterVisitor { protected boolean found(Object data) { return Boolean.TRUE == data; } public Object visit(PropertyName name, Object data) { if( Utilities.equals(name.getPropertyName(), propertyName ) ){ return true; } return data; } }; SearchFilterVisitor search = new SearchFilterVisitor(); boolean found = (Boolean) filter.accept(search, false ); return found; } /** * Check if the provided filter has child filters of some sort. * <p> * Where a child filter is considered: * <ul> * <li>Not: has a single child filter being negated</li> * <li>And: has a list of child filters</li> * <li>Or: has a list of child filters</li> * </ul> * Any other filter will return false. * @param filter * @return list of child filters */ static public boolean hasChildren( Filter filter ){ return filter instanceof BinaryLogicOperator || filter instanceof Not; } /** * List of child filters. * * Where a child filter is considered: * <ul> * <li>Not: has a single child filter being negated</li> * <li>And: has a list of child filters</li> * <li>Or: has a list of child filters</li> * </ul> * Any other filters will return false. * <p> * This represents the space covered by a number of the search functions. * <p> * The returned list is a mutable copy that can be used with filter factory to construct a * new filter when you are ready. To make that explicit I am returning an ArrayList so it * is clear that the result can be modified. * </p> * @param filter * @return are belong to us */ static public ArrayList<Filter> children( Filter filter ){ return children( filter, false ); } /** * List of child filters. * * Where a child filter is considered: * <ul> * <li>Not: has a single child filter being negated</li> * <li>And: has a list of child filters</li> * <li>Or: has a list of child filters</li> * </ul> * Any other filters will return false. * <p> * This represents the space covered by a number of the search functions, if *all* is true * this function will recursively search for additional child filters beyond those directly * avaialble from your filter. * <p> * The returned list is a mutable copy that can be used with filter factory to construct a * new filter when you are ready. To make that explicit I am returning an ArrayList so it * is clear that the result can be modified. * </p> * @param filter * @param all true to recurse into the filter and retrieve all children; false to only * return the top level children * @return are belong to us */ static public ArrayList<Filter> children( Filter filter, boolean all ){ final ArrayList<Filter> children = new ArrayList<Filter>(); if( filter == null ){ return children; } if( all ){ filter.accept( new DefaultFilterVisitor() { public Object visit(And filter, Object data) { List<Filter> childList = filter.getChildren(); if (childList != null) { for( Filter child : childList) { if (child == null ) continue; children.add( child ); data = child.accept(this, data); } } return data; } public Object visit(Or filter, Object data) { List<Filter> childList = filter.getChildren(); if (childList != null) { for( Filter child : childList) { if (child == null ) continue; children.add( child ); data = child.accept(this, data); } } return data; } public Object visit(Not filter, Object data) { Filter child = filter.getFilter(); if (child != null) { children.add( child ); data = child.accept(this, data); } return data; } }, null ); } else { if( filter instanceof Not){ Not not = (Not) filter; if( not.getFilter() != null ){ children.add( not.getFilter() ); } } if (filter instanceof BinaryLogicOperator ){ BinaryLogicOperator parent = (BinaryLogicOperator) filter; List<Filter> reviewChildren = parent.getChildren(); if( reviewChildren != null ){ for( Filter child : reviewChildren ){ if( child != null ){ children.add(child); } } } } } return children; } /** * Find the first child-filter (or the base filter itself) that is of the given type and uses * the specified property. * @param filter * @param filterType - class of the filter to look for * @param property - name of the property to look for * @return */ public static <T extends Filter> T search(Filter filter, Class<T> filterType, String propertyName ){ List<Filter> allBase = children(filter); for( Filter base : allBase ){ if( filterType.isInstance(base) && uses(base, propertyName) ){ return filterType.cast(base); } } return null; // not found } /** * Given a filter which contains a term which is a PropertyName, returns the name of the property. * Returns null if no PropertyName is passed * @param filter * @return */ public static String findPropertyName(Filter filter) { if (filter == null) return null; class SearchFilterVisitor extends AbstractSearchFilterVisitor { protected boolean found(Object data) { return data != null; } public Object visit(PropertyName name, Object data) { return name.getPropertyName(); } }; SearchFilterVisitor search = new SearchFilterVisitor(); return (String)filter.accept(search, null ); } /** * Find all filters (including the base filter itself) that are of the given type and use * the specified property. * @param filter * @param filterType * @param property * @return all filters that are of the given type using the specified property */ static <T extends Filter> List<T> findAllByTypeAndName(Filter filter, Class<T> filterType, String property) { List<T> retVal = new ArrayList<T>(); List<Filter> allBase = children(filter); allBase.add( 0, filter ); for( Filter base : allBase ){ if( filterType.isInstance(base) && uses(base, property) ){ retVal.add( filterType.cast(base) ); } } return retVal; } }