You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/05/01 15:27:05 UTC

[sis] 01/02: Implement the `getResourceClass()` method added in `Filter` and `Expression` interfaces. That method make possible to determine whether some optimizations are allowed or not.

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 26a0f263d22459f766629a67e3c382a37dfe5868
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon May 1 13:38:39 2023 +0200

    Implement the `getResourceClass()` method added in `Filter` and `Expression` interfaces.
    That method make possible to determine whether some optimizations are allowed or not.
---
 .../org/apache/sis/feature/FeatureOperations.java  |  2 +-
 .../org/apache/sis/filter/AssociationValue.java    | 10 +++++++++-
 .../java/org/apache/sis/filter/BinaryFunction.java | 11 +++++++++++
 .../apache/sis/filter/BinaryGeometryFilter.java    | 11 +++++++++++
 .../org/apache/sis/filter/ComparisonFilter.java    |  6 ++++++
 .../apache/sis/filter/DefaultFilterFactory.java    |  4 ++--
 .../java/org/apache/sis/filter/FilterNode.java     | 13 +++++++++++--
 .../org/apache/sis/filter/IdentifierFilter.java    | 19 ++++++++++++-------
 .../java/org/apache/sis/filter/LikeFilter.java     |  8 ++++++++
 .../java/org/apache/sis/filter/LogicalFilter.java  | 17 +++++++++++++++++
 .../java/org/apache/sis/filter/Optimization.java   |  8 ++++----
 .../java/org/apache/sis/filter/PropertyValue.java  | 12 ++++++++++--
 .../java/org/apache/sis/filter/UnaryFunction.java  | 10 ++++++++++
 .../sis/internal/coverage/j2d/ObservableImage.java |  2 +-
 .../sis/internal/filter/GeometryConverter.java     |  8 ++++++++
 .../java/org/apache/sis/internal/filter/Node.java  | 22 +++++++++++++++++++++-
 .../internal/filter/sqlmm/FunctionWithSRID.java    |  9 +++++++++
 .../internal/filter/sqlmm/GeometryConstructor.java |  8 ++++++++
 .../sis/internal/filter/sqlmm/OneGeometry.java     | 18 +++++++++++++++++-
 .../apache/sis/internal/filter/sqlmm/ST_Point.java | 12 ++++++++++++
 .../sis/internal/filter/sqlmm/ST_Transform.java    |  8 ++++++++
 .../sis/internal/filter/sqlmm/TwoGeometries.java   | 16 ++++++++++++++++
 .../apache/sis/filter/IdentifierFilterTest.java    |  4 +++-
 .../org/apache/sis/filter/LeafExpressionTest.java  |  3 ++-
 .../apache/sis/internal/filter/FunctionMock.java   |  8 ++++++++
 .../sis/internal/filter/FunctionNamesTest.java     |  2 ++
 .../sis/internal/filter/ValueReferenceMock.java    |  8 ++++++++
 27 files changed, 235 insertions(+), 24 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
index b9ab137a05..b4ab1692d0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
@@ -290,7 +290,7 @@ public final class FeatureOperations extends Static {
     /**
      * Creates an operation which delegates the computation to a given expression producing values of unknown type.
      * This method can be used as an alternative to {@link #expression expression(…)} when the constraint on the
-     * parameterized type {@code <V>} between {@code expression} and {@code result} can not be enforced at compile time.
+     * parameterized type {@code <V>} between {@code expression} and {@code result} cannot be enforced at compile time.
      * This method casts or converts the expression to the expected type by a call to
      * {@link Expression#toValueType(Class)}.
      *
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/AssociationValue.java b/core/sis-feature/src/main/java/org/apache/sis/filter/AssociationValue.java
index 16d967535c..c8d714b8d5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/AssociationValue.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/AssociationValue.java
@@ -42,7 +42,7 @@ import org.opengis.filter.ValueReference;
  * (the tip) is evaluated by a {@link PropertyValue}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  *
  * @param  <V>  the type of value computed by the expression.
  *
@@ -94,6 +94,14 @@ final class AssociationValue<V> extends LeafExpression<Feature, V>
         this.accessor = accessor;
     }
 
+    /**
+     * Returns the class of resources expected by this expression.
+     */
+    @Override
+    public final Class<Feature> getResourceClass() {
+        return Feature.class;
+    }
+
     /**
      * For {@link #toString()} implementation.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
index b7c33e33a2..a149e05cf9 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
@@ -84,6 +84,17 @@ abstract class BinaryFunction<R,V1,V2> extends Node {
         this.expression2 = expression2;
     }
 
+    /**
+     * Returns the class of resources expected by this filter.
+     * Defined for {@link Filter#getResourceClass()} and {@link Expression#getResourceClass()} implementations.
+     *
+     * @return type of resources accepted by this filter, or {@code null} if inconsistent.
+     */
+    public final Class<? super R> getResourceClass() {
+        return specializedClass(expression1.getResourceClass(),
+                                expression2.getResourceClass());
+    }
+
     /**
      * Returns the expressions used as parameters by this function.
      * Defined for {@link Expression#getParameters()} implementations.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
index f668f78886..8b62b37a7f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
@@ -164,6 +164,17 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
         return unwrapped;
     }
 
+    /**
+     * Returns the class of resources expected by this filter.
+     *
+     * @return type of resources accepted by this filter, or {@code null} if inconsistent.
+     */
+    @Override
+    public final Class<? super R> getResourceClass() {
+        return specializedClass(expression1.getResourceClass(),
+                                expression2.getResourceClass());
+    }
+
     /**
      * Returns the two expressions used as parameters by this filter.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java
index 1853662fe7..791e545b61 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java
@@ -812,6 +812,12 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
             this.upper = new    LessThanOrEqualTo<>(expression, upper, true, MatchAction.ANY);
         }
 
+        /** Returns the class of resources expected by this filter. */
+        @Override public final Class<? super R> getResourceClass() {
+            return specializedClass(lower.getResourceClass(),
+                                    upper.getResourceClass());
+        }
+
         /**
          * Returns the 3 children of this node. Since {@code lower.expression2}
          * is the same as {@code upper.expression1}, that repetition is omitted.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
index 5f381105b0..7d56e36cc1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
@@ -186,7 +186,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
          */
         @Override
         public ResourceId<Feature> resourceId(final String identifier) {
-            return new IdentifierFilter<>(identifier);
+            return new IdentifierFilter(identifier);
         }
 
         /**
@@ -207,7 +207,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
         public ResourceId<Feature> resourceId(final String identifier, final Version version,
                                               final Instant startTime, final Instant endTime)
         {
-            return new IdentifierFilter<>(identifier);
+            return new IdentifierFilter(identifier);
         }
 
         /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java
index d5a8c25b85..d35ad68a46 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java
@@ -58,10 +58,19 @@ abstract class FilterNode<R> extends Node implements Filter<R> {
      * @param  <R>       desired parameterized type.
      * @param  template  the filter from which to get the runtime value of {@code <R>}.
      * @param  other     the predicate to cast to a filter compatible with the target.
-     * @return the casted predicate, or {@code null} if it can not be casted.
+     * @return the casted predicate, or {@code null} if it cannot be casted.
      */
+    @SuppressWarnings("unchecked")
     static <R> Filter<R> castOrNull(final Filter<R> template, final Predicate<? super R> other) {
-        // TODO
+        if (other instanceof Filter<?>) {
+            final Class<?> type = template.getResourceClass();
+            if (type != null) {
+                final Class<?> to = ((Filter<?>) other).getResourceClass();
+                if (to != null && type.isAssignableFrom(to)) {
+                    return (Filter<R>) other;
+                }
+            }
+        }
         return null;
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java
index fbfe274224..3e2ec855a0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java
@@ -34,12 +34,9 @@ import org.opengis.filter.ResourceId;
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.4
- *
- * @param  <R>  the type of resources used as inputs.
- *
- * @since 1.1
+ * @since   1.1
  */
-final class IdentifierFilter<R extends Feature> extends FilterNode<R> implements ResourceId<R> {
+final class IdentifierFilter extends FilterNode<Feature> implements ResourceId<Feature> {
     /**
      * For cross-version compatibility.
      */
@@ -58,6 +55,14 @@ final class IdentifierFilter<R extends Feature> extends FilterNode<R> implements
         this.identifier = identifier;
     }
 
+    /**
+     * Returns the class of resources expected by this expression.
+     */
+    @Override
+    public Class<Feature> getResourceClass() {
+        return Feature.class;
+    }
+
     /**
      * Returns the identifiers of feature instances to accept.
      */
@@ -70,7 +75,7 @@ final class IdentifierFilter<R extends Feature> extends FilterNode<R> implements
      * Returns the parameters of this filter.
      */
     @Override
-    public List<Expression<R,?>> getExpressions() {
+    public List<Expression<Feature,?>> getExpressions() {
         return List.of(new LeafExpression.Literal<>(identifier));
     }
 
@@ -88,7 +93,7 @@ final class IdentifierFilter<R extends Feature> extends FilterNode<R> implements
      * is one of the identifier specified at {@code IdentifierFilter} construction time.
      */
     @Override
-    public boolean test(R object) {
+    public boolean test(final Feature object) {
         if (object == null) {
             return false;
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java
index f4cb6bbc7d..d2651378be 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java
@@ -161,6 +161,14 @@ final class LikeFilter<R> extends FilterNode<R> implements LikeOperator<R>, Opti
         return new LikeFilter<>(this, effective[0]);
     }
 
+    /**
+     * Returns the class of resources expected by this filter.
+     */
+    @Override
+    public Class<? super R> getResourceClass() {
+        return expression.getResourceClass();
+    }
+
     /**
      * Returns the children of this node for displaying purposes.
      * This is used by {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
index 4dee42760d..0bdde0b4e6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
@@ -86,6 +86,18 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
      */
     protected abstract LogicalFilter<R> createSameType(Collection<? extends Filter<R>> op);
 
+    /**
+     * Returns the class of resources expected by this filter.
+     */
+    @Override
+    public Class<? super R> getResourceClass() {
+        Class<? super R> type = Object.class;
+        for (final Filter<R> operand : operands) {
+            type = specializedClass(type, operand.getResourceClass());
+        }
+        return type;
+    }
+
     /**
      * Returns a list containing all of the child filters of this object.
      */
@@ -230,6 +242,11 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
             return LogicalOperatorName.NOT;
         }
 
+        /** Returns the class of resources expected by this filter. */
+        @Override public Class<? super R> getResourceClass() {
+            return operand.getResourceClass();
+        }
+
         /** Symbol of the operation. */
         @Override protected char symbol() {
             return '¬';
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
index d13ef64099..2f1ca09039 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
@@ -233,8 +233,8 @@ public class Optimization {
 
         /**
          * Returns the {@code AND} logical operation between this filter and the given predicate.
-         * If the given predicate is an instance of {@link Filter}, then the returned predicate
-         * is an instance of {@code Optimization.OnFilter}.
+         * If the given predicate is an instance of {@code Filter<R>}, then the returned predicate
+         * is an instance of {@code Optimization.OnFilter<R>}.
          *
          * @param  other  the other predicate.
          * @return the {@code AND} logical operation between this filter and the given predicate.
@@ -253,8 +253,8 @@ public class Optimization {
 
         /**
          * Returns the {@code OR} logical operation between this filter and the given predicate.
-         * If the given predicate is an instance of {@link Filter}, then the returned predicate
-         * is an instance of {@code Optimization.OnFilter}.
+         * If the given predicate is an instance of {@code Filter<R>}, then the returned predicate
+         * is an instance of {@code Optimization.OnFilter<R>}.
          *
          * @param  other  the other predicate.
          * @return the {@code OR} logical operation between this filter and the given predicate.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java b/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java
index 20d281e95e..cae63556d3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java
@@ -46,7 +46,7 @@ import org.opengis.filter.ValueReference;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  *
  * @param  <V>  the type of value computed by the expression.
  *
@@ -76,7 +76,7 @@ abstract class PropertyValue<V> extends LeafExpression<Feature,V>
     protected final boolean isVirtual;
 
     /**
-     * The prefix in a x-path for considering a property as virual.
+     * The prefix in a x-path for considering a property as virtual.
      */
     static final String VIRTUAL_PREFIX = "/*/";
 
@@ -133,6 +133,14 @@ split:  if (path != null) {
         return (path == null || path.isEmpty()) ? tip : new AssociationValue<>(path, tip);
     }
 
+    /**
+     * Returns the class of resources expected by this expression.
+     */
+    @Override
+    public final Class<Feature> getResourceClass() {
+        return Feature.class;
+    }
+
     /**
      * For {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
index 4cc5327b6c..cff832db14 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
@@ -65,6 +65,16 @@ class UnaryFunction<R,V> extends Node {
         this.expression = expression;
     }
 
+    /**
+     * Returns the class of resources expected by this filter.
+     * Defined for {@link Filter#getResourceClass()} implementations.
+     *
+     * @return type of resources accepted by this filter, or {@code null} if inconsistent.
+     */
+    public final Class<? super R> getResourceClass() {
+        return expression.getResourceClass();
+    }
+
     /**
      * Returns the expression used as parameter by this function.
      * Defined for {@link Expression#getParameters()} implementations.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ObservableImage.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ObservableImage.java
index 04942d510a..07279f0ef8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ObservableImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ObservableImage.java
@@ -36,7 +36,7 @@ import org.apache.sis.util.ArraysExt;
  * <p>This class should be used in preference to {@link BufferedImage} when the image may be the
  * source of {@link org.apache.sis.image.ImageProcessor} operations. It is the case In particular
  * when this image is given to {@link org.apache.sis.coverage.grid.GridCoverage2D} constructor.
- * We can not prevent {@link BufferedImage} to implement {@link WritableRenderedImage}, but we
+ * We cannot prevent {@link BufferedImage} to implement {@link WritableRenderedImage}, but we
  * can give a change to Apache SIS to be notified about modifications to pixel data.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
index edb9f26946..a9bcf55dc4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
@@ -106,6 +106,14 @@ final class GeometryConverter<R,G> extends Node implements Optimization.OnExpres
         return NAME;
     }
 
+    /**
+     * Returns the class of resources expected by this expression.
+     */
+    @Override
+    public Class<? super R> getResourceClass() {
+        return expression.getResourceClass();
+    }
+
     /**
      * Returns the expression used as parameters for this function.
      * This is the value specified at construction time.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java
index f48299a752..581588efb3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java
@@ -19,10 +19,10 @@ package org.apache.sis.internal.filter;
 import java.util.Map;
 import java.util.IdentityHashMap;
 import java.util.Collection;
-import java.io.Serializable;
 import java.util.Collections;
 import java.util.function.Predicate;
 import java.util.logging.Logger;
+import java.io.Serializable;
 import org.opengis.util.CodeList;
 import org.opengis.util.LocalName;
 import org.opengis.util.ScopedName;
@@ -96,6 +96,26 @@ public abstract class Node implements Serializable {
                                           type, 1, 1, null, (AttributeType<?>[]) null);
     }
 
+    /**
+     * Returns the most specialized class of the given pair of class. A specialized class is guaranteed to exist
+     * if parametrized type safety has not been bypassed with unchecked casts, because {@code <R>} is always valid.
+     * However this method is not guaranteed to be able to find that specialized type, because it could be none of
+     * the given arguments if {@code t1}, {@code t2} and {@code <R>} are interfaces with {@code <R>} extending both
+     * {@code t1} and {@code t2}.
+     *
+     * @param  <R>  the compile-time type of resources expected by filters or expressions.
+     * @param  t1   the runtime type of resources expected by the first filter or expression. May be null.
+     * @param  t2   the runtime type of resources expected by the second filter or expression. May be null.
+     * @return the most specialized type of resources, or {@code null} if it cannot be determined.
+     */
+    protected static <R> Class<? super R> specializedClass(final Class<? super R> t1, final Class<? super R> t2) {
+        if (t1 != null && t2 != null) {
+            if (t1.isAssignableFrom(t2)) return t2;
+            if (t2.isAssignableFrom(t1)) return t1;
+        }
+        return null;
+    }
+
     /**
      * Returns the mathematical symbol for this binary function.
      * For comparison operators, the symbol should be one of {@literal < > ≤ ≥ = ≠}.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java
index daa6f702d8..b2640be641 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java
@@ -206,6 +206,15 @@ search: if (crs instanceof CoordinateReferenceSystem) {
         }
     }
 
+    /**
+     * Returns the class of resources expected by this expression.
+     * Subclasses should override this method.
+     */
+    @Override
+    public Class<? super R> getResourceClass() {
+        return (srid != null) ? srid.getResourceClass() : Object.class;
+    }
+
     /**
      * Provides the type of values produced by this expression when a feature of the given type is evaluated.
      *
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java
index a687d91527..bebef3918e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java
@@ -77,6 +77,14 @@ class GeometryConstructor<R,G> extends FunctionWithSRID<R> {
         return new GeometryConstructor<>(operation, effective, getGeometryLibrary());
     }
 
+    /**
+     * Returns the class of resources expected by this expression.
+     */
+    @Override
+    public Class<? super R> getResourceClass() {
+        return specializedClass(geometry.getResourceClass(), super.getResourceClass());
+    }
+
     /**
      * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java
index b1c7432548..2ce129b57d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java
@@ -26,7 +26,7 @@ import org.opengis.filter.Expression;
 
 /**
  * SQLMM spatial functions taking a single geometry operand.
- * This base class assume that the geometry is the only parameter.
+ * This base class assumes that the geometry is the only parameter.
  * Subclasses may add other kind of parameters.
  *
  * @author  Johann Sorel (Geomatys)
@@ -75,6 +75,14 @@ class OneGeometry<R,G> extends SpatialFunction<R> {
         return getGeometryLibrary(geometry);
     }
 
+    /**
+     * Returns the class of resources expected by this expression.
+     */
+    @Override
+    public Class<? super R> getResourceClass() {
+        return geometry.getResourceClass();
+    }
+
     /**
      * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
      */
@@ -127,6 +135,14 @@ class OneGeometry<R,G> extends SpatialFunction<R> {
             return new WithArgument<>(operation, effective, getGeometryLibrary());
         }
 
+        /**
+         * Returns the class of resources expected by this expression.
+         */
+        @Override
+        public Class<? super R> getResourceClass() {
+            return specializedClass(super.getResourceClass(), argument.getResourceClass());
+        }
+
         /**
          * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
          */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java
index 19d0e6ca91..eeb2809a65 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java
@@ -98,6 +98,18 @@ final class ST_Point<R,G> extends FunctionWithSRID<R> {
         return library;
     }
 
+    /**
+     * Returns the class of resources expected by this expression.
+     */
+    @Override
+    public Class<? super R> getResourceClass() {
+        Class<? super R> type = super.getResourceClass();
+        for (final Expression<R,?> p : parameters) {
+            type = specializedClass(type, p.getResourceClass());
+        }
+        return type;
+    }
+
     /**
      * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java
index 463b1e2660..887ae43dc4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java
@@ -93,6 +93,14 @@ final class ST_Transform<R,G> extends FunctionWithSRID<R> {
         return new ST_Transform<>(effective, getGeometryLibrary());
     }
 
+    /**
+     * Returns the class of resources expected by this expression.
+     */
+    @Override
+    public Class<? super R> getResourceClass() {
+        return specializedClass(geometry.getResourceClass(), super.getResourceClass());
+    }
+
     /**
      * Returns a handler for the library of geometric objects used by this expression.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java
index e394a62f31..ad06f76fa8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java
@@ -115,6 +115,14 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
         return getGeometryLibrary(geometry1);
     }
 
+    /**
+     * Returns the class of resources expected by this expression.
+     */
+    @Override
+    public Class<? super R> getResourceClass() {
+        return specializedClass(geometry1.getResourceClass(), geometry2.getResourceClass());
+    }
+
     /**
      * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
      */
@@ -172,6 +180,14 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
             return new WithArgument<>(operation, effective, getGeometryLibrary());
         }
 
+        /**
+         * Returns the class of resources expected by this expression.
+         */
+        @Override
+        public Class<? super R> getResourceClass() {
+            return specializedClass(super.getResourceClass(), argument.getResourceClass());
+        }
+
         /**
          * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
          */
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/IdentifierFilterTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/IdentifierFilterTest.java
index 10d75b2326..d005888119 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/IdentifierFilterTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/IdentifierFilterTest.java
@@ -35,7 +35,7 @@ import org.opengis.filter.FilterFactory;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 public final class IdentifierFilterTest extends TestCase {
@@ -81,6 +81,7 @@ public final class IdentifierFilterTest extends TestCase {
         final Feature f3 = ftb.clear().setName("Test 3").build().newInstance();
 
         final Filter<Feature> id = factory.resourceId("123");
+        assertEquals(Feature.class, id.getResourceClass());
         assertTrue (id.test(f1));
         assertTrue (id.test(f2));
         assertFalse(id.test(f3));
@@ -103,6 +104,7 @@ public final class IdentifierFilterTest extends TestCase {
                 factory.resourceId("abc"),
                 factory.resourceId("123"));
 
+        assertEquals(Feature.class, id.getResourceClass());
         assertTrue (id.test(f1));
         assertTrue (id.test(f2));
         assertFalse(id.test(f3));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java
index 1444efaf01..c1b80108a7 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java
@@ -33,7 +33,7 @@ import org.opengis.filter.ValueReference;
  * Tests {@link LeafExpression}.
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 public final class LeafExpressionTest extends TestCase {
@@ -93,6 +93,7 @@ public final class LeafExpressionTest extends TestCase {
         final Feature f = ftb.setName("Test").build().newInstance();
 
         ValueReference<Feature,?> ref = factory.property("some_property");
+        assertEquals(Feature.class, ref.getResourceClass());
         assertNull(ref.apply(f));
         assertNull(ref.apply(null));
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionMock.java b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionMock.java
index dc25a40a7c..5faece4129 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionMock.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionMock.java
@@ -64,6 +64,14 @@ final class FunctionMock implements Expression<Map<String,?>, Object> {
         return Names.createScopedName(null, null, name);
     }
 
+    /**
+     * Returns the type of resources accepted by this mock.
+     */
+    @Override
+    public Class<Map> getResourceClass() {
+        return Map.class;
+    }
+
     /**
      * Returns the function parameters.
      */
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionNamesTest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionNamesTest.java
index f3ee5e5c43..3ec880cfcd 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionNamesTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionNamesTest.java
@@ -60,6 +60,7 @@ public final class FunctionNamesTest extends TestCase {
      */
     private static abstract class FilterBase implements ComparisonOperator<Object> {
         @Override public List<Expression<Object,?>> getExpressions() {return List.of();}
+        @Override public Class<Object> getResourceClass() {return Object.class;}
         @Override public boolean test(Object resource) {return false;}
     }
 
@@ -121,6 +122,7 @@ public final class FunctionNamesTest extends TestCase {
         final var expression = new ValueReference<Object,Object>() {
             @Override public String getXPath()      {return null;}
             @Override public Object apply(Object o) {return null;}
+            @Override public Class<Object> getResourceClass() {return Object.class;}
             @Override public <N> Expression<Object,N> toValueType(Class<N> target) {
                 throw new UnsupportedOperationException();
             }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/ValueReferenceMock.java b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/ValueReferenceMock.java
index 90eb9a1f75..0e56410095 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/ValueReferenceMock.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/ValueReferenceMock.java
@@ -56,6 +56,14 @@ final class ValueReferenceMock<V> implements ValueReference<Map<String,?>, V> {
         this.type  = type;
     }
 
+    /**
+     * Returns the type of resources accepted by this mock.
+     */
+    @Override
+    public Class<Map> getResourceClass() {
+        return Map.class;
+    }
+
     /**
      * Returns the name of the property for which to get values.
      */