kinow commented on a change in pull request #23:
URL: https://github.com/apache/sis/pull/23#discussion_r656801677



##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
##########
@@ -511,19 +540,29 @@ private static Number number(final boolean result) {
 
 
     /**
-     * The {@value #NAME} {@literal (<)} filter.
+     * The {@code "PropertyIsLessThan"} {@literal (<)} filter.
      */
-    static final class LessThan extends ComparisonFunction implements 
org.opengis.filter.PropertyIsLessThan {
+    static final class LessThan<R> extends ComparisonFunction<R> {
         /** For cross-version compatibility during (de)serialization. */
         private static final long serialVersionUID = 6126039112844823196L;
 
-        /** Creates a new filter for the {@value #NAME} operation. */
-        LessThan(Expression expression1, Expression expression2, boolean 
isMatchingCase, MatchAction matchAction) {
+        /** Creates a new filter. */
+        LessThan(final Expression<? super R, ?> expression1,
+                 final Expression<? super R, ?> expression2,
+                 boolean isMatchingCase, MatchAction matchAction)
+        {
             super(expression1, expression2, isMatchingCase, matchAction);
         }
 
-        /** Identification of this operation. */
-        @Override public String getName() {return NAME;}
+        /** Creates a new filter of the same type but different parameters. */
+        @Override public Filter<R> recreate(final Expression<? super R, ?>[] 
effective) {
+            return new LessThan<>(effective[0], effective[1], isMatchingCase, 
matchAction);
+        }
+
+        /** Identification of the this operation. */

Review comment:
       I think either the or this, not both?

##########
File path: 
storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
##########
@@ -16,506 +16,330 @@
  */
 package org.apache.sis.internal.sql.feature;
 
-import java.util.Arrays;
 import java.util.List;
-import java.util.Optional;
-import java.util.function.BooleanSupplier;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+import java.util.Collections;
+import java.util.function.BiConsumer;
+import org.opengis.util.LocalName;
+import org.opengis.util.GenericName;
+import org.opengis.util.NameSpace;
+import org.opengis.util.NameFactory;
+import org.opengis.geometry.Envelope;
+import org.opengis.geometry.Geometry;
+import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.WraparoundMethod;
 import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.internal.feature.GeometryWrapper;
+import org.apache.sis.internal.filter.FunctionNames;
+import org.apache.sis.internal.filter.Visitor;
+import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.setup.GeometryLibrary;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.iso.Names;
-import org.opengis.filter.*;
-import org.opengis.filter.expression.Add;
-import org.opengis.filter.expression.BinaryExpression;
-import org.opengis.filter.expression.Divide;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.ExpressionVisitor;
-import org.opengis.filter.expression.Function;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.expression.Multiply;
-import org.opengis.filter.expression.NilExpression;
-import org.opengis.filter.expression.PropertyName;
-import org.opengis.filter.expression.Subtract;
-import org.opengis.filter.spatial.BBOX;
-import org.opengis.filter.spatial.Beyond;
-import org.opengis.filter.spatial.BinarySpatialOperator;
-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;
-import org.opengis.filter.temporal.*;
-import org.opengis.geometry.Envelope;
-import org.opengis.geometry.Geometry;
-import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.util.GenericName;
-import org.opengis.util.LocalName;
+
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+import org.opengis.filter.ValueReference;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.LogicalOperatorName;
+import org.opengis.filter.ComparisonOperator;
+import org.opengis.filter.ComparisonOperatorName;
+import org.opengis.filter.BinaryComparisonOperator;
+import org.opengis.filter.SpatialOperator;
+import org.opengis.filter.SpatialOperatorName;
+import org.opengis.filter.BetweenComparisonOperator;
+import org.opengis.filter.LikeOperator;
+
 
 /**
- * Port of Geotk FilterToSQL for an ANSI compliant query builder.
+ * Writes SQL statement for a filter or an expression.
+ * This base class is restricted to ANSI compliant SQL.
  *
  * @implNote For now, we over-use parenthesis to ensure consistent operator 
priority. In the future, we could evolve
  * this component to provide more elegant transcription of filter groups.
  *
  * No case insensitive support of binary comparison is done.
  *
- * TODO: define a set of accepter property names (even better: link to {@link 
FeatureAdapter}), so any {@link PropertyName}
+ * TODO: define a set of accepter property names (even better: link to {@link 
FeatureAdapter}), so any {@link ValueReference}
  * filter refering to non pure SQL property (like relations) will cause a 
failure.
  *
- * @author Alexis Manin (Geomatys)
- * @version 2.0
- * @since   2.0
+ * @author  Alexis Manin (Geomatys)
+ * @version 1.1
+ * @since   1.1
  * @module
  */
-public class ANSIInterpreter implements FilterVisitor, ExpressionVisitor {
+class ANSIInterpreter extends Visitor<Feature,StringBuilder> {
     /**
      * TODO
      */
     private static final GeometryLibrary LIBRARY = null;
 
-    private final java.util.function.Function<Literal, CharSequence> 
valueFormatter;
+    private final NameFactory nameFactory;
 
-    private final java.util.function.Function<PropertyName, CharSequence> 
nameFormatter;
+    private final NameSpace scope;
 
     public ANSIInterpreter() {
-        this(ANSIInterpreter::format, ANSIInterpreter::format);
-    }
-
-    public ANSIInterpreter(
-            java.util.function.Function<Literal, CharSequence> valueFormatter,
-            java.util.function.Function<PropertyName, CharSequence> 
nameFormatter)
-    {
-        ArgumentChecks.ensureNonNull("valueFormatter", valueFormatter);
-        ArgumentChecks.ensureNonNull("nameFormatter", nameFormatter);
-        this.valueFormatter = valueFormatter;
-        this.nameFormatter = nameFormatter;
-    }
-
-    @Override
-    public CharSequence visitNullFilter(Object extraData) {
-        throw new UnsupportedOperationException("Null filter is not 
supported.");
-    }
-
-    @Override
-    public Object visit(ExcludeFilter filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(IncludeFilter filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public CharSequence visit(And filter, Object extraData) {
-        return join(filter, " AND ", extraData);
-    }
-
-    @Override
-    public Object visit(Id filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(Not filter, Object extraData) {
-        final CharSequence innerFilter = evaluateMandatory(filter.getFilter(), 
extraData);
-        return "NOT (" + innerFilter + ")";
-    }
-
-    @Override
-    public Object visit(Or filter, Object extraData) {
-        return join(filter, " OR ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsBetween filter, Object extraData) {
-        final CharSequence propertyExp = 
evaluateMandatory(filter.getExpression(), extraData);
-        final CharSequence lowerExp = 
evaluateMandatory(filter.getLowerBoundary(), extraData);
-        final CharSequence upperExp = 
evaluateMandatory(filter.getUpperBoundary(), extraData);
-
-        return new StringBuilder(propertyExp)
-                .append(" BETWEEN ")
-                .append(lowerExp)
-                .append(" AND ")
-                .append(upperExp);
-    }
-
-    @Override
-    public Object visit(PropertyIsEqualTo filter, Object extraData) {
-        return joinMatchCase(filter, " = ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsNotEqualTo filter, Object extraData) {
-        return joinMatchCase(filter, " <> ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsGreaterThan filter, Object extraData) {
-        return joinMatchCase(filter, " > ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object 
extraData) {
-        return joinMatchCase(filter, " >= ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsLessThan filter, Object extraData) {
-        return joinMatchCase(filter, " < ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsLessThanOrEqualTo filter, Object extraData) {
-        return joinMatchCase(filter, " <= ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsLike filter, Object extraData) {
-        // TODO: PostgreSQL extension : ilike
-        ensureMatchCase(filter::isMatchingCase);
-        // TODO: port Geotk
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(PropertyIsNull filter, Object extraData) {
-        return evaluateMandatory(filter.getExpression(), extraData) + " IS 
NULL";
-    }
-
-    @Override
-    public Object visit(PropertyIsNil filter, Object extraData) {
-        return evaluateMandatory(filter.getExpression(), extraData) + " IS 
NULL";
+        nameFactory = DefaultFactories.forBuildin(NameFactory.class);
+        scope = nameFactory.createNameSpace(nameFactory.createLocalName(null, 
"xpath"),
+                                            
Collections.singletonMap("separator", "/"));
+
+        setFilterHandler(LogicalOperatorName.AND, new BinaryLogicJoin(" AND 
"));
+        setFilterHandler(LogicalOperatorName.OR,  new BinaryLogicJoin(" OR "));
+        setFilterHandler(LogicalOperatorName.NOT, (f,sb) -> {
+            final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) 
f;
+            evaluateMandatory(sb.append("NOT ("), filter.getOperands().get(0));
+            sb.append(')');
+        });
+        
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN),
 (f,sb) -> {
+            final BetweenComparisonOperator<Feature>  filter = 
(BetweenComparisonOperator<Feature>) f;
+            evaluateMandatory(sb,                     filter.getExpression());
+            evaluateMandatory(sb.append(" BETWEEN "), 
filter.getLowerBoundary());
+            evaluateMandatory(sb.append(" AND "),     
filter.getUpperBoundary());
+        });
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO,          
       new JoinMatchCase(" = "));
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_NOT_EQUAL_TO,      
       new JoinMatchCase(" <> "));
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN,      
       new JoinMatchCase(" > "));
+        
setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO, 
new JoinMatchCase(" >= "));
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN,         
       new JoinMatchCase(" < "));
+        
setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,    
new JoinMatchCase(" <= "));
+        
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE),
 (f,sb) -> {
+            final LikeOperator<Feature> filter = (LikeOperator<Feature>) f;
+            ensureMatchCase(filter.isMatchingCase());
+            // TODO: port Geotk
+            throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
+        });
+        setNullAndNilHandlers((f,sb) -> {
+            final ComparisonOperator<Feature> filter = 
(ComparisonOperator<Feature>) f;
+            evaluateMandatory(sb, filter.getExpressions().get(0));
+            sb.append(" IS NULL");
+        });
+        /*
+         * SPATIAL FILTERS
+         */
+        setFilterHandler(SpatialOperatorName.BBOX, (f,sb) -> {
+            final SpatialOperator<Feature> filter = (SpatialOperator<Feature>) 
f;
+            // TODO: This is a wrong interpretation, but sqlmm has no 
equivalent of filter encoding bbox, so we'll
+            // fallback on a standard intersection. However, PostGIS, H2, etc. 
have their own versions of such filters.
+            for (final Expression<? super Feature, ?> e : 
filter.getExpressions()) {
+                if (e == null) {
+                    throw new UnsupportedOperationException("Not supported 
yet: bbox over all geometric properties");
+                }
+            }
+            bbox(sb, filter);
+        });
+        setFilterHandler(SpatialOperatorName.CONTAINS,   new 
Function(FunctionNames.ST_Contains));
+        setFilterHandler(SpatialOperatorName.CROSSES,    new 
Function(FunctionNames.ST_Crosses));
+        setFilterHandler(SpatialOperatorName.DISJOINT,   new 
Function(FunctionNames.ST_Disjoint));
+        setFilterHandler(SpatialOperatorName.EQUALS,     new 
Function(FunctionNames.ST_Equals));
+        setFilterHandler(SpatialOperatorName.INTERSECTS, new 
Function(FunctionNames.ST_Intersects));
+        setFilterHandler(SpatialOperatorName.OVERLAPS,   new 
Function(FunctionNames.ST_Overlaps));
+        setFilterHandler(SpatialOperatorName.TOUCHES,    new 
Function(FunctionNames.ST_Touches));
+        setFilterHandler(SpatialOperatorName.WITHIN,     new 
Function(FunctionNames.ST_Within));
+        /*
+         * Expression visitor
+         */
+        setExpressionHandler(FunctionNames.Add,      new Join(" + "));
+        setExpressionHandler(FunctionNames.Subtract, new Join(" - "));
+        setExpressionHandler(FunctionNames.Divide,   new Join(" / "));
+        setExpressionHandler(FunctionNames.Multiply, new Join(" * "));
+        setExpressionHandler(FunctionNames.Literal, (e,sb) -> writeLiteral(sb, 
(Literal<Feature,?>) e));
+        setExpressionHandler(FunctionNames.ValueReference, (e,sb) -> 
writeColumnName(sb, (ValueReference<Feature,?>) e));
     }
 
-    /*
-     * SPATIAL FILTERS
+    /**
+     * Returns the SQL fragment to use for {@link SpatialOperatorName#BBOX} 
type of filter.
      */
-
-    @Override
-    public Object visit(BBOX filter, Object extraData) {
-        // TODO: This is a wrong interpretation, but sqlmm has no equivalent 
of filter encoding bbox, so we'll
-        // fallback on a standard intersection. However, PostGIS, H2, etc. 
have their own versions of such filters.
-        if (filter.getExpression1() == null || filter.getExpression2() == null)
-            throw new UnsupportedOperationException("Not supported yet : bbox 
over all geometric properties");
-        return function("ST_Intersects", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Beyond filter, Object extraData) {
-        // TODO: ISO SQL specifies that unit of distance could be specified. 
However, PostGIS documentation does not
-        // talk about it. For now, we'll fallback on Java implementation until 
we're sure how to perform native
-        // operation properly.
-        throw new UnsupportedOperationException("Not yet: unit management 
ambiguous");
-    }
-
-    @Override
-    public Object visit(Contains filter, Object extraData) {
-        return function("ST_Contains", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Crosses filter, Object extraData) {
-        return function("ST_Crosses", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Disjoint filter, Object extraData) {
-        return function("ST_Disjoint", filter, extraData);
-    }
-
-    @Override
-    public Object visit(DWithin filter, Object extraData) {
-        // TODO: as for beyond filter above, unit determination is a bit 
complicated.
-        throw new UnsupportedOperationException("Not yet: unit management to 
handle properly");
-    }
-
-    @Override
-    public Object visit(Equals filter, Object extraData) {
-        return function("ST_Equals", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Intersects filter, Object extraData) {
-        return function("ST_Intersects", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Overlaps filter, Object extraData) {
-        return function("ST_Overlaps", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Touches filter, Object extraData) {
-        return function("ST_Touches", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Within filter, Object extraData) {
-        return function("ST_Within", filter, extraData);
+    void bbox(final StringBuilder sb, final SpatialOperator<Feature> filter) {
+        function(sb, "ST_Intersects", filter);
+    }
+
+    private static void writeLiteral(final StringBuilder sb, final 
Literal<Feature,?> literal) {
+        Object value;
+        if (literal == null || (value = literal.getValue()) == null) {
+            sb.append("NULL");
+        } else if (value instanceof CharSequence) {
+            String text = value.toString();
+            text = text.replace("'", "''");
+            sb.append('\'').append(text).append('\'');
+        } else if (value instanceof Number || value instanceof Boolean) {
+            sb.append(value);
+        } else {
+            // Geometric special cases
+            if (value instanceof GeographicBoundingBox) {
+                value = new GeneralEnvelope((GeographicBoundingBox) value);
+            }
+            if (value instanceof Envelope) {
+                value = asGeometry((Envelope) value);
+            }
+            if (value instanceof Geometry) {
+                format(sb, (Geometry) value);
+            } else {
+                throw new UnsupportedOperationException("Not supported yet: 
Literal value of type " + value.getClass());
+            }
+        }
     }
 
-    /*
-     * TEMPORAL OPERATORS
+    /**
+     * Beware ! This implementation is a naïve one, expecting given property 
name to match exactly SQL database names.
+     * In the future, it would be appreciable to be able to configure a mapper 
between feature and SQL names.
+     *
+     * @param  candidate  name of property to insert in SQL statement.
      */
-
-    @Override
-    public Object visit(After filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(AnyInteracts filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(Before filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(Begins filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(BegunBy filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(During filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(EndedBy filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
+    private void writeColumnName(final StringBuilder sb, final 
ValueReference<Feature,?> candidate) {
+        final GenericName name = nameFactory.parseGenericName(scope, 
candidate.getXPath());
+        final List<? extends LocalName> components = name.getParsedNames();
+        final int n = components.size();
+        for (int i=0; i<n; i++) {
+            if (i != 0) sb.append('.');
+            sb.append('"').append(components.get(i)).append('"');
+        }
     }
 
-    @Override
-    public Object visit(Ends filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
+    private final class BinaryLogicJoin implements BiConsumer<Filter<Feature>, 
StringBuilder> {
+        private final String operator;
 
-    @Override
-    public Object visit(Meets filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(MetBy filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(OverlappedBy filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
+        BinaryLogicJoin(final String operator) {
+            this.operator = operator;
+        }
 
-    @Override
-    public Object visit(TContains filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
+        @Override
+        public void accept(final Filter<Feature> f, final StringBuilder sb) {
+            final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) 
f;
+            final List<Filter<? super Feature>> subFilters = 
filter.getOperands();
+            final int n = subFilters.size();
+            if (n != 0) {
+                sb.append('(');
+                for (int i=0; i<n; i++) {
+                    if (i != 0) sb.append(operator);
+                    evaluateMandatory(sb, subFilters.get(i));
+                }
+                sb.append(')');
+            }
+        }
     }
 
-    @Override
-    public Object visit(TEquals filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
+    private void join(final StringBuilder sb,
+                      final List<Expression<? super Feature, ?>> expressions,
+                      final int maxCount, final String operator)
+    {
+        sb.append('(');
+        final int n = Math.min(expressions.size(), maxCount);
+        for (int i=0; i<n; i++) {
+            if (i != 0) sb.append(operator);
+            evaluateMandatory(sb, expressions.get(i));
+        }
+        sb.append(')');
     }
 
-    @Override
-    public Object visit(TOverlaps filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
+    private final class JoinMatchCase implements BiConsumer<Filter<Feature>, 
StringBuilder> {
+        private final String operator;
 
-    /*
-     * Expression visitor
-     */
+        JoinMatchCase(final String operator) {
+            this.operator = operator;
+        }
 
-    @Override
-    public Object visit(NilExpression expression, Object extraData) {
-        return "NULL";
+        @Override
+        public void accept(final Filter<Feature> f, final StringBuilder sb) {
+            final BinaryComparisonOperator<Feature> filter = 
(BinaryComparisonOperator<Feature>) f;
+            ensureMatchCase(filter.isMatchingCase());
+            join(sb, filter.getExpressions(), 2, operator);
+        }
     }
 
-    @Override
-    public Object visit(Add expression, Object extraData) {
-        return join(expression, " + ", extraData);
+    protected final void join(final StringBuilder sb, final 
SpatialOperator<Feature> op, final String operator) {
+        join(sb, op.getExpressions(), 2, operator);
     }
 
-    @Override
-    public Object visit(Divide expression, Object extraData) {
-        return join(expression, " / ", extraData);
-    }
+    private final class Join implements BiConsumer<Expression<Feature,?>, 
StringBuilder> {
+        private final String operator;
 
-    @Override
-    public Object visit(Function expression, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 01/10/2019
-    }
+        Join(final String operator) {
+            this.operator = operator;
+        }
 
-    @Override
-    public Object visit(Literal expression, Object extraData) {
-        return valueFormatter.apply(expression);
+        @Override
+        public void accept(final Expression<Feature,?> expression, final 
StringBuilder sb) {
+            join(sb, expression.getParameters(), 2, operator);
+        }
     }
 
-    @Override
-    public Object visit(Multiply expression, Object extraData) {
-        return join(expression, " * ", extraData);
-    }
+    private final class Function implements BiConsumer<Filter<Feature>, 
StringBuilder> {
+        private final String name;
 
+        Function(final String name) {
+            this.name = name;
+        }
 
-    @Override
-    public Object visit(PropertyName expression, Object extraData) {
-        return nameFormatter.apply(expression);
+        @Override
+        public void accept(final Filter<Feature> f, final StringBuilder sb) {
+            function(sb, name, (SpatialOperator<Feature>) f);
+        }
     }
 
-    @Override
-    public Object visit(Subtract expression, Object extraData) {
-        return join(expression, " - ", extraData);
+    private void function(final StringBuilder sb, final String name, final 
SpatialOperator<Feature> filter) {
+        join(sb.append(name), filter.getExpressions(), Integer.MAX_VALUE, ", 
");
     }
 
-    /*
-     * UTILITIES
+    /**
+     * Executes the registered action for the given filter.
+     * Throws an exception if the filter did not wrote anything in the buffer.
+     *
+     * <h4>Note on type safety</h4>
+     * This method signature uses {@code <? super R>} for caller's convenience 
because this is the type that
+     * we get from {@link LogicalOperator#getOperands()}. But the {@link 
BiConsumer} uses exactly {@code <R>}
+     * type because doing otherwise causes complications with types that can 
not be expressed in Java (kinds
+     * of {@code <? super ? super R>}). The cast in this method is okay if we 
do not invoke any {@code filter}
+     * method with a return value (directly or indirectly as list elements) of 
exactly {@code <R>} type.
+     * Such methods do not exist in the GeoAPI interfaces, so we are safe if 
the {@link BiConsumer}
+     * does not invoke implementation-specific methods.
+     *
+     * @param  sb      where to write the result of all actions.
+     * @param  filter  the filter for which to execute an action based on its 
type.
+     * @throws UnsupportedOperationException if there is no action registered 
for the given filter.
      */
-
-    protected static CharSequence format(Literal candidate) {
-        Object value = candidate == null ? null : candidate.getValue();
-        if (value == null) return "NULL";
-        else if (value instanceof CharSequence) {
-            final String asStr = value.toString();
-            asStr.replace("'", "''");
-            return "'"+asStr+"'";
-        } else if (value instanceof Number || value instanceof Boolean) {
-            return value.toString();
-        }
-
-        // geometric special cases
-        if (value instanceof GeographicBoundingBox) {
-            value = new GeneralEnvelope((GeographicBoundingBox) value);
-        }
-        if (value instanceof Envelope) {
-            value = asGeometry((Envelope) value);
+    @SuppressWarnings("unchecked")
+    private void evaluateMandatory(final StringBuilder sb, final Filter<? 
super Feature> filter) {
+        final int pos = sb.length();
+        visit((Filter<Feature>) filter, sb);
+        if (sb.length() <= pos) {
+            throw new IllegalArgumentException("Filter evaluate to an empty 
text: " + filter);
         }
-        if (value instanceof Geometry) {
-            return format((Geometry) value);
-        }
-        throw new UnsupportedOperationException("Not supported yet: Literal 
value of type "+value.getClass());
     }
 
     /**
-     * Beware ! This implementation is a naïve one, expecting given property 
name to match exactly SQL database names.
-     * In the future, it would be appreciable to be able to configure a mapper 
between feature and SQL names.
-     * @param candidate The property name to parse.
-     * @return The SQL representation of the given name.
+     * Executes the registered action for the given expression.
+     * Throws an exception if the expression did not wrote anything in the 
buffer.

Review comment:
       s/did not wrote/did not write

##########
File path: core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
##########
@@ -43,21 +49,23 @@
 import org.locationtech.jts.geom.LineString;
 import org.locationtech.jts.geom.LinearRing;
 import org.locationtech.jts.geom.Polygon;
-import org.opengis.filter.And;
+import org.opengis.util.FactoryException;
+import org.opengis.feature.Feature;
+import org.opengis.filter.Expression;
 import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory2;
-import org.opengis.filter.Or;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.PropertyName;
+import org.opengis.filter.FilterFactory;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.LogicalOperatorName;
+import org.opengis.filter.ValueReference;
 
 import static org.apache.sis.internal.cql.CQLParser.*;
 
 
 /**
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
+ * @version 1.1
+ * @since   1.1

Review comment:
       Isn't the `@since` tag showing when the file was introduced? Shouldn't 
it be left as `1.0`? Then any method added can receive a `@since 1.1`.
   
   We used to have `@version` tags in Apache Commons too, first with SVN 
commits (done automatically by subsversion) but during the move to Git we 
decided to drop it, since it was hard to define the version when you had 
multiple changes in a short time, in-between releases. So we ended up leaving 
users to decide the version of the file based on git. Probably doesn't apply to 
Sis.

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
##########
@@ -572,28 +616,33 @@ private static Number number(final boolean result) {
         @Override protected boolean compare      (ChronoLocalDate        left, 
ChronoLocalDate        right) {return !left.isAfter(right);}
         @Override protected boolean compare      (ChronoLocalDateTime<?> left, 
ChronoLocalDateTime<?> right) {return !left.isAfter(right);}
         @Override protected boolean compare      (ChronoZonedDateTime<?> left, 
ChronoZonedDateTime<?> right) {return !left.isAfter(right);}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object 
extraData) {
-            return visitor.visit(this, extraData);
-        }
     }
 
 
     /**
-     * The {@value #NAME} {@literal (>)} filter.
+     * The {@code "PropertyIsGreaterThan"} {@literal (>)} filter.
      */
-    static final class GreaterThan extends ComparisonFunction implements 
org.opengis.filter.PropertyIsGreaterThan {
+    static final class GreaterThan<R> extends ComparisonFunction<R> {
         /** For cross-version compatibility during (de)serialization. */
         private static final long serialVersionUID = 8605517892232632586L;
 
-        /** Creates a new filter for the {@value #NAME} operation. */
-        GreaterThan(Expression expression1, Expression expression2, boolean 
isMatchingCase, MatchAction matchAction) {
+        /** Creates a new filter. */
+        GreaterThan(final Expression<? super R, ?> expression1,
+                    final Expression<? super R, ?> expression2,
+                    boolean isMatchingCase, MatchAction matchAction)
+        {
             super(expression1, expression2, isMatchingCase, matchAction);
         }
 
-        /** Identification of this operation. */
-        @Override public String getName() {return NAME;}
+        /** Creates a new filter of the same type but different parameters. */
+        @Override public Filter<R> recreate(final Expression<? super R, ?>[] 
effective) {
+            return new GreaterThan<>(effective[0], effective[1], 
isMatchingCase, matchAction);
+        }
+
+        /** Identification of the this operation. */

Review comment:
       Ditto here, the or this?

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
##########
@@ -95,13 +103,34 @@
      * @param  isMatchingCase  specifies whether comparisons are case 
sensitive.
      * @param  matchAction     specifies how the comparisons shall be 
evaluated for a collection of values.
      */
-    ComparisonFunction(final Expression expression1, final Expression 
expression2, final boolean isMatchingCase, final MatchAction matchAction) {
+    ComparisonFunction(final Expression<? super R, ?> expression1,
+                       final Expression<? super R, ?> expression2,
+                       final boolean isMatchingCase, final MatchAction 
matchAction)
+    {
         super(expression1, expression2);
         this.isMatchingCase = isMatchingCase;
         this.matchAction = matchAction;
         ArgumentChecks.ensureNonNull("matchAction", matchAction);
     }
 
+    /**
+     * Returns the element on the left side of the comparison expression.
+     * This is the element at index 0 in the {@linkplain #getExpressions() 
list of expressions}.
+     */
+    @Override
+    public final Expression<? super R, ?> getOperand1() {
+        return expression1;
+    }
+
+    /**
+     * Returns the element on the left side of the comparison expression.
+     * This is the element at index 0 in the {@linkplain #getExpressions() 
list of expressions}.

Review comment:
       Should it be the element on the right side, and at index 1? Just asking 
because the description looks the same as the one from the method above this 
one.

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
##########
@@ -16,788 +16,976 @@
  */
 package org.apache.sis.filter;
 
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.HashMap;
+import java.util.Collection;
 import java.util.ServiceLoader;
-import java.util.Set;
-import org.opengis.filter.*;
-import org.opengis.filter.capability.*;
-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.Literal;
-import org.opengis.filter.expression.Multiply;
-import org.opengis.filter.expression.PropertyName;
-import org.opengis.filter.expression.Subtract;
-import org.opengis.filter.identity.FeatureId;
-import org.opengis.filter.identity.GmlObjectId;
-import org.opengis.filter.identity.Identifier;
-import org.opengis.filter.sort.SortBy;
-import org.opengis.filter.sort.SortOrder;
-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;
-import org.opengis.filter.temporal.*;
+import java.time.Instant;
+import javax.measure.Quantity;
+import javax.measure.quantity.Length;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.Geometry;
-import org.opengis.referencing.NoSuchAuthorityCodeException;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.util.FactoryException;
-import org.opengis.util.GenericName;
-import org.apache.sis.internal.system.Modules;
-import org.apache.sis.internal.system.SystemListener;
-import org.apache.sis.internal.feature.FunctionRegister;
+import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.internal.filter.sqlmm.SQLMM;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.util.collection.BackingStoreException;
-import org.apache.sis.geometry.ImmutableEnvelope;
+import org.apache.sis.internal.filter.sqlmm.Registry;
+import org.apache.sis.internal.filter.FunctionRegister;
+import org.apache.sis.geometry.WraparoundMethod;
+import org.apache.sis.util.iso.AbstractFactory;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.*;
+import org.opengis.feature.Feature;
+import org.opengis.filter.capability.FilterCapabilities;
 
 
 /**
- * Default implementation of GeoAPI filter factory for creation of {@link 
Filter} and {@link Expression} instances.
- *
- * <div class="warning"><b>Warning:</b> most methods in this class are still 
unimplemented.
- * This is a very early draft subject to changes.
- * <b>TODO: the API of this class needs severe revision! DO NOT RELEASE.</b>
- * See <a href="https://github.com/opengeospatial/geoapi/issues/32";>GeoAPI 
issue #32</a>.</div>
+ * A factory of default {@link Filter} and {@link Expression} implementations.
+ * This base class operates on resources of arbitrary type {@code <R>}.
+ * Concrete subclass operates on resources of specific type such as {@link 
org.opengis.feature.Feature}.
  *
  * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
- * @since   1.1
+ *
+ * @param  <R>  the type of resources (e.g. {@link 
org.opengis.feature.Feature}) to use as inputs.
+ * @param  <G>  base class of geometry objects. The implementation-neutral 
type is GeoAPI {@link Geometry},
+ *              but this factory allows the use of other implementations such 
as JTS
+ *              {@link org.locationtech.jts.geom.Geometry} or ESRI {@link 
com.esri.core.geometry.Geometry}.
+ * @param  <T>  base class of temporal objects.
+ *
+ * @since 1.1
  * @module
  */
-public class DefaultFilterFactory implements FilterFactory2 {
+public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory 
implements FilterFactory<R,G,T> {
     /**
-     * All functions identified by a name like {@code "cos"}, {@code "hypot"}, 
<i>etc</i>.
-     * The actual function creations is delegated to an external factory such 
as {@link SQLMM}.
-     * The factories are fetched by {@link #function(String, Expression...)} 
when first needed.
-     * This factory is cleared if classpath changes, for allowing dynamic 
reloading.
-     *
-     * @see #function(String, Expression...)
+     * The geometry library used by this factory.
      */
-    private static final Map<String,FunctionRegister> FUNCTION_REGISTERS = new 
HashMap<>();
-    static {
-        SystemListener.add(new SystemListener(Modules.FEATURE) {
-            @Override protected void classpathChanged() {
-                synchronized (FUNCTION_REGISTERS) {
-                    FUNCTION_REGISTERS.clear();
-                }
-            }
-        });
-    }
+    private final Geometries<G> library;
 
     /**
-     * According to OGC Filter encoding v2.0, comparison operators should 
default to case sensitive comparison.
-     * We use this constant to model it, so it will be easier to change 
default value if the standard evolves.
-     * Documentation reference: OGC 09-026r1 and ISO 19143:2010(E), section 
7.7.3.2.
+     * The strategy to use for representing a region crossing the 
anti-meridian.
      */
-    private static final boolean DEFAULT_MATCH_CASE = true;
+    private final WraparoundMethod wraparound;
 
     /**
-     * Creates a new factory.
+     * All functions identified by a name like {@code "cos"}, {@code "hypot"}, 
<i>etc</i>.
+     * The actual function creations is delegated to an external factory such 
as SQLMM registry.

Review comment:
       s/creations/creation

##########
File path: 
storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
##########
@@ -16,506 +16,330 @@
  */
 package org.apache.sis.internal.sql.feature;
 
-import java.util.Arrays;
 import java.util.List;
-import java.util.Optional;
-import java.util.function.BooleanSupplier;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+import java.util.Collections;
+import java.util.function.BiConsumer;
+import org.opengis.util.LocalName;
+import org.opengis.util.GenericName;
+import org.opengis.util.NameSpace;
+import org.opengis.util.NameFactory;
+import org.opengis.geometry.Envelope;
+import org.opengis.geometry.Geometry;
+import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.WraparoundMethod;
 import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.internal.feature.GeometryWrapper;
+import org.apache.sis.internal.filter.FunctionNames;
+import org.apache.sis.internal.filter.Visitor;
+import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.setup.GeometryLibrary;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.iso.Names;
-import org.opengis.filter.*;
-import org.opengis.filter.expression.Add;
-import org.opengis.filter.expression.BinaryExpression;
-import org.opengis.filter.expression.Divide;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.ExpressionVisitor;
-import org.opengis.filter.expression.Function;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.expression.Multiply;
-import org.opengis.filter.expression.NilExpression;
-import org.opengis.filter.expression.PropertyName;
-import org.opengis.filter.expression.Subtract;
-import org.opengis.filter.spatial.BBOX;
-import org.opengis.filter.spatial.Beyond;
-import org.opengis.filter.spatial.BinarySpatialOperator;
-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;
-import org.opengis.filter.temporal.*;
-import org.opengis.geometry.Envelope;
-import org.opengis.geometry.Geometry;
-import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.util.GenericName;
-import org.opengis.util.LocalName;
+
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+import org.opengis.filter.ValueReference;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.LogicalOperatorName;
+import org.opengis.filter.ComparisonOperator;
+import org.opengis.filter.ComparisonOperatorName;
+import org.opengis.filter.BinaryComparisonOperator;
+import org.opengis.filter.SpatialOperator;
+import org.opengis.filter.SpatialOperatorName;
+import org.opengis.filter.BetweenComparisonOperator;
+import org.opengis.filter.LikeOperator;
+
 
 /**
- * Port of Geotk FilterToSQL for an ANSI compliant query builder.
+ * Writes SQL statement for a filter or an expression.
+ * This base class is restricted to ANSI compliant SQL.
  *
  * @implNote For now, we over-use parenthesis to ensure consistent operator 
priority. In the future, we could evolve
  * this component to provide more elegant transcription of filter groups.
  *
  * No case insensitive support of binary comparison is done.
  *
- * TODO: define a set of accepter property names (even better: link to {@link 
FeatureAdapter}), so any {@link PropertyName}
+ * TODO: define a set of accepter property names (even better: link to {@link 
FeatureAdapter}), so any {@link ValueReference}
  * filter refering to non pure SQL property (like relations) will cause a 
failure.
  *
- * @author Alexis Manin (Geomatys)
- * @version 2.0
- * @since   2.0
+ * @author  Alexis Manin (Geomatys)
+ * @version 1.1
+ * @since   1.1
  * @module
  */
-public class ANSIInterpreter implements FilterVisitor, ExpressionVisitor {
+class ANSIInterpreter extends Visitor<Feature,StringBuilder> {
     /**
      * TODO
      */
     private static final GeometryLibrary LIBRARY = null;
 
-    private final java.util.function.Function<Literal, CharSequence> 
valueFormatter;
+    private final NameFactory nameFactory;
 
-    private final java.util.function.Function<PropertyName, CharSequence> 
nameFormatter;
+    private final NameSpace scope;
 
     public ANSIInterpreter() {
-        this(ANSIInterpreter::format, ANSIInterpreter::format);
-    }
-
-    public ANSIInterpreter(
-            java.util.function.Function<Literal, CharSequence> valueFormatter,
-            java.util.function.Function<PropertyName, CharSequence> 
nameFormatter)
-    {
-        ArgumentChecks.ensureNonNull("valueFormatter", valueFormatter);
-        ArgumentChecks.ensureNonNull("nameFormatter", nameFormatter);
-        this.valueFormatter = valueFormatter;
-        this.nameFormatter = nameFormatter;
-    }
-
-    @Override
-    public CharSequence visitNullFilter(Object extraData) {
-        throw new UnsupportedOperationException("Null filter is not 
supported.");
-    }
-
-    @Override
-    public Object visit(ExcludeFilter filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(IncludeFilter filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public CharSequence visit(And filter, Object extraData) {
-        return join(filter, " AND ", extraData);
-    }
-
-    @Override
-    public Object visit(Id filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(Not filter, Object extraData) {
-        final CharSequence innerFilter = evaluateMandatory(filter.getFilter(), 
extraData);
-        return "NOT (" + innerFilter + ")";
-    }
-
-    @Override
-    public Object visit(Or filter, Object extraData) {
-        return join(filter, " OR ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsBetween filter, Object extraData) {
-        final CharSequence propertyExp = 
evaluateMandatory(filter.getExpression(), extraData);
-        final CharSequence lowerExp = 
evaluateMandatory(filter.getLowerBoundary(), extraData);
-        final CharSequence upperExp = 
evaluateMandatory(filter.getUpperBoundary(), extraData);
-
-        return new StringBuilder(propertyExp)
-                .append(" BETWEEN ")
-                .append(lowerExp)
-                .append(" AND ")
-                .append(upperExp);
-    }
-
-    @Override
-    public Object visit(PropertyIsEqualTo filter, Object extraData) {
-        return joinMatchCase(filter, " = ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsNotEqualTo filter, Object extraData) {
-        return joinMatchCase(filter, " <> ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsGreaterThan filter, Object extraData) {
-        return joinMatchCase(filter, " > ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object 
extraData) {
-        return joinMatchCase(filter, " >= ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsLessThan filter, Object extraData) {
-        return joinMatchCase(filter, " < ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsLessThanOrEqualTo filter, Object extraData) {
-        return joinMatchCase(filter, " <= ", extraData);
-    }
-
-    @Override
-    public Object visit(PropertyIsLike filter, Object extraData) {
-        // TODO: PostgreSQL extension : ilike
-        ensureMatchCase(filter::isMatchingCase);
-        // TODO: port Geotk
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(PropertyIsNull filter, Object extraData) {
-        return evaluateMandatory(filter.getExpression(), extraData) + " IS 
NULL";
-    }
-
-    @Override
-    public Object visit(PropertyIsNil filter, Object extraData) {
-        return evaluateMandatory(filter.getExpression(), extraData) + " IS 
NULL";
+        nameFactory = DefaultFactories.forBuildin(NameFactory.class);
+        scope = nameFactory.createNameSpace(nameFactory.createLocalName(null, 
"xpath"),
+                                            
Collections.singletonMap("separator", "/"));
+
+        setFilterHandler(LogicalOperatorName.AND, new BinaryLogicJoin(" AND 
"));
+        setFilterHandler(LogicalOperatorName.OR,  new BinaryLogicJoin(" OR "));
+        setFilterHandler(LogicalOperatorName.NOT, (f,sb) -> {
+            final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) 
f;
+            evaluateMandatory(sb.append("NOT ("), filter.getOperands().get(0));
+            sb.append(')');
+        });
+        
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN),
 (f,sb) -> {
+            final BetweenComparisonOperator<Feature>  filter = 
(BetweenComparisonOperator<Feature>) f;
+            evaluateMandatory(sb,                     filter.getExpression());
+            evaluateMandatory(sb.append(" BETWEEN "), 
filter.getLowerBoundary());
+            evaluateMandatory(sb.append(" AND "),     
filter.getUpperBoundary());
+        });
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO,          
       new JoinMatchCase(" = "));
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_NOT_EQUAL_TO,      
       new JoinMatchCase(" <> "));
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN,      
       new JoinMatchCase(" > "));
+        
setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO, 
new JoinMatchCase(" >= "));
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN,         
       new JoinMatchCase(" < "));
+        
setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,    
new JoinMatchCase(" <= "));
+        
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE),
 (f,sb) -> {
+            final LikeOperator<Feature> filter = (LikeOperator<Feature>) f;
+            ensureMatchCase(filter.isMatchingCase());
+            // TODO: port Geotk
+            throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
+        });
+        setNullAndNilHandlers((f,sb) -> {
+            final ComparisonOperator<Feature> filter = 
(ComparisonOperator<Feature>) f;
+            evaluateMandatory(sb, filter.getExpressions().get(0));
+            sb.append(" IS NULL");
+        });
+        /*
+         * SPATIAL FILTERS
+         */
+        setFilterHandler(SpatialOperatorName.BBOX, (f,sb) -> {
+            final SpatialOperator<Feature> filter = (SpatialOperator<Feature>) 
f;
+            // TODO: This is a wrong interpretation, but sqlmm has no 
equivalent of filter encoding bbox, so we'll
+            // fallback on a standard intersection. However, PostGIS, H2, etc. 
have their own versions of such filters.
+            for (final Expression<? super Feature, ?> e : 
filter.getExpressions()) {
+                if (e == null) {
+                    throw new UnsupportedOperationException("Not supported 
yet: bbox over all geometric properties");
+                }
+            }
+            bbox(sb, filter);
+        });
+        setFilterHandler(SpatialOperatorName.CONTAINS,   new 
Function(FunctionNames.ST_Contains));
+        setFilterHandler(SpatialOperatorName.CROSSES,    new 
Function(FunctionNames.ST_Crosses));
+        setFilterHandler(SpatialOperatorName.DISJOINT,   new 
Function(FunctionNames.ST_Disjoint));
+        setFilterHandler(SpatialOperatorName.EQUALS,     new 
Function(FunctionNames.ST_Equals));
+        setFilterHandler(SpatialOperatorName.INTERSECTS, new 
Function(FunctionNames.ST_Intersects));
+        setFilterHandler(SpatialOperatorName.OVERLAPS,   new 
Function(FunctionNames.ST_Overlaps));
+        setFilterHandler(SpatialOperatorName.TOUCHES,    new 
Function(FunctionNames.ST_Touches));
+        setFilterHandler(SpatialOperatorName.WITHIN,     new 
Function(FunctionNames.ST_Within));
+        /*
+         * Expression visitor
+         */
+        setExpressionHandler(FunctionNames.Add,      new Join(" + "));
+        setExpressionHandler(FunctionNames.Subtract, new Join(" - "));
+        setExpressionHandler(FunctionNames.Divide,   new Join(" / "));
+        setExpressionHandler(FunctionNames.Multiply, new Join(" * "));
+        setExpressionHandler(FunctionNames.Literal, (e,sb) -> writeLiteral(sb, 
(Literal<Feature,?>) e));
+        setExpressionHandler(FunctionNames.ValueReference, (e,sb) -> 
writeColumnName(sb, (ValueReference<Feature,?>) e));
     }
 
-    /*
-     * SPATIAL FILTERS
+    /**
+     * Returns the SQL fragment to use for {@link SpatialOperatorName#BBOX} 
type of filter.
      */
-
-    @Override
-    public Object visit(BBOX filter, Object extraData) {
-        // TODO: This is a wrong interpretation, but sqlmm has no equivalent 
of filter encoding bbox, so we'll
-        // fallback on a standard intersection. However, PostGIS, H2, etc. 
have their own versions of such filters.
-        if (filter.getExpression1() == null || filter.getExpression2() == null)
-            throw new UnsupportedOperationException("Not supported yet : bbox 
over all geometric properties");
-        return function("ST_Intersects", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Beyond filter, Object extraData) {
-        // TODO: ISO SQL specifies that unit of distance could be specified. 
However, PostGIS documentation does not
-        // talk about it. For now, we'll fallback on Java implementation until 
we're sure how to perform native
-        // operation properly.
-        throw new UnsupportedOperationException("Not yet: unit management 
ambiguous");
-    }
-
-    @Override
-    public Object visit(Contains filter, Object extraData) {
-        return function("ST_Contains", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Crosses filter, Object extraData) {
-        return function("ST_Crosses", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Disjoint filter, Object extraData) {
-        return function("ST_Disjoint", filter, extraData);
-    }
-
-    @Override
-    public Object visit(DWithin filter, Object extraData) {
-        // TODO: as for beyond filter above, unit determination is a bit 
complicated.
-        throw new UnsupportedOperationException("Not yet: unit management to 
handle properly");
-    }
-
-    @Override
-    public Object visit(Equals filter, Object extraData) {
-        return function("ST_Equals", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Intersects filter, Object extraData) {
-        return function("ST_Intersects", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Overlaps filter, Object extraData) {
-        return function("ST_Overlaps", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Touches filter, Object extraData) {
-        return function("ST_Touches", filter, extraData);
-    }
-
-    @Override
-    public Object visit(Within filter, Object extraData) {
-        return function("ST_Within", filter, extraData);
+    void bbox(final StringBuilder sb, final SpatialOperator<Feature> filter) {
+        function(sb, "ST_Intersects", filter);
+    }
+
+    private static void writeLiteral(final StringBuilder sb, final 
Literal<Feature,?> literal) {
+        Object value;
+        if (literal == null || (value = literal.getValue()) == null) {
+            sb.append("NULL");
+        } else if (value instanceof CharSequence) {
+            String text = value.toString();
+            text = text.replace("'", "''");
+            sb.append('\'').append(text).append('\'');
+        } else if (value instanceof Number || value instanceof Boolean) {
+            sb.append(value);
+        } else {
+            // Geometric special cases
+            if (value instanceof GeographicBoundingBox) {
+                value = new GeneralEnvelope((GeographicBoundingBox) value);
+            }
+            if (value instanceof Envelope) {
+                value = asGeometry((Envelope) value);
+            }
+            if (value instanceof Geometry) {
+                format(sb, (Geometry) value);
+            } else {
+                throw new UnsupportedOperationException("Not supported yet: 
Literal value of type " + value.getClass());
+            }
+        }
     }
 
-    /*
-     * TEMPORAL OPERATORS
+    /**
+     * Beware ! This implementation is a naïve one, expecting given property 
name to match exactly SQL database names.
+     * In the future, it would be appreciable to be able to configure a mapper 
between feature and SQL names.
+     *
+     * @param  candidate  name of property to insert in SQL statement.
      */
-
-    @Override
-    public Object visit(After filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(AnyInteracts filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(Before filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(Begins filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(BegunBy filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(During filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(EndedBy filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
+    private void writeColumnName(final StringBuilder sb, final 
ValueReference<Feature,?> candidate) {
+        final GenericName name = nameFactory.parseGenericName(scope, 
candidate.getXPath());
+        final List<? extends LocalName> components = name.getParsedNames();
+        final int n = components.size();
+        for (int i=0; i<n; i++) {
+            if (i != 0) sb.append('.');
+            sb.append('"').append(components.get(i)).append('"');
+        }
     }
 
-    @Override
-    public Object visit(Ends filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
+    private final class BinaryLogicJoin implements BiConsumer<Filter<Feature>, 
StringBuilder> {
+        private final String operator;
 
-    @Override
-    public Object visit(Meets filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(MetBy filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
-
-    @Override
-    public Object visit(OverlappedBy filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
+        BinaryLogicJoin(final String operator) {
+            this.operator = operator;
+        }
 
-    @Override
-    public Object visit(TContains filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
+        @Override
+        public void accept(final Filter<Feature> f, final StringBuilder sb) {
+            final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) 
f;
+            final List<Filter<? super Feature>> subFilters = 
filter.getOperands();
+            final int n = subFilters.size();
+            if (n != 0) {
+                sb.append('(');
+                for (int i=0; i<n; i++) {
+                    if (i != 0) sb.append(operator);
+                    evaluateMandatory(sb, subFilters.get(i));
+                }
+                sb.append(')');
+            }
+        }
     }
 
-    @Override
-    public Object visit(TEquals filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
+    private void join(final StringBuilder sb,
+                      final List<Expression<? super Feature, ?>> expressions,
+                      final int maxCount, final String operator)
+    {
+        sb.append('(');
+        final int n = Math.min(expressions.size(), maxCount);
+        for (int i=0; i<n; i++) {
+            if (i != 0) sb.append(operator);
+            evaluateMandatory(sb, expressions.get(i));
+        }
+        sb.append(')');
     }
 
-    @Override
-    public Object visit(TOverlaps filter, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 30/09/2019
-    }
+    private final class JoinMatchCase implements BiConsumer<Filter<Feature>, 
StringBuilder> {
+        private final String operator;
 
-    /*
-     * Expression visitor
-     */
+        JoinMatchCase(final String operator) {
+            this.operator = operator;
+        }
 
-    @Override
-    public Object visit(NilExpression expression, Object extraData) {
-        return "NULL";
+        @Override
+        public void accept(final Filter<Feature> f, final StringBuilder sb) {
+            final BinaryComparisonOperator<Feature> filter = 
(BinaryComparisonOperator<Feature>) f;
+            ensureMatchCase(filter.isMatchingCase());
+            join(sb, filter.getExpressions(), 2, operator);
+        }
     }
 
-    @Override
-    public Object visit(Add expression, Object extraData) {
-        return join(expression, " + ", extraData);
+    protected final void join(final StringBuilder sb, final 
SpatialOperator<Feature> op, final String operator) {
+        join(sb, op.getExpressions(), 2, operator);
     }
 
-    @Override
-    public Object visit(Divide expression, Object extraData) {
-        return join(expression, " / ", extraData);
-    }
+    private final class Join implements BiConsumer<Expression<Feature,?>, 
StringBuilder> {
+        private final String operator;
 
-    @Override
-    public Object visit(Function expression, Object extraData) {
-        throw new UnsupportedOperationException("Not supported yet"); // 
"Alexis Manin (Geomatys)" on 01/10/2019
-    }
+        Join(final String operator) {
+            this.operator = operator;
+        }
 
-    @Override
-    public Object visit(Literal expression, Object extraData) {
-        return valueFormatter.apply(expression);
+        @Override
+        public void accept(final Expression<Feature,?> expression, final 
StringBuilder sb) {
+            join(sb, expression.getParameters(), 2, operator);
+        }
     }
 
-    @Override
-    public Object visit(Multiply expression, Object extraData) {
-        return join(expression, " * ", extraData);
-    }
+    private final class Function implements BiConsumer<Filter<Feature>, 
StringBuilder> {
+        private final String name;
 
+        Function(final String name) {
+            this.name = name;
+        }
 
-    @Override
-    public Object visit(PropertyName expression, Object extraData) {
-        return nameFormatter.apply(expression);
+        @Override
+        public void accept(final Filter<Feature> f, final StringBuilder sb) {
+            function(sb, name, (SpatialOperator<Feature>) f);
+        }
     }
 
-    @Override
-    public Object visit(Subtract expression, Object extraData) {
-        return join(expression, " - ", extraData);
+    private void function(final StringBuilder sb, final String name, final 
SpatialOperator<Feature> filter) {
+        join(sb.append(name), filter.getExpressions(), Integer.MAX_VALUE, ", 
");
     }
 
-    /*
-     * UTILITIES
+    /**
+     * Executes the registered action for the given filter.
+     * Throws an exception if the filter did not wrote anything in the buffer.

Review comment:
       s/did not wrote/did not write

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
##########
@@ -0,0 +1,297 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter;
+
+import java.util.Map;
+import java.util.List;
+import java.util.IdentityHashMap;
+import java.util.ConcurrentModificationException;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+
+
+/**
+ * Description of optimizations or simplifications to attempt on filters and 
expressions.
+ * Optimizations can include the following changes:
+ *
+ * <ul>
+ *   <li>Application of some logical identities such as {@code NOT(NOT(A)) == 
A}.</li>
+ *   <li>Application of logical short circuits such as {@code A & FALSE == 
FALSE}.</li>
+ *   <li>Immediate evaluation of expressions where all parameters are literal 
values.</li>
+ * </ul>
+ *
+ * Current version does not yet provide configuration options.
+ * But this class is the place where such options may be added in the future.
+ *
+ * <p>This class is <strong>not</strong> thread-safe. A new instance shall be 
created
+ * for each thread applying optimizations. Example:</p>
+ *
+ * {@preformat java
+ *     Filter<R> filter = ...;
+ *     filter = new Optimization().apply(filter);
+ * }
+ *
+ * <h2>How optimizations are applied</h2>
+ * Optimizations are specific to each expression and filter type.
+ * For optimizations to happen, classes must implement the {@link 
OnExpression} or {@link OnFilter} interface.
+ * The {@link #apply(Filter)} and {@link #apply(Expression)} methods in this 
{@code Optimization} class merely
+ * delegate to the methods defined in above-cited interfaces, with safety 
guards against infinite recursivity.
+ *
+ * <h2>Behavioral changes</h2>
+ * Optimized filters shall produce the same results than non-optimized filters.
+ * However side-effects may differ, in particular regarding exceptions that 
may be thrown.
+ * For example if a filter tests {@code A & B} and if {@code Optimization} 
determines that the {@code B}
+ * condition will always evaluate to {@code false}, then the {@code A} 
condition will never be tested.
+ * If that condition had side-effects or threw an exception,
+ * those effects will disappear in the optimized filter.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class Optimization {
+    /**
+     * An arbitrary object meaning that a filter or expression optimization in 
under progress.

Review comment:
       /in under progress/is under progress

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
##########
@@ -0,0 +1,297 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter;
+
+import java.util.Map;
+import java.util.List;
+import java.util.IdentityHashMap;
+import java.util.ConcurrentModificationException;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+
+
+/**
+ * Description of optimizations or simplifications to attempt on filters and 
expressions.
+ * Optimizations can include the following changes:
+ *
+ * <ul>
+ *   <li>Application of some logical identities such as {@code NOT(NOT(A)) == 
A}.</li>
+ *   <li>Application of logical short circuits such as {@code A & FALSE == 
FALSE}.</li>
+ *   <li>Immediate evaluation of expressions where all parameters are literal 
values.</li>
+ * </ul>
+ *
+ * Current version does not yet provide configuration options.
+ * But this class is the place where such options may be added in the future.
+ *
+ * <p>This class is <strong>not</strong> thread-safe. A new instance shall be 
created
+ * for each thread applying optimizations. Example:</p>
+ *
+ * {@preformat java
+ *     Filter<R> filter = ...;
+ *     filter = new Optimization().apply(filter);
+ * }
+ *
+ * <h2>How optimizations are applied</h2>
+ * Optimizations are specific to each expression and filter type.
+ * For optimizations to happen, classes must implement the {@link 
OnExpression} or {@link OnFilter} interface.
+ * The {@link #apply(Filter)} and {@link #apply(Expression)} methods in this 
{@code Optimization} class merely
+ * delegate to the methods defined in above-cited interfaces, with safety 
guards against infinite recursivity.
+ *
+ * <h2>Behavioral changes</h2>
+ * Optimized filters shall produce the same results than non-optimized filters.
+ * However side-effects may differ, in particular regarding exceptions that 
may be thrown.
+ * For example if a filter tests {@code A & B} and if {@code Optimization} 
determines that the {@code B}
+ * condition will always evaluate to {@code false}, then the {@code A} 
condition will never be tested.
+ * If that condition had side-effects or threw an exception,
+ * those effects will disappear in the optimized filter.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class Optimization {
+    /**
+     * An arbitrary object meaning that a filter or expression optimization in 
under progress.
+     */
+    private static final Object COMPUTING = Void.TYPE;
+
+    /**
+     * Filters and expressions already optimized. Also used for avoiding 
never-ending loops.
+     * The map is created when first needed.
+     *
+     * <div class="note"><b>Note:</b> the same map is used for filters and 
expressions.
+     * It is not a problem if keys do not implement the two interfaces in same 
time.
+     * If it happens anyway, it should still be okay because the method 
signatures are
+     * the same in both interfaces (only the return type changes), so the same 
methods
+     * would be invoked no matter if we consider the keys as a filter or an 
expression.</div>
+     */
+    private Map<Object,Object> done;
+
+    /**
+     * Creates a new instance.
+     */
+    public Optimization() {
+    }
+
+    /**
+     * Optimizes or simplifies the given filter. If the given instance 
implements the {@link OnFilter} interface,
+     * then its {@code optimize(this)} method is invoked. Otherwise this 
method returns the given filter as-is.
+     *
+     * @param  <R>     the type of resources (e.g. {@code Feature}) used as 
inputs.
+     * @param  filter  the filter to optimize, or {@code null}.
+     * @return the optimized filter, or {@code null} if the given filter was 
null.
+     *         May be {@code filter} if no optimization or simplification has 
been applied.
+     * @throws IllegalArgumentException if the given filter is already in 
process of being optimized
+     *         (i.e. there is a recursive call to {@code apply(…)} for the 
same filter).
+     */
+    public <R> Filter<? super R> apply(final Filter<R> filter) {
+        if (!(filter instanceof OnFilter<?>)) {
+            return filter;
+        }
+        final boolean isFirstCall = (done == null);
+        if (isFirstCall) {
+            done = new IdentityHashMap<>();
+        }
+        try {
+            final Object previous = done.putIfAbsent(filter, COMPUTING);
+            if (previous == COMPUTING) {
+                throw new 
IllegalArgumentException(Errors.format(Errors.Keys.RecursiveCreateCallForKey_1, 
filter.getOperatorType()));
+            }
+            @SuppressWarnings("unchecked")
+            Filter<? super R> result = (Filter<? super R>) previous;
+            if (result == null) {
+                result = ((OnFilter<R>) filter).optimize(this);
+                if (done.put(filter, result) != COMPUTING) {
+                    // Should not happen unless this `Optimization` is used 
concurrently in many threads.
+                    throw new ConcurrentModificationException();
+                }
+            }
+            return result;
+        } finally {
+            if (isFirstCall) {
+                done = null;
+            }
+        }
+    }
+
+    /**
+     * Filter than can be optimized. Each filter implementation knows which 
rules can be applied.
+     * For that reason, the optimization algorithms are keep in each 
implementation class.

Review comment:
       a/are keep/are kept

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
##########
@@ -0,0 +1,297 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter;
+
+import java.util.Map;
+import java.util.List;
+import java.util.IdentityHashMap;
+import java.util.ConcurrentModificationException;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+
+
+/**
+ * Description of optimizations or simplifications to attempt on filters and 
expressions.
+ * Optimizations can include the following changes:
+ *
+ * <ul>
+ *   <li>Application of some logical identities such as {@code NOT(NOT(A)) == 
A}.</li>
+ *   <li>Application of logical short circuits such as {@code A & FALSE == 
FALSE}.</li>
+ *   <li>Immediate evaluation of expressions where all parameters are literal 
values.</li>
+ * </ul>
+ *
+ * Current version does not yet provide configuration options.
+ * But this class is the place where such options may be added in the future.
+ *
+ * <p>This class is <strong>not</strong> thread-safe. A new instance shall be 
created
+ * for each thread applying optimizations. Example:</p>
+ *
+ * {@preformat java
+ *     Filter<R> filter = ...;
+ *     filter = new Optimization().apply(filter);
+ * }
+ *
+ * <h2>How optimizations are applied</h2>
+ * Optimizations are specific to each expression and filter type.
+ * For optimizations to happen, classes must implement the {@link 
OnExpression} or {@link OnFilter} interface.
+ * The {@link #apply(Filter)} and {@link #apply(Expression)} methods in this 
{@code Optimization} class merely
+ * delegate to the methods defined in above-cited interfaces, with safety 
guards against infinite recursivity.
+ *
+ * <h2>Behavioral changes</h2>
+ * Optimized filters shall produce the same results than non-optimized filters.
+ * However side-effects may differ, in particular regarding exceptions that 
may be thrown.
+ * For example if a filter tests {@code A & B} and if {@code Optimization} 
determines that the {@code B}
+ * condition will always evaluate to {@code false}, then the {@code A} 
condition will never be tested.
+ * If that condition had side-effects or threw an exception,
+ * those effects will disappear in the optimized filter.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class Optimization {
+    /**
+     * An arbitrary object meaning that a filter or expression optimization in 
under progress.
+     */
+    private static final Object COMPUTING = Void.TYPE;
+
+    /**
+     * Filters and expressions already optimized. Also used for avoiding 
never-ending loops.
+     * The map is created when first needed.
+     *
+     * <div class="note"><b>Note:</b> the same map is used for filters and 
expressions.
+     * It is not a problem if keys do not implement the two interfaces in same 
time.

Review comment:
       s/in same time/at the same time

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java
##########
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter;
+
+import java.util.List;
+import java.util.Arrays;
+import java.util.Collection;
+import javax.measure.Quantity;
+import javax.measure.quantity.Length;
+import org.opengis.geometry.Geometry;
+import org.apache.sis.internal.feature.Geometries;
+import org.apache.sis.internal.feature.GeometryWrapper;
+import org.apache.sis.internal.feature.SpatialOperationContext;
+import org.apache.sis.util.ArgumentChecks;
+
+// Branch-dependent imports
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+import org.opengis.filter.DistanceOperator;
+import org.opengis.filter.DistanceOperatorName;
+
+
+/**
+ * Spatial operations between two geometries and using a distance.
+ * The nature of the operation depends on the subclass.
+ *
+ * <div class="note"><b>Note:</b>
+ * this class has 3 parameters, but the third one is not an expression.
+ * It still a "binary" operator if we count only the expressions.</div>

Review comment:
       s/It still/It is still

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
##########
@@ -0,0 +1,297 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter;
+
+import java.util.Map;
+import java.util.List;
+import java.util.IdentityHashMap;
+import java.util.ConcurrentModificationException;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+
+
+/**
+ * Description of optimizations or simplifications to attempt on filters and 
expressions.
+ * Optimizations can include the following changes:
+ *
+ * <ul>
+ *   <li>Application of some logical identities such as {@code NOT(NOT(A)) == 
A}.</li>
+ *   <li>Application of logical short circuits such as {@code A & FALSE == 
FALSE}.</li>
+ *   <li>Immediate evaluation of expressions where all parameters are literal 
values.</li>
+ * </ul>
+ *
+ * Current version does not yet provide configuration options.
+ * But this class is the place where such options may be added in the future.
+ *
+ * <p>This class is <strong>not</strong> thread-safe. A new instance shall be 
created
+ * for each thread applying optimizations. Example:</p>
+ *
+ * {@preformat java
+ *     Filter<R> filter = ...;
+ *     filter = new Optimization().apply(filter);
+ * }
+ *
+ * <h2>How optimizations are applied</h2>
+ * Optimizations are specific to each expression and filter type.
+ * For optimizations to happen, classes must implement the {@link 
OnExpression} or {@link OnFilter} interface.
+ * The {@link #apply(Filter)} and {@link #apply(Expression)} methods in this 
{@code Optimization} class merely
+ * delegate to the methods defined in above-cited interfaces, with safety 
guards against infinite recursivity.
+ *
+ * <h2>Behavioral changes</h2>
+ * Optimized filters shall produce the same results than non-optimized filters.
+ * However side-effects may differ, in particular regarding exceptions that 
may be thrown.
+ * For example if a filter tests {@code A & B} and if {@code Optimization} 
determines that the {@code B}
+ * condition will always evaluate to {@code false}, then the {@code A} 
condition will never be tested.
+ * If that condition had side-effects or threw an exception,
+ * those effects will disappear in the optimized filter.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class Optimization {
+    /**
+     * An arbitrary object meaning that a filter or expression optimization in 
under progress.
+     */
+    private static final Object COMPUTING = Void.TYPE;
+
+    /**
+     * Filters and expressions already optimized. Also used for avoiding 
never-ending loops.
+     * The map is created when first needed.
+     *
+     * <div class="note"><b>Note:</b> the same map is used for filters and 
expressions.
+     * It is not a problem if keys do not implement the two interfaces in same 
time.
+     * If it happens anyway, it should still be okay because the method 
signatures are
+     * the same in both interfaces (only the return type changes), so the same 
methods
+     * would be invoked no matter if we consider the keys as a filter or an 
expression.</div>
+     */
+    private Map<Object,Object> done;
+
+    /**
+     * Creates a new instance.
+     */
+    public Optimization() {
+    }
+
+    /**
+     * Optimizes or simplifies the given filter. If the given instance 
implements the {@link OnFilter} interface,
+     * then its {@code optimize(this)} method is invoked. Otherwise this 
method returns the given filter as-is.
+     *
+     * @param  <R>     the type of resources (e.g. {@code Feature}) used as 
inputs.
+     * @param  filter  the filter to optimize, or {@code null}.
+     * @return the optimized filter, or {@code null} if the given filter was 
null.
+     *         May be {@code filter} if no optimization or simplification has 
been applied.
+     * @throws IllegalArgumentException if the given filter is already in 
process of being optimized
+     *         (i.e. there is a recursive call to {@code apply(…)} for the 
same filter).
+     */
+    public <R> Filter<? super R> apply(final Filter<R> filter) {
+        if (!(filter instanceof OnFilter<?>)) {
+            return filter;
+        }
+        final boolean isFirstCall = (done == null);
+        if (isFirstCall) {
+            done = new IdentityHashMap<>();
+        }
+        try {
+            final Object previous = done.putIfAbsent(filter, COMPUTING);
+            if (previous == COMPUTING) {
+                throw new 
IllegalArgumentException(Errors.format(Errors.Keys.RecursiveCreateCallForKey_1, 
filter.getOperatorType()));
+            }
+            @SuppressWarnings("unchecked")
+            Filter<? super R> result = (Filter<? super R>) previous;
+            if (result == null) {
+                result = ((OnFilter<R>) filter).optimize(this);
+                if (done.put(filter, result) != COMPUTING) {
+                    // Should not happen unless this `Optimization` is used 
concurrently in many threads.
+                    throw new ConcurrentModificationException();
+                }
+            }
+            return result;
+        } finally {
+            if (isFirstCall) {
+                done = null;
+            }
+        }
+    }
+
+    /**
+     * Filter than can be optimized. Each filter implementation knows which 
rules can be applied.

Review comment:
       s/than can be/that can be

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
##########
@@ -248,13 +159,38 @@ public PropertyTypeBuilder expectedType(FeatureType 
ignored, final FeatureTypeBu
          * Invoked when a new attribute type need to be created for the given 
standard type.
          * The given standard type should be a GeoAPI interface, not the 
implementation class.
          */
-        private static <T> AttributeType<T> newType(final Class<T> 
standardType) {
+        private static <R> AttributeType<R> newType(final Class<R> 
standardType) {
             return createType(standardType, Names.createLocalName(null, null, 
"Literal"));
         }
+    }
+
+
+
+

Review comment:
       Unnecessary extra lines? No harm in leaving them though :man_shrugging: 

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
##########
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.filter.sqlmm;
+
+import java.util.EnumMap;
+import java.util.Collection;
+import org.opengis.util.LocalName;
+import org.opengis.util.ScopedName;
+import org.apache.sis.filter.Optimization;
+import org.apache.sis.internal.filter.Node;
+import org.apache.sis.internal.feature.Resources;
+import org.apache.sis.internal.feature.Geometries;
+import org.apache.sis.internal.feature.FeatureExpression;
+import org.apache.sis.feature.builder.FeatureTypeBuilder;
+import org.apache.sis.feature.builder.PropertyTypeBuilder;
+import org.apache.sis.feature.builder.AttributeTypeBuilder;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.iso.Names;
+
+// Branch-dependent imports
+import org.opengis.feature.FeatureType;
+import org.opengis.filter.Expression;
+import org.opengis.filter.InvalidFilterValueException;
+
+
+/**
+ * Base class of SQLMM spatial functions.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @param  <R>  the type of resources (e.g. {@link 
org.opengis.feature.Feature}) used as inputs.
+ *
+ * @since 1.1
+ * @module
+ */
+abstract class SpatialFunction<R> extends Node implements 
FeatureExpression<R,Object>, Optimization.OnExpression<R,Object> {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 6933519274722660893L;
+
+    /**
+     * Scope of all names defined by SQLMM standard.
+     *
+     * @see #createName(SQLMM)
+     */
+    private static final LocalName SCOPE = Names.createLocalName("ISO", null, 
"sqlmm");;
+
+    /**
+     * Identification of the SQLMM operation.
+     */
+    final SQLMM operation;
+
+    /**
+     * All operation names as {@link ScopedName} instances.
+     * Values are {@linkplain #createName(SQLMM) created} when first needed.
+     */
+    private static final EnumMap<SQLMM, ScopedName> NAMES = new 
EnumMap<>(SQLMM.class);
+
+    /**
+     * Creates a new function. This constructor verifies that the number of 
parameters
+     * is between {@link SQLMM#minParamCount} and {@link SQLMM#maxParamCount} 
inclusive,
+     * but does not store the parameters. Parameters shall be stored by 
subclasses.
+     *
+     * @param  operation   identification of the SQLMM operation.
+     * @param  parameters  sub-expressions that will be evaluated to provide 
the parameters to the function.
+     * @throws IllegalArgumentException if the number of parameters is not in 
the expected range.
+     */
+    SpatialFunction(final SQLMM operation, final Expression<? super R, ?>[] 
parameters) {
+        this.operation = operation;
+        final int n = parameters.length;
+        final int minParamCount = operation.minParamCount;
+        final String message;
+        if (n < minParamCount) {
+            if (n == 0) {
+                message = Errors.format(Errors.Keys.EmptyArgument_1, 
"parameters");
+            } else {
+                message = Errors.format(Errors.Keys.TooFewArguments_2, 
minParamCount, n);
+            }
+        } else {
+            final int maxParamCount = operation.maxParamCount;
+            if (n > maxParamCount) {
+                message = Errors.format(Errors.Keys.TooManyArguments_2, 
maxParamCount, n);
+            } else {
+                return;
+            }
+        }
+        throw new IllegalArgumentException(message);
+    }
+
+    /**
+     * Returns a handler for the library of geometric objects used by this 
expression.
+     * This is typically implemented by a call to {@code 
getGeometryLibrary(geometry)}
+     * where {@code geometry} as the first expression returning a geometry 
object.
+     *
+     * @return the geometry library (never {@code null}).
+     *
+     * @see #getGeometryLibrary(Expression)
+     */
+    abstract Geometries<?> getGeometryLibrary();
+
+    /**
+     * Returns the name of the function to be called. This method returns
+     * a scoped name with the {@link SQLMM} function name in the local part.
+     */
+    @Override
+    public final ScopedName getFunctionName() {
+        synchronized (NAMES) {
+            return NAMES.computeIfAbsent(operation, 
SpatialFunction::createName);
+        }
+    }
+
+    /**
+     * Invoked by {@link #getFunctionName()} when a name needs to be created.
+     */
+    private static ScopedName createName(final SQLMM operation) {
+        return Names.createScopedName(SCOPE, null, operation.name());
+    }
+
+    /**
+     * Returns the children of this node, which are the {@linkplain 
#getParameters() parameters list}.
+     * This is used for information purpose only, for example in order to 
build a string representation.
+     *
+     * @return the children of this node.
+     */
+    @Override
+    protected final Collection<?> getChildren() {
+        return getParameters();
+    }
+
+    /**
+     * Returns a Backus-Naur Form (BNF) of this function.
+     *
+     * @todo Fetch parameter names from {@code FilterCapabilities}.
+     *       Or maybe use annotations, which would also be used for 
capabilities implementation.
+     */
+    public String getSyntax() {
+        final int minParamCount = operation.minParamCount;
+        final int maxParamCount = operation.maxParamCount;
+        final StringBuilder sb = new StringBuilder();
+        sb.append(getFunctionName().tip()).append('(');
+        for (int i = 0; i < maxParamCount; i++) {
+            if (i == minParamCount) sb.append('[');
+            if (i != 0) sb.append(", ");
+            sb.append("param").append(i + 1);
+        }
+        if (maxParamCount > minParamCount) {
+            sb.append(']');
+        }
+        return sb.append(')').toString();
+    }
+
+    /**
+     * Returns the kind of objects evaluated by this expression.
+     */
+    @Override
+    public final Class<?> getValueClass() {
+        return operation.getReturnType(getGeometryLibrary());
+    }
+
+    /**
+     * Returns {@code this} if this expression provides values of the 
specified type,
+     * or throws an exception otherwise.
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public final <N> Expression<R,N> toValueType(final Class<N> type) {
+        if (type.isAssignableFrom(getValueClass())) {
+            return (Expression<R,N>) this;
+        } else {
+            throw new 
ClassCastException(Errors.format(Errors.Keys.CanNotConvertValue_2, 
getFunctionName(), type));
+        }
+    }
+
+    /**
+     * Provides the type of values produced by this expression when a feature 
of the given type is evaluated.
+     * There is two cases:

Review comment:
       s/There is two cases/There are two cases

##########
File path: 
core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
##########
@@ -16,788 +16,976 @@
  */
 package org.apache.sis.filter;
 
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.HashMap;
+import java.util.Collection;
 import java.util.ServiceLoader;
-import java.util.Set;
-import org.opengis.filter.*;
-import org.opengis.filter.capability.*;
-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.Literal;
-import org.opengis.filter.expression.Multiply;
-import org.opengis.filter.expression.PropertyName;
-import org.opengis.filter.expression.Subtract;
-import org.opengis.filter.identity.FeatureId;
-import org.opengis.filter.identity.GmlObjectId;
-import org.opengis.filter.identity.Identifier;
-import org.opengis.filter.sort.SortBy;
-import org.opengis.filter.sort.SortOrder;
-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;
-import org.opengis.filter.temporal.*;
+import java.time.Instant;
+import javax.measure.Quantity;
+import javax.measure.quantity.Length;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.Geometry;
-import org.opengis.referencing.NoSuchAuthorityCodeException;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.util.FactoryException;
-import org.opengis.util.GenericName;
-import org.apache.sis.internal.system.Modules;
-import org.apache.sis.internal.system.SystemListener;
-import org.apache.sis.internal.feature.FunctionRegister;
+import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.internal.filter.sqlmm.SQLMM;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.util.collection.BackingStoreException;
-import org.apache.sis.geometry.ImmutableEnvelope;
+import org.apache.sis.internal.filter.sqlmm.Registry;
+import org.apache.sis.internal.filter.FunctionRegister;
+import org.apache.sis.geometry.WraparoundMethod;
+import org.apache.sis.util.iso.AbstractFactory;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.*;
+import org.opengis.feature.Feature;
+import org.opengis.filter.capability.FilterCapabilities;
 
 
 /**
- * Default implementation of GeoAPI filter factory for creation of {@link 
Filter} and {@link Expression} instances.
- *
- * <div class="warning"><b>Warning:</b> most methods in this class are still 
unimplemented.
- * This is a very early draft subject to changes.
- * <b>TODO: the API of this class needs severe revision! DO NOT RELEASE.</b>
- * See <a href="https://github.com/opengeospatial/geoapi/issues/32";>GeoAPI 
issue #32</a>.</div>
+ * A factory of default {@link Filter} and {@link Expression} implementations.
+ * This base class operates on resources of arbitrary type {@code <R>}.
+ * Concrete subclass operates on resources of specific type such as {@link 
org.opengis.feature.Feature}.
  *
  * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
- * @since   1.1
+ *
+ * @param  <R>  the type of resources (e.g. {@link 
org.opengis.feature.Feature}) to use as inputs.
+ * @param  <G>  base class of geometry objects. The implementation-neutral 
type is GeoAPI {@link Geometry},
+ *              but this factory allows the use of other implementations such 
as JTS
+ *              {@link org.locationtech.jts.geom.Geometry} or ESRI {@link 
com.esri.core.geometry.Geometry}.
+ * @param  <T>  base class of temporal objects.
+ *
+ * @since 1.1
  * @module
  */
-public class DefaultFilterFactory implements FilterFactory2 {
+public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory 
implements FilterFactory<R,G,T> {
     /**
-     * All functions identified by a name like {@code "cos"}, {@code "hypot"}, 
<i>etc</i>.
-     * The actual function creations is delegated to an external factory such 
as {@link SQLMM}.
-     * The factories are fetched by {@link #function(String, Expression...)} 
when first needed.
-     * This factory is cleared if classpath changes, for allowing dynamic 
reloading.
-     *
-     * @see #function(String, Expression...)
+     * The geometry library used by this factory.
      */
-    private static final Map<String,FunctionRegister> FUNCTION_REGISTERS = new 
HashMap<>();
-    static {
-        SystemListener.add(new SystemListener(Modules.FEATURE) {
-            @Override protected void classpathChanged() {
-                synchronized (FUNCTION_REGISTERS) {
-                    FUNCTION_REGISTERS.clear();
-                }
-            }
-        });
-    }
+    private final Geometries<G> library;
 
     /**
-     * According to OGC Filter encoding v2.0, comparison operators should 
default to case sensitive comparison.
-     * We use this constant to model it, so it will be easier to change 
default value if the standard evolves.
-     * Documentation reference: OGC 09-026r1 and ISO 19143:2010(E), section 
7.7.3.2.
+     * The strategy to use for representing a region crossing the 
anti-meridian.
      */
-    private static final boolean DEFAULT_MATCH_CASE = true;
+    private final WraparoundMethod wraparound;
 
     /**
-     * Creates a new factory.
+     * All functions identified by a name like {@code "cos"}, {@code "hypot"}, 
<i>etc</i>.
+     * The actual function creations is delegated to an external factory such 
as SQLMM registry.
+     * The factories are fetched by {@link #function(String, Expression...)} 
when first needed.
+     *
+     * @see #function(String, Expression...)
      */
-    public DefaultFilterFactory() {
-    }
-
-    // SPATIAL FILTERS 
/////////////////////////////////////////////////////////
+    private final Map<String,FunctionRegister> availableFunctions;
 
     /**
-     * Creates an operator that evaluates to {@code true} when the bounding 
box of the feature's geometry overlaps
-     * the given bounding box.
+     * Creates a new factory for geometries of temporal objects of the given 
types.
+     * The {@code spatial} argument can be one of the following classes:
      *
-     * @param  propertyName  name of geometry property (for a {@link 
PropertyName} to access a feature's Geometry)
-     * @param  minx          minimum "x" value (for a literal envelope).
-     * @param  miny          minimum "y" value (for a literal envelope).
-     * @param  maxx          maximum "x" value (for a literal envelope).
-     * @param  maxy          maximum "y" value (for a literal envelope).
-     * @param  srs           identifier of the Coordinate Reference System to 
use for a literal envelope.
-     * @return operator that evaluates to {@code true} when the bounding box 
of the feature's geometry overlaps
-     *         the bounding box provided in arguments to this method.
+     * <table class="sis">
+     *   <caption>Authorized spatial class argument values</caption>
+     *   <tr><th>Library</th> <th>Spatial class</th></tr>
+     *   <tr><td>ESRI</td>    <td>{@code 
com.esri.core.geometry.Geometry}</td></tr>
+     *   <tr><td>JTS</td>     <td>{@code 
org.locationtech.jts.geom.Geometry}</td></tr>
+     *   <tr><td>Java2D</td>  <td>{@code java.awt.Shape}</td></tr>
+     *   <tr><td>Default</td> <td>{@code java.lang.Object}</td></tr>
+     * </table>
      *
-     * @see #bbox(Expression, Envelope)
-     */
-    @Override
-    public BBOX bbox(final String propertyName, final double minx,
-            final double miny, final double maxx, final double maxy, final 
String srs)
-    {
-        return bbox(property(propertyName), minx, miny, maxx, maxy, srs);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public BBOX bbox(final Expression e,
-                     final double minx, final double miny,
-                     final double maxx, final double maxy, final String srs)
-    {
-        final CoordinateReferenceSystem crs = decodeCRS(srs);
-        return bbox(e, new ImmutableEnvelope(new double[] {minx, miny},
-                                             new double[] {maxx, maxy}, crs));
-    }
-
-    /**
-     * Tries to decode a full {@link CoordinateReferenceSystem} from given 
text.
-     * First, we try to interpret it as a code, and if it fails, we try to 
read it as a WKT.
+     * <table class="sis">
+     *   <caption>Authorized temporal class argument values</caption>
+     *   <tr><th>Library</th> <th>Temporal class</th></tr>
+     *   <tr><td>Default</td> <td>{@code java.lang.Object}</td></tr>
+     * </table>
      *
-     * @param  srs  the text describing the reference system. If null or 
blank, a null value is returned.
-     * @return possible null value if input text is empty or blank.
-     * @throws BackingStoreException if an error occurs while decoding the 
text.
-     */
-    private static CoordinateReferenceSystem decodeCRS(String srs) {
-        if (srs == null || (srs = srs.trim()).isEmpty()) {
-            return null;
-        }
-        try {
-            return CRS.forCode(srs);
-        } catch (NoSuchAuthorityCodeException e) {
-            try {
-                return CRS.fromWKT(srs);
-            } catch (FactoryException bis) {
-                e.addSuppressed(bis);
+     * @param  spatial     type of spatial objects,  or {@code Object.class} 
for default.
+     * @param  temporal    type of temporal objects, or {@code Object.class} 
for default.
+     * @param  wraparound  the strategy to use for representing a region 
crossing the anti-meridian.
+     */
+    @SuppressWarnings("unchecked")
+    protected DefaultFilterFactory(final Class<G> spatial, final Class<T> 
temporal, final WraparoundMethod wraparound) {
+        ArgumentChecks.ensureNonNull("spatial",    spatial);
+        ArgumentChecks.ensureNonNull("temporal",   temporal);
+        ArgumentChecks.ensureNonNull("wraparound", wraparound);
+        if (spatial == Object.class) {
+            library = (Geometries<G>) 
Geometries.implementation((GeometryLibrary) null);
+        } else {
+            library = (Geometries<G>) Geometries.implementation(spatial);
+            if (library == null || library.rootClass != spatial) {
+                throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, 
"spatial", spatial));
             }
-            throw new BackingStoreException(e);
-        } catch (FactoryException e) {
-            throw new BackingStoreException(e);
         }
+        if (temporal != Object.class) {
+            throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, 
"temporal", temporal));
+        }
+        this.wraparound = wraparound;
+        availableFunctions = new HashMap<>();
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public BBOX bbox(final Expression e, final Envelope bounds) {
-        return new DefaultBBOX(e, literal(bounds));
-    }
-
-    /**
-     * Creates an operator that checks if all of a feature's geometry is more 
distant than the given distance
-     * from the given geometry.
+     * Returns a factory operating on {@link Feature} instances.
+     * The {@linkplain GeometryLibrary geometry library} will be the system 
default.
      *
-     * @param  propertyName  name of geometry property (for a {@link 
PropertyName} to access a feature's Geometry).
-     * @param  geometry      the geometry from which to evaluate the distance.
-     * @param  distance      minimal distance for evaluating the expression as 
{@code true}.
-     * @param  units         units of the given {@code distance}.
-     * @return operator that evaluates to {@code true} when all of a feature's 
geometry is more distant than
-     *         the given distance from the given geometry.
-     */
-    @Override
-    public Beyond beyond(final String propertyName, final Geometry geometry,
-            final double distance, final String units)
-    {
-        final PropertyName name = property(propertyName);
-        final Literal geom = literal(geometry);
-        return beyond(name, geom, distance, units);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Beyond beyond(final Expression left, final Expression right,
-            final double distance, final String units)
-    {
-        return new SpatialFunction.Beyond(left, right, distance, units);
-    }
-
-    /**
-     * {@inheritDoc}
+     * @return factory operating on {@link Feature} instances.
+     *
+     * @todo The type of temporal object is not yet determined.
      */
-    @Override
-    public Contains contains(final String propertyName, final Geometry 
geometry) {
-        final PropertyName name = property(propertyName);
-        final Literal geom = literal(geometry);
-        return contains(name, geom);
+    public static synchronized FilterFactory<Feature, Object, Object> 
forFeatures() {
+        return Features.DEFAULT;
     }
 
     /**
-     * {@inheritDoc}
+     * Describes the abilities of this factory. The description includes 
restrictions on
+     * the available spatial operations, scalar operations, lists of supported 
functions,
+     * and description of which geometry literals are understood.
+     *
+     * @return description of the abilities of this factory.
      */
     @Override
-    public Contains contains(final Expression left, final Expression right) {
-        return new SpatialFunction.Contains(left, right);
+    public FilterCapabilities getCapabilities() {
+        return Capabilities.INSTANCE;
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Crosses crosses(final String propertyName, final Geometry geometry) 
{
-        final PropertyName name = property(propertyName);
-        final Literal geom = literal(geometry);
-        return crosses(name, geom);
-    }
+     * A filter factory operating on {@link Feature} instances.
+     *
+     * @param  <G>  base class of geometry objects. The implementation-neutral 
type is GeoAPI {@link Geometry},
+     *              but this factory allows the use of other implementations 
such as JTS
+     *              {@link org.locationtech.jts.geom.Geometry} or ESRI {@link 
com.esri.core.geometry.Geometry}.
+     * @param  <T>  base class of temporal objects.
+     */
+    public static class Features<G,T> extends DefaultFilterFactory<Feature, G, 
T> {
+        /**
+         * The instance using system default.
+         *
+         * @see #forFeatures()
+         */
+        static final FilterFactory<Feature,Object,Object> DEFAULT =
+                new Features<>(Object.class, Object.class, 
WraparoundMethod.SPLIT);;
+
+        /**
+         * Creates a new factory operating on {@link Feature} instances.
+         * See the {@linkplain 
DefaultFilterFactory#DefaultFilterFactory(Class, Class, WraparoundMethod)}
+         * super-class constructor} for a list of valid class arguments.
+         *
+         * @param  spatial     type of spatial objects,  or {@code 
Object.class} for default.
+         * @param  temporal    type of temporal objects, or {@code 
Object.class} for default.
+         * @param  wraparound  the strategy to use for representing a region 
crossing the anti-meridian.
+         *
+         * @see DefaultFilterFactory#forFeatures()
+         */
+        public Features(final Class<G> spatial, final Class<T> temporal, final 
WraparoundMethod wraparound) {
+            super(spatial, temporal, wraparound);
+        }
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Crosses crosses(final Expression left, final Expression right) {
-        return new SpatialFunction.Crosses(left, right);
-    }
+        /**
+         * Creates a new predicate to identify an identifiable resource within 
a filter expression.
+         * If {@code startTime} and {@code endTime} are non-null, the filter 
will select all versions
+         * of a resource between the specified dates.
+         *
+         * @param  identifier  identifier of the resource that shall be 
selected by the predicate.
+         * @param  version     version of the resource shall be selected, or 
{@code null} for any version.
+         * @param  startTime   start time of the resource to select, or {@code 
null} if none.
+         * @param  endTime     end time of the resource to select, or {@code 
null} if none.
+         * @return the predicate.
+         *
+         * @todo Current implementation ignores the version, start time and 
end time.
+         *       This limitation may be resolved in a future version.
+         */
+        @Override
+        public ResourceId<Feature> resourceId(final String identifier, final 
Version version,
+                                              final Instant startTime, final 
Instant endTime)
+        {
+            return new FilterByIdentifier<>(identifier);
+        }
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Disjoint disjoint(final String propertyName, final Geometry 
geometry) {
-        final PropertyName name = property(propertyName);
-        final Literal geom = literal(geometry);
-        return disjoint(name, geom);
+        /**
+         * Creates an expression whose value is computed by retrieving the 
value indicated by a path in a resource.
+         * If all characters in the path are {@linkplain 
Character#isUnicodeIdentifierPart(int) Unicode identifier parts},
+         * then the XPath expression is simply a property name.
+         *
+         * <p>The desired type of property values can be specified. For 
example if the property values should be numbers,
+         * then {@code type} can be <code>{@linkplain Number}.class</code>. If 
property values can be of any type with no
+         * conversion desired, then {@code type} should be {@code 
Object.class}.</p>
+         *
+         * @param  <V>    the type of the values to be fetched (compile-time 
value of {@code type}).
+         * @param  xpath  the path to the property whose value will be 
returned by the {@code apply(R)} method.
+         * @param  type   the type of the values to be fetched (run-time value 
of {@code <V>}).
+         * @return an expression evaluating the referenced property value.
+         */
+        @Override
+        public <V> ValueReference<Feature,V> property(final String xpath, 
final Class<V> type) {
+            return PropertyValue.create(xpath, type);
+        }
     }
 
     /**
-     * {@inheritDoc}
+     * Creates a constant, literal value that can be used in expressions.
+     * The given value should be data objects such as strings, numbers, dates 
or geometries.
+     *
+     * @param  <V>    the type of the value of the literal.
+     * @param  value  the literal value. May be {@code null}.
+     * @return a literal for the given value.
      */
     @Override
-    public Disjoint disjoint(final Expression left, final Expression right) {
-        return new SpatialFunction.Disjoint(left, right);
+    public <V> Literal<R,V> literal(final V value) {
+        return new LeafExpression.Literal<>(value);
     }
 
     /**
-     * {@inheritDoc}
+     * Filter operator that compares that two sub-expressions are equal to 
each other.
+     *
+     * @param  expression1     the first of the two expressions to be used by 
this comparator.
+     * @param  expression2     the second of the two expressions to be used by 
this comparator.
+     * @param  isMatchingCase  specifies whether comparisons are case 
sensitive.
+     * @param  matchAction     specifies how the comparisons shall be 
evaluated for a collection of values.
+     * @return a filter evaluating {@code expression1} = {@code expression2}.
+     *
+     * @see ComparisonOperatorName#PROPERTY_IS_EQUAL_TO
+     * @todo Revisit if we can be more specific on the second parameterized 
type in expressions.
      */
     @Override
-    public DWithin dwithin(final String propertyName, final Geometry geometry,
-            final double distance, final String units)
+    public BinaryComparisonOperator<R> equal(final Expression<? super R, ?> 
expression1,
+                                             final Expression<? super R, ?> 
expression2,
+                                             boolean isMatchingCase, 
MatchAction matchAction)
     {
-        final PropertyName name = property(propertyName);
-        final Literal geom = literal(geometry);
-        return dwithin(name, geom, distance, units);
+        return new ComparisonFunction.EqualTo<>(expression1, expression2, 
isMatchingCase, matchAction);
     }
 
     /**
-     * {@inheritDoc}
+     * Filter operator that compares that its two sub-expressions are not 
equal to each other.
+     *
+     * @param  expression1     the first of the two expressions to be used by 
this comparator.
+     * @param  expression2     the second of the two expressions to be used by 
this comparator.
+     * @param  isMatchingCase  specifies whether comparisons are case 
sensitive.
+     * @param  matchAction     specifies how the comparisons shall be 
evaluated for a collection of values.
+     * @return a filter evaluating {@code expression1} ≠ {@code expression2}.
+     *
+     * @see ComparisonOperatorName#PROPERTY_IS_NOT_EQUAL_TO
+     * @todo Revisit if we can be more specific on the second parameterized 
type in expressions.
      */
     @Override
-    public DWithin dwithin(final Expression left, final Expression right,
-            final double distance, final String units)
+    public BinaryComparisonOperator<R> notEqual(final Expression<? super R, ?> 
expression1,
+                                                final Expression<? super R, ?> 
expression2,
+                                                boolean isMatchingCase, 
MatchAction matchAction)
     {
-        return new SpatialFunction.DWithin(left, right, distance, units);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Equals equals(final String propertyName, final Geometry geometry) {
-        final PropertyName name = property(propertyName);
-        final Literal geom = literal(geometry);
-        return equal(name, geom);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Equals equal(final Expression left, final Expression right) {
-        return new SpatialFunction.Equals(left, right);
+        return new ComparisonFunction.NotEqualTo<>(expression1, expression2, 
isMatchingCase, matchAction);
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Intersects intersects(final String propertyName, final Geometry 
geometry) {
-        final PropertyName name = property(propertyName);
-        final Literal geom = literal(geometry);
-        return intersects(name, geom);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Intersects intersects(final Expression left, final Expression 
right) {
-        return new SpatialFunction.Intersects(left, right);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Overlaps overlaps(final String propertyName, final Geometry 
geometry) {
-        final PropertyName name = property(propertyName);
-        final Literal geom = literal(geometry);
-        return overlaps(name, geom);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Overlaps overlaps(final Expression left, final Expression right) {
-        return new SpatialFunction.Overlaps(left, right);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Touches touches(final String propertyName, final Geometry geometry) 
{
-        final PropertyName name = property(propertyName);
-        final Literal geom = literal(geometry);
-        return touches(name, geom);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Touches touches(final Expression left, final Expression right) {
-        return new SpatialFunction.Touches(left, right);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Within within(final String propertyName, final Geometry geometry) {
-        final PropertyName name = property(propertyName);
-        final Literal geom = literal(geometry);
-        return within(name, geom);
-    }
-
-    /**
-     * {@inheritDoc}
+     * Filter operator that checks that its first sub-expression is less than 
its second sub-expression.
+     *
+     * @param  expression1     the first of the two expressions to be used by 
this comparator.
+     * @param  expression2     the second of the two expressions to be used by 
this comparator.
+     * @param  isMatchingCase  specifies whether comparisons are case 
sensitive.
+     * @param  matchAction     specifies how the comparisons shall be 
evaluated for a collection of values.
+     * @return a filter evaluating {@code expression1} &lt; {@code 
expression2}.
+     *
+     * @see ComparisonOperatorName#PROPERTY_IS_LESS_THAN
+     * @todo Revisit if we can be more specific on the second parameterized 
type in expressions.
      */
     @Override
-    public Within within(final Expression left, final Expression right) {
-        return new SpatialFunction.Within(left, right);
+    public BinaryComparisonOperator<R> less(final Expression<? super R, ?> 
expression1,
+                                            final Expression<? super R, ?> 
expression2,
+                                            boolean isMatchingCase, 
MatchAction matchAction)
+    {
+        return new ComparisonFunction.LessThan<>(expression1, expression2, 
isMatchingCase, matchAction);
     }
 
-    // IDENTIFIERS 
/////////////////////////////////////////////////////////////
-
     /**
-     * {@inheritDoc}
+     * Filter operator that checks that its first sub-expression is greater 
than its second sub-expression.
+     *
+     * @param  expression1     the first of the two expressions to be used by 
this comparator.
+     * @param  expression2     the second of the two expressions to be used by 
this comparator.
+     * @param  isMatchingCase  specifies whether comparisons are case 
sensitive.
+     * @param  matchAction     specifies how the comparisons shall be 
evaluated for a collection of values.
+     * @return a filter evaluating {@code expression1} &gt; {@code 
expression2}.
+     *
+     * @see ComparisonOperatorName#PROPERTY_IS_GREATER_THAN
+     * @todo Revisit if we can be more specific on the second parameterized 
type in expressions.
      */
     @Override
-    public FeatureId featureId(final String id) {
-        return new DefaultObjectId(id);
+    public BinaryComparisonOperator<R> greater(final Expression<? super R, ?> 
expression1,
+                                               final Expression<? super R, ?> 
expression2,
+                                               boolean isMatchingCase, 
MatchAction matchAction)
+    {
+        return new ComparisonFunction.GreaterThan<>(expression1, expression2, 
isMatchingCase, matchAction);
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public GmlObjectId gmlObjectId(final String id) {
-        return new DefaultObjectId(id);
-    }
-
-    // FILTERS 
/////////////////////////////////////////////////////////////////
-
-    /**
-     * {@inheritDoc}
+     * Filter operator that checks that its first sub-expression is less than 
or equal to its second sub-expression.
+     *
+     * @param  expression1     the first of the two expressions to be used by 
this comparator.
+     * @param  expression2     the second of the two expressions to be used by 
this comparator.
+     * @param  isMatchingCase  specifies whether comparisons are case 
sensitive.
+     * @param  matchAction     specifies how the comparisons shall be 
evaluated for a collection of values.
+     * @return a filter evaluating {@code expression1} ≤ {@code expression2}.
+     *
+     * @see ComparisonOperatorName#PROPERTY_IS_LESS_THAN_OR_EQUAL_TO
+     * @todo Revisit if we can be more specific on the second parameterized 
type in expressions.
      */
     @Override
-    public And and(final Filter filter1, final Filter filter2) {
-        return and(Arrays.asList(filter1, filter2));
+    public BinaryComparisonOperator<R> lessOrEqual(final Expression<? super R, 
?> expression1,
+                                                   final Expression<? super R, 
?> expression2,
+                                                   boolean isMatchingCase, 
MatchAction matchAction)
+    {
+        return new ComparisonFunction.LessThanOrEqualTo<>(expression1, 
expression2, isMatchingCase, matchAction);
     }
 
     /**
-     * {@inheritDoc}
+     * Filter operator that checks that its first sub-expression is greater 
than its second sub-expression.
+     *
+     * @param  expression1     the first of the two expressions to be used by 
this comparator.
+     * @param  expression2     the second of the two expressions to be used by 
this comparator.
+     * @param  isMatchingCase  specifies whether comparisons are case 
sensitive.
+     * @param  matchAction     specifies how the comparisons shall be 
evaluated for a collection of values.
+     * @return a filter evaluating {@code expression1} ≥ {@code expression2}.
+     *
+     * @see ComparisonOperatorName#PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO
+     * @todo Revisit if we can be more specific on the second parameterized 
type in expressions.
      */
     @Override
-    public And and(final List<Filter> filters) {
-        return new LogicalFunction.And(filters);
+    public BinaryComparisonOperator<R> greaterOrEqual(final Expression<? super 
R, ?> expression1,
+                                                      final Expression<? super 
R, ?> expression2,
+                                                      boolean isMatchingCase, 
MatchAction matchAction)
+    {
+        return new ComparisonFunction.GreaterThanOrEqualTo<>(expression1, 
expression2, isMatchingCase, matchAction);
     }
 
     /**
-     * {@inheritDoc}
+     * Filter operation for a range check.
+     * The lower and upper boundary values are inclusive.
+     *
+     * @param  expression     the expression to be compared by this comparator.
+     * @param  lowerBoundary  the lower bound (inclusive) as an expression.
+     * @param  upperBoundary  the upper bound (inclusive) as an expression.
+     * @return a filter evaluating ({@code expression} ≥ {@code lowerBoundary})
+     *                       &amp; ({@code expression} ≤ {@code 
upperBoundary}).
      */
     @Override
-    public Or or(final Filter filter1, final Filter filter2) {
-        return or(Arrays.asList(filter1, filter2));
+    public BetweenComparisonOperator<R> between(final Expression<? super R, ?> 
expression,
+                                                final Expression<? super R, ?> 
lowerBoundary,
+                                                final Expression<? super R, ?> 
upperBoundary)
+    {
+        return new ComparisonFunction.Between<>(expression, lowerBoundary, 
upperBoundary);
     }
 
     /**
-     * {@inheritDoc}
+     * Character string comparison operator with pattern matching and 
specified wildcards.
+     *
+     * @param  expression      source of values to compare against the pattern.
+     * @param  pattern         pattern to match against expression values.
+     * @param  wildcard        pattern character for matching any sequence of 
characters.
+     * @param  singleChar      pattern character for matching exactly one 
character.
+     * @param  escape          pattern character for indicating that the next 
character should be matched literally.
+     * @param  isMatchingCase  specifies how a filter expression processor 
should perform string comparisons.
+     * @return a character string comparison operator with pattern matching.
      */
     @Override
-    public Or or(final List<Filter> filters) {
-        return new LogicalFunction.Or(filters);
+    public LikeOperator<R> like(final Expression<? super R, ?> expression, 
final String pattern,
+            final char wildcard, final char singleChar, final char escape, 
final boolean isMatchingCase)
+    {
+        return new DefaultLike<>(expression, pattern, wildcard, singleChar, 
escape, isMatchingCase);
     }
 
     /**
-     * {@inheritDoc}
+     * An operator that tests if an expression's value is {@code null}.
+     * This corresponds to checking whether the property exists in the 
real-world.
+     *
+     * @param  expression  source of values to compare against {@code null}.
+     * @return a filter that checks if an expression's value is {@code null}.
      */
     @Override
-    public Not not(final Filter filter) {
-        return new UnaryFunction.Not(filter);
+    public NullOperator<R> isNull(final Expression<? super R, ?> expression) {
+        return new UnaryFunction.IsNull<>(expression);
     }
 
     /**
-     * {@inheritDoc}
+     * An operator that tests if an expression's value is nil.
+     * The difference with {@link NullOperator} is that a value should exist
+     * but can not be provided for the reason given by {@code nilReason}.
+     * Possible reasons are:
+     *
+     * <ul>
+     *   <li><b>inapplicable</b> — there is no value.</li>
+     *   <li><b>template</b>     — the value will be available later.</li>
+     *   <li><b>missing</b>      — the correct value is not readily available 
to the sender of this data.
+     *                             Furthermore, a correct value may not 
exist.</li>
+     *   <li><b>unknown</b>      — the correct value is not known to, and not 
computable by, the sender of this data.
+     *                             However, a correct value probably 
exists..</li>
+     *   <li><b>withheld</b>     — the value is not divulged.</li>
+     *   <li>Other strings at implementation choice.</li>
+     * </ul>
+     *
+     * @param  expression  source of values to compare against nil values.
+     * @param  nilReason   the reason why the value is nil, or {@code null} 
for accepting any reason.
+     * @return a filter that checks if an expression's value is nil for the 
specified reason.
+     *
+     * @see org.apache.sis.xml.NilObject
+     * @see org.apache.sis.xml.NilReason
      */
     @Override
-    public Id id(final Set<? extends Identifier> ids) {
-        return new FilterByIdentifier(ids);
+    public NilOperator<R> isNil(final Expression<? super R, ?> expression, 
final String nilReason) {
+        return new UnaryFunction.IsNil<>(expression, nilReason);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates a {@code AND} filter between two or more filters.
+     *
+     * @param  operands  a collection of at least 2 operands.
+     * @return a filter evaluating {@code operand1 AND operand2 AND operand3}…
+     * @throws IllegalArgumentException if the given collection contains less 
than 2 elements.
+     *
+     * @see LogicalOperatorName#AND
      */
     @Override
-    public PropertyName property(final GenericName name) {
-        return property(name.toString());
+    public LogicalOperator<R> and(final Collection<? extends Filter<? super 
R>> operands) {
+        return new LogicalFunction.And<>(operands);
     }
 
     /**
-     * Creates a new expression retrieving values from a property of the given 
name.
+     * Creates a {@code OR} filter between two or more filters.
      *
-     * @param  name  name of the property (usually a feature attribute).
+     * @param  operands  a collection of at least 2 operands.
+     * @return a filter evaluating {@code operand1 OR operand2 OR operand3}…
+     * @throws IllegalArgumentException if the given collection contains less 
than 2 elements.
+     *
+     * @see LogicalOperatorName#OR
      */
     @Override
-    public PropertyName property(final String name) {
-        return new LeafExpression.Property(name);
+    public LogicalOperator<R> or(final Collection<? extends Filter<? super R>> 
operands) {
+        return new LogicalFunction.Or<>(operands);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates a {@code NOT} filter for the given filter.
+     *
+     * @param  operand  the operand of the NOT operation.
+     * @return a filter evaluating {@code NOT operand}.
+     *
+     * @see LogicalOperatorName#NOT
      */
     @Override
-    public PropertyIsBetween between(final Expression expression, final 
Expression lower, final Expression upper) {
-        return new ComparisonFunction.Between(expression, lower, upper);
+    public LogicalOperator<R> not(final Filter<? super R> operand) {
+        return new LogicalFunction.Not<>(operand);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates an operator that checks if the bounding box of the feature's 
geometry interacts
+     * with the bounding box provided in the filter properties.
+     *
+     * @param  geometry  expression fetching the geometry to check for 
interaction with bounds.
+     * @param  bounds    the bounds to check geometry against.
+     * @return a filter checking for any interactions between the bounding 
boxes.
+     *
+     * @see SpatialOperatorName#BBOX
+     *
+     * @todo Maybe the expression parameterized type should extend {@link 
Geometry}.
      */
     @Override
-    public PropertyIsEqualTo equals(final Expression expression1, final 
Expression expression2) {
-        return equal(expression1, expression2, DEFAULT_MATCH_CASE, 
MatchAction.ANY);
+    public BinarySpatialOperator<R> bbox(final Expression<? super R, ? extends 
G> geometry, final Envelope bounds) {
+        return new SpatialFunction<>(library, geometry, bounds, wraparound);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates an operator that checks if the geometry of the two operands are 
equal.
+     *
+     * @param  geometry1  expression fetching the first geometry of the binary 
operator.
+     * @param  geometry2  expression fetching the second geometry of the 
binary operator.
+     * @return a filter for the "Equals" operation between the two geometries.
+     *
+     * @see SpatialOperatorName#EQUALS
+     *
+     * @todo Rename {@code equal}.
      */
     @Override
-    public PropertyIsEqualTo equal(final Expression expression1, final 
Expression expression2,
-                                   final boolean isMatchingCase, final 
MatchAction matchAction)
+    public BinarySpatialOperator<R> equals(final Expression<? super R, ? 
extends G> geometry1,
+                                           final Expression<? super R, ? 
extends G> geometry2)
     {
-        return new ComparisonFunction.EqualTo(expression1, expression2, 
isMatchingCase, matchAction);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsNotEqualTo notEqual(final Expression expression1, final 
Expression expression2) {
-        return notEqual(expression1, expression2, DEFAULT_MATCH_CASE, 
MatchAction.ANY);
+        return new SpatialFunction<>(SpatialOperatorName.EQUALS, library, 
geometry1, geometry2);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates an operator that checks if the first operand is disjoint from 
the second.
+     *
+     * @param  geometry1  expression fetching the first geometry of the binary 
operator.
+     * @param  geometry2  expression fetching the second geometry of the 
binary operator.
+     * @return a filter for the "Disjoint" operation between the two 
geometries.
+     *
+     * @see SpatialOperatorName#DISJOINT
      */
     @Override
-    public PropertyIsNotEqualTo notEqual(final Expression expression1, final 
Expression expression2,
-                                         final boolean isMatchingCase, final 
MatchAction matchAction)
+    public BinarySpatialOperator<R> disjoint(final Expression<? super R, ? 
extends G> geometry1,
+                                             final Expression<? super R, ? 
extends G> geometry2)
     {
-        return new ComparisonFunction.NotEqualTo(expression1, expression2, 
isMatchingCase, matchAction);
+        return new SpatialFunction<>(SpatialOperatorName.DISJOINT, library, 
geometry1, geometry2);
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsGreaterThan greater(final Expression expression1, final 
Expression expression2) {
-        return greater(expression1, expression2, DEFAULT_MATCH_CASE, 
MatchAction.ANY);
-    }
-
-    /**
-     * {@inheritDoc}
+     * Creates an operator that checks if the two geometric operands intersect.
+     *
+     * @param  geometry1  expression fetching the first geometry of the binary 
operator.
+     * @param  geometry2  expression fetching the second geometry of the 
binary operator.
+     * @return a filter for the "Intersects" operation between the two 
geometries.
+     *
+     * @see SpatialOperatorName#INTERSECTS
      */
     @Override
-    public PropertyIsGreaterThan greater(final Expression expression1, final 
Expression expression2,
-                                         final boolean isMatchingCase, final 
MatchAction matchAction)
+    public BinarySpatialOperator<R> intersects(final Expression<? super R, ? 
extends G> geometry1,
+                                               final Expression<? super R, ? 
extends G> geometry2)
     {
-        return new ComparisonFunction.GreaterThan(expression1, expression2, 
isMatchingCase, matchAction);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsGreaterThanOrEqualTo greaterOrEqual(final Expression 
expression1, final Expression expression2) {
-        return greaterOrEqual(expression1, expression2, DEFAULT_MATCH_CASE, 
MatchAction.ANY);
+        return new SpatialFunction<>(SpatialOperatorName.INTERSECTS, library, 
geometry1, geometry2);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates an operator that checks if the two geometric operands touch 
each other, but do not overlap.
+     *
+     * @param  geometry1  expression fetching the first geometry of the binary 
operator.
+     * @param  geometry2  expression fetching the second geometry of the 
binary operator.
+     * @return a filter for the "Touches" operation between the two geometries.
+     *
+     * @see SpatialOperatorName#TOUCHES
      */
     @Override
-    public PropertyIsGreaterThanOrEqualTo greaterOrEqual(final Expression 
expression1, final Expression expression2,
-                                                         final boolean 
isMatchingCase, final MatchAction matchAction)
+    public BinarySpatialOperator<R> touches(final Expression<? super R, ? 
extends G> geometry1,
+                                            final Expression<? super R, ? 
extends G> geometry2)
     {
-        return new ComparisonFunction.GreaterThanOrEqualTo(expression1, 
expression2, isMatchingCase, matchAction);
+        return new SpatialFunction<>(SpatialOperatorName.TOUCHES, library, 
geometry1, geometry2);
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsLessThan less(final Expression expression1, final 
Expression expression2) {
-        return less(expression1, expression2, DEFAULT_MATCH_CASE, 
MatchAction.ANY);
-    }
-
-    /**
-     * {@inheritDoc}
+     * Creates an operator that checks if the first geometric operand crosses 
the second.
+     *
+     * @param  geometry1  expression fetching the first geometry of the binary 
operator.
+     * @param  geometry2  expression fetching the second geometry of the 
binary operator.
+     * @return a filter for the "Crosses" operation between the two geometries.
+     *
+     * @see SpatialOperatorName#CROSSES
      */
     @Override
-    public PropertyIsLessThan less(final Expression expression1, final 
Expression expression2,
-                                   final boolean isMatchingCase, MatchAction 
matchAction)
+    public BinarySpatialOperator<R> crosses(final Expression<? super R, ? 
extends G> geometry1,
+                                            final Expression<? super R, ? 
extends G> geometry2)
     {
-        return new ComparisonFunction.LessThan(expression1, expression2, 
isMatchingCase, matchAction);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsLessThanOrEqualTo lessOrEqual(final Expression 
expression1, final Expression expression2) {
-        return lessOrEqual(expression1, expression2, DEFAULT_MATCH_CASE, 
MatchAction.ANY);
+        return new SpatialFunction<>(SpatialOperatorName.CROSSES, library, 
geometry1, geometry2);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates an operator that checks if the first geometric operand is 
completely
+     * contained by the constant geometric operand.
+     *
+     * @param  geometry1  expression fetching the first geometry of the binary 
operator.
+     * @param  geometry2  expression fetching the second geometry of the 
binary operator.
+     * @return a filter for the "Within" operation between the two geometries.
+     *
+     * @see SpatialOperatorName#WITHIN
      */
     @Override
-    public PropertyIsLessThanOrEqualTo lessOrEqual(final Expression 
expression1, final Expression expression2,
-                                                   final boolean 
isMatchingCase, final MatchAction matchAction)
+    public BinarySpatialOperator<R> within(final Expression<? super R, ? 
extends G> geometry1,
+                                           final Expression<? super R, ? 
extends G> geometry2)
     {
-        return new ComparisonFunction.LessThanOrEqualTo(expression1, 
expression2, isMatchingCase, matchAction);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsLike like(final Expression expression, final String 
pattern) {
-        return like(expression, pattern, "*", "?", "\\");
+        return new SpatialFunction<>(SpatialOperatorName.WITHIN, library, 
geometry1, geometry2);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates an operator that checks if the first geometric operand contains 
the second.
+     *
+     * @param  geometry1  expression fetching the first geometry of the binary 
operator.
+     * @param  geometry2  expression fetching the second geometry of the 
binary operator.
+     * @return a filter for the "Contains" operation between the two 
geometries.
+     *
+     * @see SpatialOperatorName#CONTAINS
      */
     @Override
-    public PropertyIsLike like(final Expression expression, final String 
pattern,
-            final String wildcard, final String singleChar, final String 
escape)
+    public BinarySpatialOperator<R> contains(final Expression<? super R, ? 
extends G> geometry1,
+                                             final Expression<? super R, ? 
extends G> geometry2)
     {
-        return like(expression, pattern, wildcard, singleChar, escape, 
DEFAULT_MATCH_CASE);
+        return new SpatialFunction<>(SpatialOperatorName.CONTAINS, library, 
geometry1, geometry2);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates an operator that checks if the interior of the first geometric 
operand
+     * somewhere overlaps the interior of the second geometric operand.
+     *
+     * @param  geometry1  expression fetching the first geometry of the binary 
operator.
+     * @param  geometry2  expression fetching the second geometry of the 
binary operator.
+     * @return a filter for the "Overlaps" operation between the two 
geometries.
+     *
+     * @see SpatialOperatorName#OVERLAPS
      */
     @Override
-    public PropertyIsLike like(final Expression expression, final String 
pattern,
-            final String wildcard, final String singleChar,
-            final String escape, final boolean isMatchingCase)
+    public BinarySpatialOperator<R>  overlaps(final Expression<? super R, ? 
extends G> geometry1,
+                                              final Expression<? super R, ? 
extends G> geometry2)
     {
-        return new DefaultLike(expression, pattern, wildcard, singleChar, 
escape, isMatchingCase);
+        return new SpatialFunction<>(SpatialOperatorName.OVERLAPS, library, 
geometry1, geometry2);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates an operator that checks if all of a feature's geometry is more 
distant

Review comment:
       s/is more/are more ?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to