You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@sis.apache.org by GitBox <gi...@apache.org> on 2021/06/22 15:07:23 UTC

[GitHub] [sis] desruisseaux opened a new pull request #23: Updates for change in `Filter` interfaces:

desruisseaux opened a new pull request #23:
URL: https://github.com/apache/sis/pull/23


   This is an upgrade for major changes in `org.opengis.filter` package. The GeiAPI interfaces have been modified for using the model defined by UML in ISO 19143 (OGC filter encoding) international standard instead of XML schema. This is incompatible changes, but the old interfaces were in the "pending" section of GeoAPI and have never been part of a formal GeoAPI release. The plan was described in https://github.com/opengeospatial/geoapi/issues/32.
   
   This merge request depends on https://github.com/opengeospatial/geoapi/pull/67.
   


-- 
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:
users@infra.apache.org



[GitHub] [sis] asfgit merged pull request #23: Updates for change in `Filter` interfaces:

Posted by GitBox <gi...@apache.org>.
asfgit merged pull request #23:
URL: https://github.com/apache/sis/pull/23


   


-- 
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.

To unsubscribe, e-mail: dev-unsubscribe@sis.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [sis] desruisseaux commented on a change in pull request #23: Updates for change in `Filter` interfaces:

Posted by GitBox <gi...@apache.org>.
desruisseaux commented on a change in pull request #23:
URL: https://github.com/apache/sis/pull/23#discussion_r656929804



##########
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:
       Yes you are right, this is a copy-and-paste error. Thanks for spotting!




-- 
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:
users@infra.apache.org



[GitHub] [sis] desruisseaux commented on a change in pull request #23: Updates for change in `Filter` interfaces:

Posted by GitBox <gi...@apache.org>.
desruisseaux commented on a change in pull request #23:
URL: https://github.com/apache/sis/pull/23#discussion_r656942594



##########
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:
       Fixed 10 occurrences of this error in the code base.




-- 
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:
users@infra.apache.org



[GitHub] [sis] desruisseaux commented on a change in pull request #23: Updates for change in `Filter` interfaces:

Posted by GitBox <gi...@apache.org>.
desruisseaux commented on a change in pull request #23:
URL: https://github.com/apache/sis/pull/23#discussion_r656940609



##########
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:
       Thanks, I take this opportunity for fixing this error in the whole code base (~20 occurrences).




-- 
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:
users@infra.apache.org



[GitHub] [sis] desruisseaux commented on a change in pull request #23: Updates for change in `Filter` interfaces:

Posted by GitBox <gi...@apache.org>.
desruisseaux commented on a change in pull request #23:
URL: https://github.com/apache/sis/pull/23#discussion_r656933835



##########
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:
       A single line is used between methods, but in this case it is a separation between bigger entities (inner classes). I have no strong opinion about what should be the formatting guideline in those cases however. If there is a well-established practice I would be happy to apply.




-- 
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:
users@infra.apache.org



[GitHub] [sis] kinow commented on a change in pull request #23: Updates for change in `Filter` interfaces:

Posted by GitBox <gi...@apache.org>.
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:
users@infra.apache.org



[GitHub] [sis] desruisseaux commented on a change in pull request #23: Updates for change in `Filter` interfaces:

Posted by GitBox <gi...@apache.org>.
desruisseaux commented on a change in pull request #23:
URL: https://github.com/apache/sis/pull/23#discussion_r656925259



##########
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:
       Thanks for the review! Yes, the `@since` tag should show when the file was introduced in public API. But this CQL file has never been released yet; we removed it from SIS 1.0 release because it was not ready at that time. SIS 1.1 would be its first release.
   
   On the `@version` tag, the current practice in SIS is to set that number to the latest SIS release where that file got significant change. So even if we update a file many times between SIS 1.0 and 1.1, the `@version` tag is still updated only once until the release. Which changes are considered "significant" is left to developer appreciation. For example I do not update the version number if I only improved javadoc with no code change.




-- 
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:
users@infra.apache.org



[GitHub] [sis] kinow commented on a change in pull request #23: Updates for change in `Filter` interfaces:

Posted by GitBox <gi...@apache.org>.
kinow commented on a change in pull request #23:
URL: https://github.com/apache/sis/pull/23#discussion_r656940563



##########
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:
       >because it was not ready at that time. SIS 1.1 would be its first release.
   
   Ah! :+1: makes sense!
   
   >Which changes are considered "significant" is left to developer appreciation. For example I do not update the version number if I only improved javadoc with no code change.
   
   Makes sense too, thanks Martin!




-- 
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:
users@infra.apache.org



[GitHub] [sis] kinow commented on a change in pull request #23: Updates for change in `Filter` interfaces:

Posted by GitBox <gi...@apache.org>.
kinow commented on a change in pull request #23:
URL: https://github.com/apache/sis/pull/23#discussion_r656941302



##########
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:
       No strong opinion either, just pointed in case it was unintentional :-)




-- 
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:
users@infra.apache.org



[GitHub] [sis] kinow commented on a change in pull request #23: Updates for change in `Filter` interfaces:

Posted by GitBox <gi...@apache.org>.
kinow commented on a change in pull request #23:
URL: https://github.com/apache/sis/pull/23#discussion_r656940563



##########
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:
       >because it was not ready at that time. SIS 1.1 would be its first release.
   
   Ah! :+1: makes sense!
   
   >Which changes are considered "significant" is left to developer appreciation. For example I do not update the version number if I only improved javadoc with no code change.
   
   Fair enough, marking this one as resolved to simplify the review for others. Thanks Martin!




-- 
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:
users@infra.apache.org