/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-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.util.HashMap; import java.util.Map; import org.geotools.filter.capability.ArithmeticOperatorsImpl; import org.geotools.filter.capability.ComparisonOperatorsImpl; import org.geotools.filter.capability.FilterCapabilitiesImpl; import org.geotools.filter.capability.FunctionNameImpl; import org.geotools.filter.capability.FunctionsImpl; import org.geotools.filter.capability.OperatorImpl; import org.geotools.filter.capability.SpatialOperatorImpl; import org.geotools.filter.capability.SpatialOperatorsImpl; import org.geotools.filter.visitor.IsFullySupportedFilterVisitor; import org.geotools.filter.visitor.IsSupportedFilterVisitor; import org.geotools.filter.visitor.OperatorNameFilterVisitor; import org.opengis.filter.And; import org.opengis.filter.Filter; import org.opengis.filter.Id; 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.capability.FilterCapabilities; import org.opengis.filter.capability.GeometryOperand; import org.opengis.filter.expression.Add; import org.opengis.filter.expression.Divide; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Function; import org.opengis.filter.expression.Multiply; import org.opengis.filter.expression.Subtract; 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; /** * Allows for easier interaction with FilterCapabilities. * <p> * This class provides some out of the box FilterCapabilities constants that * you can quickly use to describe the encoding abilities of your service. * <p> * This class behaves similar to Citations in that the constants are to * be considered immutable, methods have been provided to assist * in composing your own set of FilterCapabilities. * <p> * Example:<pre><code> * Capabilities capabilities = new Capabilities(); * capabilities.addAll( Capabilities.LOGICAL ); * capabilities.addAll( Capabilities.SIMPLE_COMPARISONS ); * </code></pre> * You can use the Capabilities class at runtime to check * existing filters to see if they are fully supported:<pre><code> * if( fullySupports( filter )) { * // do something * } * </code></pre> * Right now the class gives no indication as to what part of the provided * filter was in error. * * @author Jody Garnett * * * @source $URL$ */ public class Capabilities { private static Map<Class<?>,String> scalarNames; static { scalarNames = new HashMap<Class<?>,String>(); scalarNames.put(PropertyIsEqualTo.class,PropertyIsEqualTo.NAME); scalarNames.put(PropertyIsNotEqualTo.class,PropertyIsNotEqualTo.NAME); scalarNames.put(PropertyIsGreaterThan.class,PropertyIsGreaterThan.NAME); scalarNames.put(PropertyIsGreaterThanOrEqualTo.class,PropertyIsGreaterThanOrEqualTo.NAME); scalarNames.put(PropertyIsLessThan.class,PropertyIsLessThan.NAME); scalarNames.put(PropertyIsLessThanOrEqualTo.class,PropertyIsLessThanOrEqualTo.NAME); scalarNames.put(PropertyIsNull.class,PropertyIsNull.NAME); scalarNames.put(PropertyIsLike.class,PropertyIsLike.NAME); scalarNames.put(PropertyIsBetween.class,PropertyIsBetween.NAME); } private static Map<Class<?>,String> spatialNames; static { spatialNames = new HashMap<Class<?>,String>(); spatialNames.put(BBOX.class, BBOX.NAME ); spatialNames.put(Equals.class, Equals.NAME); spatialNames.put(Disjoint.class,Disjoint.NAME); spatialNames.put(Intersects.class,Intersects.NAME); spatialNames.put(Touches.class,Touches.NAME); spatialNames.put(Crosses.class,Crosses.NAME); spatialNames.put(Within.class,Within.NAME); spatialNames.put(Contains.class,Contains.NAME); spatialNames.put(Overlaps.class,Overlaps.NAME); spatialNames.put(Beyond.class,Beyond.NAME); spatialNames.put(DWithin.class,DWithin.NAME); } private static Map<Class<?>,String> logicalNames; static { logicalNames = new HashMap<Class<?>,String>(); logicalNames.put(And.class,"And"); // not an operator name, see scalarCapabilities.hasLogicalOperators() logicalNames.put(Or.class, "Or"); // not an operator name, see scalarCapabilities.hasLogicalOperators() logicalNames.put(Not.class, "Not"); // not an operator name, see scalarCapabilities.hasLogicalOperators() } private static Map<Class<?>,String> filterNames; static { filterNames = new HashMap<Class<?>,String>(); filterNames.putAll( scalarNames ); filterNames.putAll( spatialNames ); filterNames.putAll( logicalNames ); filterNames.put(Id.class, "Id"); // not an operator name, see idCapabilities.hasFID() or idCapabilities.hasEID() } private static Map<Class<? extends Expression>,String> arithmaticNames; static { arithmaticNames = new HashMap<Class<? extends Expression>,String>(); arithmaticNames.put(Add.class, Add.NAME ); arithmaticNames.put(Subtract.class, Subtract.NAME ); arithmaticNames.put(Multiply.class, Multiply.NAME ); arithmaticNames.put(Divide.class, Divide.NAME); } private static Map<Class<? extends Expression>,String> exprNames; static { exprNames = new HashMap<Class<? extends Expression>, String>(); exprNames.putAll( arithmaticNames ); // while function is an expression, we should check the name exprNames.put(Function.class,"Function"); } private static final OperatorNameFilterVisitor operationNameVisitor = new OperatorNameFilterVisitor(); /** Support for logical types AND, OR and NOT */ public static Capabilities LOGICAL; static { LOGICAL = new Capabilities(); LOGICAL.addType(And.class); LOGICAL.addType(Not.class); LOGICAL.addType(Or.class); } public static Capabilities LOGICAL_OPENGIS = LOGICAL; /** * Capabilities representing the simple comparisions. */ public static Capabilities SIMPLE_COMPARISONS; static { SIMPLE_COMPARISONS = new Capabilities(); SIMPLE_COMPARISONS.addType( PropertyIsEqualTo.class ); //COMPARE_EQUALS| SIMPLE_COMPARISONS.addType( PropertyIsGreaterThan.class ); // COMPARE_GREATER_THAN SIMPLE_COMPARISONS.addType( PropertyIsGreaterThanOrEqualTo.class ); // COMPARE_GREATER_THAN_EQUAL SIMPLE_COMPARISONS.addType( PropertyIsLessThan.class ); // COMPARE_LESS_THAN SIMPLE_COMPARISONS.addType( PropertyIsGreaterThanOrEqualTo.class ); // COMPARE_LESS_THAN_EQUAL SIMPLE_COMPARISONS.addType( PropertyIsNotEqualTo.class ); // COMPARE_NOT_EQUALS; } public static Capabilities SIMPLE_COMPARISONS_OPENGIS = SIMPLE_COMPARISONS; /** * This is a quick visitor (returning true / false) that only * checks one level deep. */ IsSupportedFilterVisitor supportedVisitor; /** * Visitor (returning true / false) if the provided filter is supported * by our FilterCapabilities. */ IsFullySupportedFilterVisitor fullySupportedVisitor; /** * Internal FilterCapabilities data structure used * to maintain state. */ FilterCapabilitiesImpl contents; public Capabilities(){ this( new FilterCapabilitiesImpl() ); } public Capabilities( FilterCapabilities contents ){ if( contents instanceof FilterCapabilitiesImpl){ this.contents = (FilterCapabilitiesImpl) contents; } else { this.contents = new FilterCapabilitiesImpl( contents ); } supportedVisitor = new IsSupportedFilterVisitor( contents ); fullySupportedVisitor = new IsFullySupportedFilterVisitor( contents ); } /** * Returns the internal FilterCapabilities data structure * used for checking. * * @return FilterCapabilities */ public FilterCapabilitiesImpl getContents() { return contents; } /** * Adds a new support type to capabilities. * <p> * This is the same as:<code>addName( toOperationName( type ) ) * <p> * @param type the Class that indicates the new support. */ public void addType( Class type ){ String name = toOperationName( type ); if( name == null ) return; addName( name ); } /** * Adds support for the provided name. * <p> * If this is a known name (avaialble as part of opengis interface) * it will be grouped into: * <ul> * <li>Spatial Operators: Will added a SpatialOperator into the mix with Point, LineString, Polygon as the supported geometry * operands (based on the assumption of JTS) * <li>Comparison Operators: * <li>Arithmetic Operators: will cause hassimpleArithmetic to be true * <li>Other: will be treated as a no argument function call * </ul> * This method will have no effect if the operator is already known. * <p> * Examples:<pre><code> * capabilities.addName("Beyond"); // will enabled Beyond Filter * capabilities.addName("NullCheck"); // will enable PropertyIsNull Filter * capabilities.addName("SUB"); // will enabled hasSimpleArithmetic * capabilities.addName("PI"); // add a no argument function called PI() * </code></pre> * * @param name FilterCapabilities Operand name such as "BBOX", "Like" or "MUL" */ public void addName( String name ){ if( name == null ){ return; } else if( spatialNames.containsValue( name )){ SpatialOperatorsImpl operators = contents.getSpatialCapabilities().getSpatialOperators(); if( operators.getOperator( name ) == null ){ SpatialOperatorImpl operator = new SpatialOperatorImpl(name); // default JTS? operator.getGeometryOperands().add( GeometryOperand.LineString ); operator.getGeometryOperands().add( GeometryOperand.Point ); operator.getGeometryOperands().add( GeometryOperand.Polygon ); operators.getOperators().add( operator ); } } else if( scalarNames.containsValue( name )){ ComparisonOperatorsImpl operators = contents.getScalarCapabilities().getComparisonOperators(); if( operators.getOperator( name ) == null ){ OperatorImpl operator = new OperatorImpl( name ); operators.getOperators().add( operator ); } } else if( arithmaticNames.containsValue( name )){ ArithmeticOperatorsImpl operators = contents.getScalarCapabilities().getArithmeticOperators(); operators.setSimpleArithmetic(true); } else if( logicalNames.containsValue( name )){ contents.getScalarCapabilities().setLogicalOperators(true); } else if( "Id".equals(name)){ contents.getIdCapabilities().setFID(true); } else { FunctionsImpl functions = contents.getScalarCapabilities().getArithmeticOperators().getFunctions(); if( functions.getFunctionName( name ) == null ){ FunctionNameImpl function = new FunctionNameImpl( name, 0 ); functions.getFunctionNames().add( function ); } } } /** * Will add support for a function with the provided number of arguments * <p> * This method will have no effect if the function is already listed. * <p> * Example:<code>capabilities.addName( "Length", 1 )</code> * * @param name * @param argumentCount */ public void addName( String name, int argumentCount ){ FunctionsImpl functions = contents.getScalarCapabilities().getArithmeticOperators().getFunctions(); if( functions.getFunctionName( name ) == null ){ FunctionNameImpl function = new FunctionNameImpl( name, argumentCount ); functions.getFunctionNames().add( function ); } } /** * Document support for the provided function. * <p> * This method will have no effect if the function is already listed. * <p> * Example:<code>capabilities.addName( "Min", "value1", "value2" )</code> * @param name * @param argumentCount */ public void addName( String name, String... argumentNames ){ FunctionsImpl functions = contents.getScalarCapabilities().getArithmeticOperators().getFunctions(); if( functions.getFunctionName( name ) == null ){ FunctionNameImpl function = new FunctionNameImpl( name, argumentNames ); functions.getFunctionNames().add( function ); } } /** * Determines if specific filter passed in is supported. * * @see IsSupportedFilterVisitor * @param filter The Filter to be tested. * @return true if supported, false otherwise. */ public boolean supports(Filter filter) { if (filter == null) { return false; } if( supportedVisitor == null ){ supportedVisitor = new IsSupportedFilterVisitor( contents ); } return (Boolean) filter.accept( supportedVisitor, null ); } /** * Determines if the filter and all its sub filters and expressions are supported. * <p> * Is most important for logic filters, as they are the only ones with * subFilters. The geoapi FilterVisitor and ExpressionVisitors * allow for the handling of null, even so care should be taken to use * Filter.INCLUDE and Expression.NIL where you can. * <p> * @see IsFullySupportedFilterVisitor * @param filter the filter to be tested. * @return true if all sub filters are supported, false otherwise. */ public boolean fullySupports(Filter filter) { if (filter == null) { return false; } if( fullySupportedVisitor == null ){ fullySupportedVisitor = new IsFullySupportedFilterVisitor( contents ); } return (Boolean) filter.accept( fullySupportedVisitor, null ); } /** * Determines if the expression and all its sub expressions is supported. * <p> * The Expression visitor used for this work can handle null, even so care * should be taken to useExpression.NIL where you can. * <p> * @see IsFullySupportedFilterVisitor * @param filter the filter to be tested. * @return true if all sub filters are supported, false otherwise. */ public boolean fullySupports(Expression expression) { if (expression == null) { return false; } if( fullySupportedVisitor == null ){ fullySupportedVisitor = new IsFullySupportedFilterVisitor( contents ); } return (Boolean) expression.accept( fullySupportedVisitor, null ); } /** * Quickly look at the filter and determine the OperationName * we need to check for in the FilterCapabilities data structure. * * @param filter * @return Operation name */ public String toOperationName( Filter filter ){ if( filter == null ) return null; return (String) filter.accept( operationNameVisitor, null); } /** * Figure out the OperationName for the provided filterType. * <p> * The returned name can be used to check the FilterCapabilities * to see if it type is supported in this execution context. * <p> * This approach is not applicable for Functions. * <p> * @param filterType Filter type * @return Operation name for the provided FilterType */ public String toOperationName( Class filterType ){ if( filterType == null ) return null; String quick = filterNames.get( filterType ); if( quick != null ) { return quick; } // The following is O(N) and slightly wrong in that And.class is not an operator for( Map.Entry<Class<?>,String> mapping : filterNames.entrySet() ){ if( mapping.getKey().isAssignableFrom( filterType )){ return mapping.getValue(); } } /* // The following is < O(N) but more complicated to maintain if( SpatialOperator.class.isAssignableFrom(filterType)) { if( BBOX.class.isAssignableFrom(filterType)){ return BBOX.NAME; } else if ( Contains.class.isAssignableFrom(filterType)){ return Contains.NAME; } else if ( Crosses.class.isAssignableFrom(filterType)){ return Crosses.NAME; } else if ( Disjoint.class.isAssignableFrom(filterType)){ return Disjoint.NAME; } else if ( Beyond.class.isAssignableFrom(filterType)){ return Beyond.NAME; } else if ( DWithin.class.isAssignableFrom(filterType)){ return DWithin.NAME; } else if ( Equals.class.isAssignableFrom(filterType)){ return Equals.NAME; } else if ( Intersects.class.isAssignableFrom(filterType)){ return Intersects.NAME; } else if ( Overlaps.class.isAssignableFrom(filterType)){ return Overlaps.NAME; } else if ( Touches.class.isAssignableFrom(filterType)){ return Touches.NAME; } else if ( Within.class.isAssignableFrom(filterType)){ return Within.NAME; } } else if( BinaryComparisonOperator.class.isAssignableFrom(filterType) ){ if ( PropertyIsEqualTo.class.isAssignableFrom(filterType)){ return PropertyIsEqualTo.NAME; } else if ( PropertyIsGreaterThan.class.isAssignableFrom(filterType)){ return PropertyIsGreaterThan.NAME; } else if ( PropertyIsGreaterThanOrEqualTo.class.isAssignableFrom(filterType)){ return PropertyIsGreaterThanOrEqualTo.NAME; } else if ( PropertyIsLessThan.class.isAssignableFrom(filterType)){ return PropertyIsLessThan.NAME; } else if ( PropertyIsLessThanOrEqualTo.class.isAssignableFrom(filterType)){ return PropertyIsLessThanOrEqualTo.NAME; } else if ( PropertyIsNotEqualTo.class.isAssignableFrom(filterType)){ return PropertyIsNotEqualTo.NAME; } } else if( PropertyIsBetween.class.isAssignableFrom(filterType)) { return PropertyIsBetween.NAME; } else if( PropertyIsLike.class.isAssignableFrom(filterType)) { return PropertyIsLike.NAME; } else if( PropertyIsNull.class.isAssignableFrom(filterType)) { return PropertyIsNull.NAME; } else if( Function.class.isAssignableFrom(filterType)) { throw new IllegalArgumentException("Cannot determine function name from class"); } */ return null; } public void addAll( Capabilities copy ){ addAll( copy.getContents() ); } public void addAll( FilterCapabilities copy ) { contents.addAll( copy ); } }