/* * 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. *
* 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. *
** Example use: *
* Filters filters = new Filters( factory );
* filters.duplicate( original );
*
* The above example creates a copy of the provided Filter,
* the factory provided will be used when creating the duplicated
* content.
*
* * 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. *
*
* double sum = Filters.number( Object ) + Filters.number( Object );
*
* The above example will support the conversion of many things into a format
* suitable for addition - the complete list is something like:
* * 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. *
* * @author Jody Garnett (LISAsoft) * @since GeoTools 2.2 * @version 8.0 * * @source $URL$ */ public class Filters { /**NOTFOUND
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.
*
* 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
* This method handles the case of:
*
* Filter objects are mutable, when copying a rich
* data structure (like SLD) you will need to duplicate
* the Filters referenced therein.
*
* This utility method is designed to help people port their
* code quickly, an instanceof check is much preferred.
*
* 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.
*
* 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.
*
* 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).
*
* If you have a specific Feature, please do this:
*
*
* 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.
*
* @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.
*
* BEFORE: filter.getFilterType() == FilterType.GEOMETRY_CONTAINS
* QUICK: Filters.getFilterType( filter ) == FilterType.GEOMETRY_CONTAINS
* AFTER: filter instanceof Contains
*
*
* Color value = expr.evaualte( feature, Color.class );
* return value instanceof Color ? (Color) value : null;
*
* This function allows for the non stongly typed Math Opperations * favoured by the Expression standard. *
** Able to hanle: *
* 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. *
*
* A couple notes: ** Examples: *
* This method has been superseeded by Converters * which offers a more general and open ended solution. *
* @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. ** This method has been superseeded by Converters which offers a more general and open ended * solution. *
* * @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. *
* You can use this method to quickly build up the set of any mentioned attribute names.
*
* @param filter
* @return Set of propertyNames
*/
public Set
* 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.
*
* 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.
*
* 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.
*
* Note this is a simple test and is faster than calling
*
* Where a child filter is considered:
*
* This represents the space covered by a number of the search functions.
*
* 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.
*
* 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.
*
* 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.
* attributeNames( filter ).contains( name )
* @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.
*
*
* 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:
*
*
* Any other filters will return false.
*
*
* Any other filters will return false.
*