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 d94bbdd49113b9c889542a7eab63cd318521430a
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Aug 27 17:14:32 2021 +0200

    Add an `Optimization.setFeatureType(…)` method and update some 
filter/expression implementations for taking advantage of it.
---
 .../apache/sis/filter/BinaryGeometryFilter.java    |  51 +++++++--
 .../java/org/apache/sis/filter/LeafExpression.java |   2 +-
 .../java/org/apache/sis/filter/Optimization.java   |  60 ++++++++++-
 .../java/org/apache/sis/filter/PropertyValue.java  | 120 +++++++++++++++++++--
 .../sis/internal/filter/sqlmm/TwoGeometries.java   |  40 +++++++
 .../org/apache/sis/filter/LogicalFunctionTest.java |  35 ++++++
 .../internal/filter/sqlmm/RegistryTestCase.java    |  25 +++++
 7 files changed, 309 insertions(+), 24 deletions(-)

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 50199f0..e1afc00 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
@@ -21,10 +21,12 @@ import java.util.Arrays;
 import javax.measure.Unit;
 import javax.measure.IncommensurableException;
 import org.opengis.util.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
 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.internal.feature.AttributeConvention;
 import org.apache.sis.internal.filter.Node;
 import org.apache.sis.util.ArgumentChecks;
 
@@ -32,9 +34,12 @@ import org.apache.sis.util.ArgumentChecks;
 import org.opengis.filter.Filter;
 import org.opengis.filter.Literal;
 import org.opengis.filter.Expression;
+import org.opengis.filter.ValueReference;
 import org.opengis.filter.SpatialOperator;
 import org.opengis.filter.BinarySpatialOperator;
 import org.opengis.filter.InvalidFilterValueException;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.PropertyNotFoundException;
 
 
 /**
@@ -175,17 +180,23 @@ abstract class BinaryGeometryFilter<R,G> extends Node 
implements SpatialOperator
      */
     @Override
     public final Filter<? super R> optimize(final Optimization optimization) {
-        final Expression<? super R, ?> geometry1  = unwrap(expression1);
-        final Expression<? super R, ?> geometry2  = unwrap(expression2);
-        final Expression<? super R, ?> effective1 = 
optimization.apply(geometry1);
-        final Expression<? super R, ?> effective2 = 
optimization.apply(geometry2);
-        final Literal<? super R, ?> literal;
-        final boolean immediate;                // true if the filter should 
be evaluated immediately.
-        final boolean literalIsNull;            // true if one of the literal 
value is null.
+        Expression<? super R, ?> geometry1  = unwrap(expression1);
+        Expression<? super R, ?> geometry2  = unwrap(expression2);
+        Expression<? super R, ?> effective1 = optimization.apply(geometry1);
+        Expression<? super R, ?> effective2 = optimization.apply(geometry2);
+        Expression<? super R, ?> other;     // The expression which is not 
literal.
+        Expression<? super R, GeometryWrapper<G>> wrapper;
+        Literal<? super R, ?> literal;
+        boolean immediate;                  // true if the filter should be 
evaluated immediately.
+        boolean literalIsNull;              // true if one of the literal 
value is null.
         if (effective2 instanceof Literal<?,?>) {
+            other     = effective1;
+            wrapper   = expression2;
             literal   = (Literal<? super R, ?>) effective2;
             immediate = (effective1 instanceof Literal<?,?>);
         } else if (effective1 instanceof Literal<?,?>) {
+            other     = effective2;
+            wrapper   = expression1;
             literal   = (Literal<? super R, ?>) effective1;
             immediate = false;
         } else {
@@ -197,6 +208,31 @@ abstract class BinaryGeometryFilter<R,G> extends Node 
implements SpatialOperator
             // If the literal has no value, then the filter will always 
evaluate to a negative result.
             result = negativeResult();
         } else {
+            /*
+             * If we are optimizing for a feature type, and if the other 
expression is a property value,
+             * then try to fetch the CRS of the property values. If we can 
transform the literal to that
+             * CRS, do it now in order to avoid doing this transformation for 
all feature instances.
+             */
+            final FeatureType featureType = optimization.getFeatureType();
+            if (featureType != null && other instanceof ValueReference<?,?>) 
try {
+                final CoordinateReferenceSystem targetCRS = 
AttributeConvention.getCRSCharacteristic(
+                        featureType, 
featureType.getProperty(((ValueReference<?,?>) other).getXPath()));
+                if (targetCRS != null) {
+                    final GeometryWrapper<G> geometry    = wrapper.apply(null);
+                    final GeometryWrapper<G> transformed = 
geometry.transform(targetCRS);
+                    if (geometry != transformed) {
+                        literal = Optimization.literal(transformed);
+                        if (literal == effective1) effective1 = literal;
+                        else effective2 = literal;
+                    }
+                }
+            } catch (PropertyNotFoundException | TransformException e) {
+                warning(e, true);
+            }
+            /*
+             * If one of the "effective" parameter has been modified, recreate 
a new filter.
+             * If all operands are literal, we can evaluate that filter 
immediately.
+             */
             Filter<? super R> filter = this;
             if ((effective1 != geometry1) || (effective2 != geometry2)) {
                 filter = recreate(effective1, effective2);
@@ -204,7 +240,6 @@ abstract class BinaryGeometryFilter<R,G> extends Node 
implements SpatialOperator
             if (!immediate) {
                 return filter;
             }
-            // If all operands are literal, we can evaluate the expression 
immediately.
             result = filter.test(null);
         }
         return result ? Filter.include() : Filter.exclude();
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java 
b/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
index cf21ee4..0bd07b6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
@@ -190,7 +190,7 @@ abstract class LeafExpression<R,V> extends Node implements 
FeatureExpression<R,V
          */
         @Override
         public Expression<? super R, ? extends V> optimize(final Optimization 
optimization) {
-            return new Literal<>(getValue());
+            return Optimization.literal(getValue());
         }
 
         /**
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 b582bce..e52bc88 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
@@ -32,6 +32,7 @@ import org.opengis.filter.Literal;
 import org.opengis.filter.Expression;
 import org.opengis.filter.LogicalOperator;
 import org.opengis.filter.LogicalOperatorName;
+import org.opengis.feature.FeatureType;
 
 
 /**
@@ -44,11 +45,15 @@ import org.opengis.filter.LogicalOperatorName;
  *   <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.
+ * The following options can enable some additional optimizations:
  *
- * <p>This class is <strong>not</strong> thread-safe. A new instance shall be 
created
- * for each thread applying optimizations. Example:</p>
+ * <ul>
+ *   <li>The type of the {@code Feature} instances to be filtered.</li>
+ * </ul>
+ *
+ * <h2>Usage in multi-threads context</h2>
+ * This class is <strong>not</strong> thread-safe.
+ * A new instance shall be created for each thread applying optimizations. 
Example:
  *
  * {@preformat java
  *     Filter<R> filter = ...;
@@ -81,6 +86,11 @@ public class Optimization {
     private static final Object COMPUTING = Void.TYPE;
 
     /**
+     * The type of feature instances to be filtered, or {@code null} if 
unknown.
+     */
+    private FeatureType featureType;
+
+    /**
      * Filters and expressions already optimized. Also used for avoiding 
never-ending loops.
      * The map is created when first needed.
      *
@@ -99,6 +109,29 @@ public class Optimization {
     }
 
     /**
+     * Returns the type of feature instances to be filtered, or {@code null} 
if unknown.
+     * This is the last value specified by a call to {@link 
#setFeatureType(FeatureType)}.
+     * The default value is {@code null}.
+     *
+     * @return the type of feature instances to be filtered, or {@code null} 
if unknown.
+     */
+    public FeatureType getFeatureType() {
+        return featureType;
+    }
+
+    /**
+     * Sets the type of feature instances to be filtered.
+     * If this type is known in advance, specifying it may allow to compute 
more specific
+     * {@link org.apache.sis.util.ObjectConverter}s or to apply some geometry 
reprojection
+     * in advance.
+     *
+     * @param  type  the type of feature instances to be filtered, or {@code 
null} if unknown.
+     */
+    public void setFeatureType(final FeatureType type) {
+        featureType = type;
+    }
+
+    /**
      * 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.
      *
@@ -172,6 +205,7 @@ public class Optimization {
                 Expression<? super R, ?> e = expressions.get(i);
                 unchanged &= (e == (e = optimization.apply(e)));
                 immediate &= (e instanceof Literal<?,?>);
+                effective[i] = e;
             }
             if (immediate) {
                 return test(null) ? Filter.include() : Filter.exclude();
@@ -275,9 +309,10 @@ public class Optimization {
                 Expression<? super R, ?> e = parameters.get(i);
                 unchanged &= (e == (e = optimization.apply(e)));
                 immediate &= (e instanceof Literal<?,?>);
+                effective[i] = e;
             }
             if (immediate) {
-                return new LeafExpression.Literal<>(apply(null));
+                return literal(apply(null));
             } else if (unchanged) {
                 return this;
             } else {
@@ -387,4 +422,19 @@ public class Optimization {
         }
         throw new IllegalArgumentException();
     }
+
+    /**
+     * Creates a constant, literal value that can be used in expressions.
+     * This is a helper methods for optimizations that simplified an 
expression to a constant value.
+     *
+     * @param  <R>    the type of resources used as inputs.
+     * @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.
+     *
+     * @see DefaultFilterFactory#literal(Object)
+     */
+    public static <R,V> Literal<R,V> literal(final V value) {
+        return new LeafExpression.Literal<>(value);
+    }
 }
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 9da399d..594999a 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
@@ -19,6 +19,7 @@ package org.apache.sis.filter;
 import java.util.Collection;
 import java.util.Collections;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ObjectConverter;
 import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.UnconvertibleObjectException;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
@@ -29,6 +30,7 @@ import org.apache.sis.feature.builder.AttributeTypeBuilder;
 import org.opengis.feature.Feature;
 import org.opengis.feature.FeatureType;
 import org.opengis.feature.PropertyType;
+import org.opengis.feature.AttributeType;
 import org.opengis.feature.IdentifiedType;
 import org.opengis.feature.Operation;
 import org.opengis.feature.PropertyNotFoundException;
@@ -38,7 +40,7 @@ import org.opengis.filter.ValueReference;
 
 /**
  * Expression whose value is computed by retrieving the value indicated by the 
provided name.
- * A property name does not store any value; it acts as an indirection to a 
property value of
+ * This expression does not store any value; it acts as an indirection to a 
property value of
  * the evaluated feature.
  *
  * @author  Johann Sorel (Geomatys)
@@ -83,7 +85,7 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V> implements Val
         if (type == Object.class) {
             return (PropertyValue<V>) new AsObject(name);
         } else {
-            return new Typed<>(type, name);
+            return new Converted<>(type, name);
         }
     }
 
@@ -104,14 +106,32 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V> implements Val
     }
 
     /**
+     * Returns the type of values fetched from {@link Feature} instance.
+     * This is the type before conversion to the {@linkplain #getValueClass() 
target type}.
+     */
+    protected Class<?> getSourceClass() {
+        return Object.class;
+    }
+
+    /**
      * Returns an expression that provides values as instances of the 
specified class.
      */
     @Override
     @SuppressWarnings("unchecked")
     public final <N> Expression<Feature,N> toValueType(final Class<N> type) {
-        return type.isAssignableFrom(getValueClass()) ? (PropertyValue<N>) 
this : create(name, type);
+        if (type.isAssignableFrom(getValueClass())) {
+            return (PropertyValue<N>) this;
+        }
+        final Class<?> source = getSourceClass();
+        if (source != Object.class) {
+            return new CastedAndConverted<>(source, type, name);
+        }
+        return create(name, type);
     }
 
+
+
+
     /**
      * An expression fetching property values as {@code Object}.
      * This expression does not need to apply any type conversion.
@@ -143,15 +163,19 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V> implements Val
         }
     }
 
+
+
+
     /**
      * An expression fetching property values as an object of specified type.
+     * The value is converted from {@link Object} to the specified type.
      */
-    private static final class Typed<V> extends PropertyValue<V> {
+    private static class Converted<V> extends PropertyValue<V> implements 
Optimization.OnExpression<Feature,V> {
         /** For cross-version compatibility. */
         private static final long serialVersionUID = -1436865010478207066L;
 
         /** The desired type of values. */
-        private final Class<V> type;
+        protected final Class<V> type;
 
         /**
          * Creates a new expression retrieving values from a property of the 
given name.
@@ -159,13 +183,16 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V> implements Val
          * @param  type  the desired type for the expression result.
          * @param  name  the name of the property to fetch.
          */
-        Typed(final Class<V> type, final String name) {
+        protected Converted(final Class<V> type, final String name) {
             super(name);
             this.type = type;
         }
 
-        /** Returns the type of values computed by this expression. */
-        @Override public Class<V> getValueClass() {
+        /**
+         * Returns the type of values computed by this expression.
+         */
+        @Override
+        public final Class<V> getValueClass() {
             return type;
         }
 
@@ -187,10 +214,33 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V> implements Val
         }
 
         /**
-         * Provides the expected type of values produced by this expression 
when a feature of the given type is evaluated.
+         * Tries to optimize this expression. If an {@link ObjectConverter} 
can be determined in advance
+         * for the {@linkplain Optimization#getFeatureType() feature type for 
which to optimize},
+         * then a specialized expression is returned. Otherwise this method 
returns {@code this}.
+         */
+        @Override
+        public final Expression<Feature, ? extends V> optimize(final 
Optimization optimization) {
+            final FeatureType featureType = optimization.getFeatureType();
+            if (featureType != null) try {
+                final PropertyType property = featureType.getProperty(name);
+                if (property instanceof AttributeType<?>) {
+                    final Class<?> source = ((AttributeType<?>) 
property).getValueClass();
+                    if (source != null && source != Object.class && 
!source.isAssignableFrom(getSourceClass())) {
+                        return new CastedAndConverted<>(source, type, name);
+                    }
+                }
+            } catch (PropertyNotFoundException e) {
+                warning(e, true);
+            }
+            return this;
+        }
+
+        /**
+         * Provides the expected type of values produced by this expression
+         * when a feature of the given type is evaluated.
          */
         @Override
-        public PropertyTypeBuilder expectedType(final FeatureType valueType, 
final FeatureTypeBuilder addTo) {
+        public final PropertyTypeBuilder expectedType(final FeatureType 
valueType, final FeatureTypeBuilder addTo) {
             final PropertyTypeBuilder p = super.expectedType(valueType, addTo);
             if (p instanceof AttributeTypeBuilder<?>) {
                 final AttributeTypeBuilder<?> a = (AttributeTypeBuilder<?>) p;
@@ -225,4 +275,54 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V> implements Val
         }
         return addTo.addProperty(type);
     }
+
+
+
+
+    /**
+     * An expression fetching property values as an object of specified type.
+     * The value is first casted from {@link Object} to the expected source 
type,
+     * then converted to the specified target type.
+     */
+    private static final class CastedAndConverted<S,V> extends Converted<V> {
+        /** For cross-version compatibility. */
+        private static final long serialVersionUID = -58453954752151703L;
+
+        /** The source type before conversion. */
+        private final Class<S> source;
+
+        /** The conversion from source type to the type to be returned. */
+        private final ObjectConverter<? super S, ? extends V> converter;
+
+        /** Creates a new expression retrieving values from a property of the 
given name. */
+        CastedAndConverted(final Class<S> source, final Class<V> type, final 
String name) {
+            super(type, name);
+            this.source = source;
+            converter = ObjectConverters.find(source, type);
+        }
+
+        /**
+         * Returns the type of values fetched from {@link Feature} instance.
+         */
+        @Override
+        protected Class<S> getSourceClass() {
+            return source;
+        }
+
+        /**
+         * Returns the value of the property of the given name.
+         * If no value is found for the given feature, then this method 
returns {@code null}.
+         */
+        @Override
+        public V apply(final Feature instance) {
+            if (instance != null) try {
+                return 
converter.apply(source.cast(instance.getPropertyValue(name)));
+            } catch (PropertyNotFoundException e) {
+                warning(e, true);
+            } catch (ClassCastException | UnconvertibleObjectException e) {
+                warning(e, false);
+            }
+            return null;
+        }
+    }
 }
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 2bdc3a9..69efa59 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
@@ -18,12 +18,19 @@ package org.apache.sis.internal.filter.sqlmm;
 
 import java.util.List;
 import java.util.Arrays;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.filter.Optimization;
+import org.apache.sis.internal.feature.AttributeConvention;
 import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.internal.feature.GeometryWrapper;
 
 // Branch-dependent imports
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.PropertyNotFoundException;
 import org.opengis.filter.Expression;
+import org.opengis.filter.Literal;
+import org.opengis.filter.ValueReference;
 
 
 /**
@@ -69,6 +76,39 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
     }
 
     /**
+     * If the CRS of the first argument is known in advance and the second 
argument is a literal,
+     * transforms the second geometry to the CRS of the first argument. The 
transformed geometry
+     * is always the second argument because according SQLMM specification, 
operations shall be
+     * executed in the CRS of the first argument.
+     */
+    @Override
+    public Expression<? super R, ?> optimize(final Optimization optimization) {
+        final FeatureType featureType = optimization.getFeatureType();
+        if (featureType != null) {
+            final Expression<? super R, ?> p1 = unwrap(geometry1);
+            if (p1 instanceof ValueReference<?,?> && unwrap(geometry2) 
instanceof Literal<?,?>) try {
+                final CoordinateReferenceSystem targetCRS = 
AttributeConvention.getCRSCharacteristic(
+                        featureType, 
featureType.getProperty(((ValueReference<?,?>) p1).getXPath()));
+                if (targetCRS != null) {
+                    final GeometryWrapper<G> literal = geometry2.apply(null);
+                    if (literal != null) {
+                        final GeometryWrapper<G> tr = 
literal.transform(targetCRS);
+                        if (tr != literal) {
+                            @SuppressWarnings({"unchecked","rawtypes"})
+                            final Expression<? super R, ?>[] effective = 
getParameters().toArray(new Expression[0]);  // TODO: use generator in JDK9.
+                            effective[1] = Optimization.literal(tr);
+                            return recreate(effective);
+                        }
+                    }
+                }
+            } catch (PropertyNotFoundException | TransformException e) {
+                warning(e, true);
+            }
+        }
+        return super.optimize(optimization);
+    }
+
+    /**
      * Returns a handler for the library of geometric objects used by this 
expression.
      */
     @Override
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFunctionTest.java 
b/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFunctionTest.java
index e0ffeca..1e327a5 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFunctionTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFunctionTest.java
@@ -29,6 +29,8 @@ import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
 import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.filter.Expression;
 import org.opengis.filter.Filter;
 import org.opengis.filter.Literal;
 import org.opengis.filter.FilterFactory;
@@ -39,6 +41,7 @@ import org.opengis.filter.LogicalOperator;
  * Tests {@link LogicalFunction} implementations.
  *
  * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  * @since   1.1
  * @module
@@ -184,4 +187,36 @@ public final strictfp class LogicalFunctionTest extends 
TestCase {
         assertSame("Second optimization should have no effect.", optimized, 
new Optimization().apply(optimized));
         assertSame("Expression should have been evaluated now.", expected, 
optimized);
     }
+
+    /**
+     * Tests {@link Optimization} applied on logical filters when the {@link 
FeatureType} is known.
+     */
+    @Test
+    public void testFeatureOptimization() {
+        final String attribute = "population";
+        final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
+        ftb.addAttribute(String.class).setName(attribute);
+        final FeatureType type = ftb.setName("Test").build();
+        final Feature instance = type.newInstance();
+        instance.setPropertyValue("population", "1000");
+        /*
+         * Prepare an expression which divide the population value by 5.
+         */
+        final Expression<Feature,Number> e = 
factory.divide(factory.property(attribute, Integer.class), factory.literal(5));
+        final Optimization optimization = new Optimization();
+        assertSame(e, optimization.apply(e));                       // No 
optimization.
+        assertEquals(200, e.apply(instance).intValue());
+        /*
+         * Notify the optimizer that property values will be of `String` type.
+         * The optimizer should compute an `ObjectConverter` in advance.
+         */
+        optimization.setFeatureType(type);
+        final Expression<? super Feature, ? extends Number> opt = 
optimization.apply(e);
+        assertEquals(200, e.apply(instance).intValue());
+        assertNotSame(e, opt);
+
+        final PropertyValue<?> p = (PropertyValue<?>) 
opt.getParameters().get(0);
+        assertEquals(String.class,  p.getSourceClass());
+        assertEquals(Integer.class, p.getValueClass());
+    }
 }
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java
 
b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java
index 3877e4f..e6eddba 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.filter.sqlmm;
 
 import java.util.Arrays;
+import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
@@ -463,6 +464,30 @@ public abstract strictfp class RegistryTestCase<G> extends 
TestCase {
     }
 
     /**
+     * Tests {@link Optimization} on an arbitrary expression on feature 
instances.
+     */
+    @Test
+    public void testFeatureOptimization() {
+        geometry = library.createPoint(10, 30);
+        setGeometryCRS(HardCodedCRS.WGS84_LATITUDE_FIRST);
+        function = factory.function("ST_Union", factory.property(P_NAME), 
factory.literal(geometry));
+
+        final Optimization optimization = new Optimization();
+        final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
+        
ftb.addAttribute(library.pointClass).setName(P_NAME).setCRS(HardCodedCRS.WGS84);
+        optimization.setFeatureType(ftb.setName("Test").build());
+        final Expression<? super Feature, ?> optimized = 
optimization.apply(function);
+        assertNotSame("Optimization should produce a new expression.", 
function, optimized);
+        /*
+         * Get the second parameter, which should be a literal, and get the 
point coordinates.
+         * Verify that the order is swapped compared to the order at the 
beginning of this method.
+         */
+        final Object literal = optimized.getParameters().get(1).apply(null);
+        final DirectPosition point = library.castOrWrap(literal).getCentroid();
+        assertArrayEquals(new double[] {30, 10}, point.getCoordinate(), 
STRICT);
+    }
+
+    /**
      * Executed after each test for verifying that no unexpected log message 
has been emitted.
      */
     @After

Reply via email to