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 28334378d5da45e67777d03ec785bad409e6eb46
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Jan 26 16:34:21 2026 +0100

    Convert literal values in advance when possible.
---
 .../org/apache/sis/filter/ComparisonFilter.java    |  11 ++
 .../main/org/apache/sis/filter/Optimization.java   | 116 +++++++++++++++------
 .../main/org/apache/sis/filter/base/Node.java      |  10 ++
 3 files changed, 108 insertions(+), 29 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
index db3a7cf787..4025972525 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
@@ -154,6 +154,17 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
         return false;
     }
 
+    /**
+     * Whether to convert literals to the same type as non-literal parameters 
during the optimization phase.
+     * This is invoked by {@link Optimization} for deciding whether to attempt 
such replacement.
+     *
+     * @return whether it is okay to convert literals in advance.
+     */
+    @Override
+    public final boolean allowLiteralConversions() {
+        return true;
+    }
+
     /**
      * Tries to optimize this filter. Fist, this method applies the 
optimization documented
      * in the {@linkplain Optimization.OnFilter#optimize default method 
impmementation}.
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
index ad8f2c6fc1..8705943a6d 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
@@ -32,10 +32,14 @@ import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.BiPredicate;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.feature.internal.shared.AttributeConvention;
 import org.apache.sis.feature.Features;
+import org.apache.sis.feature.internal.shared.FeatureExpression;
+import org.apache.sis.feature.internal.shared.AttributeConvention;
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.math.FunctionProperty;
+import org.apache.sis.util.Classes;
+import org.apache.sis.util.ObjectConverters;
+import org.apache.sis.util.UnconvertibleObjectException;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.logging.Logging;
@@ -397,6 +401,54 @@ public class Optimization {
         return false;
     }
 
+    /**
+     * Returns whether all expressions are literal. In such case, the 
expression can be evaluated immediately.
+     *
+     * @param  effective  the expressions optimized by {@link OnFilter} or 
{@link OnExpression}.
+     * @return whether the given expressions contain only literals.
+     */
+    private static boolean isImmediate(final Expression<?,?>[] effective) {
+        for (int i = effective.length; --i >= 0;) {
+            if (!(effective[i] instanceof Literal<?,?>)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Converts all literal to the same class as result of non-literal 
parameters.
+     * This is useful for example in {@link ComparisonFilter} for avoiding to 
perform
+     * the same conversion of literal value each time that the filter is 
executed.
+     *
+     * @param  effective  the expressions optimized by {@link OnFilter} or 
{@link OnExpression}.
+     * @return whether at less one literal has been converted.
+     */
+    private boolean convertLiterals(final Expression<?,?>[] effective) {
+        Class<?> type = null;
+        for (final Expression<?,?> e : effective) {
+            if (e instanceof FeatureExpression<?,?> && !(e instanceof 
Literal<?,?>)) {
+                type = Classes.findCommonClass(type, ((FeatureExpression<?,?>) 
e).getResultClass());
+            }
+        }
+        boolean changed = false;
+        if (type != null && type != Object.class) {
+            for (int i = effective.length; --i >= 0;) {
+                Expression<?,?> e = effective[i];
+                if (e instanceof Literal<?,?>) try {
+                    Object value = ((Literal<?,?>) e).getValue();
+                    if (value != (value = ObjectConverters.convert(value, 
type))) {
+                        effective[i] = literal(value);
+                        changed = true;
+                    }
+                } catch (UnconvertibleObjectException ex) {
+                    warning(ex, false);
+                }
+            }
+        }
+        return changed;
+    }
+
     /**
      * Returns whether a call to {@code apply(…)} is the first of possibly 
recursive calls.
      * This method shall be invoked in all {@code apply(…)} methods before to 
do the optimization.
@@ -506,24 +558,27 @@ public class Optimization {
          * @return the simplified or optimized filter, or {@code this} if no 
optimization has been applied.
          */
         default Filter<R> optimize(final Optimization optimization) {
-            final List<Expression<R,?>> expressions = getExpressions();
             @SuppressWarnings({"unchecked", "rawtypes"})
-            final Expression<R,?>[] effective = new 
Expression[expressions.size()];
-            boolean unchanged = true;       // Will be `false` if at least one 
optimization has been applied.
-            boolean immediate = true;
+            final Expression<R,?>[] effective = 
getExpressions().toArray(Expression[]::new);
+            Filter<R> optimized = this;
+            boolean changed = false;
             for (int i=0; i<effective.length; i++) {
-                Expression<R,?> e = expressions.get(i);
-                unchanged &= (e == (e = optimization.apply(e)));
-                immediate &= (e instanceof Literal<?,?>);
-                effective[i] = e;
+                Expression<R,?> e = effective[i];
+                if (e != (e = optimization.apply(e))) {
+                    effective[i] = e;
+                    changed = true;
+                }
             }
-            if (immediate) {
-                return test(null) ? Filter.include() : Filter.exclude();
-            } else if (unchanged) {
-                return this;
-            } else {
-                return recreate(effective);
+            if (this instanceof Node && ((Node) 
this).allowLiteralConversions()) {
+                changed |= optimization.convertLiterals(effective);
+            }
+            if (changed) {
+                optimized = recreate(effective);
             }
+            if (isImmediate(effective)) {
+                return optimized.test(null) ? Filter.include() : 
Filter.exclude();
+            }
+            return optimized;
         }
 
         /**
@@ -681,24 +736,27 @@ public class Optimization {
          * @return the simplified or optimized expression, or {@code this} if 
no optimization has been applied.
          */
         default Expression<R, ? extends V> optimize(final Optimization 
optimization) {
-            final List<Expression<R,?>> parameters = getParameters();
             @SuppressWarnings({"unchecked", "rawtypes"})
-            final Expression<R,?>[] effective = new 
Expression[parameters.size()];
-            boolean unchanged = true;       // Will be `false` if at least one 
optimization has been applied.
-            boolean immediate = true;
+            final Expression<R,?>[] effective = 
getParameters().toArray(Expression[]::new);
+            Expression<R, ? extends V> optimized = this;
+            boolean changed = false;
             for (int i=0; i<effective.length; i++) {
-                Expression<R,?> e = parameters.get(i);
-                unchanged &= (e == (e = optimization.apply(e)));
-                immediate &= (e instanceof Literal<?,?>);
-                effective[i] = e;
+                Expression<R,?> e = effective[i];
+                if (e != (e = optimization.apply(e))) {
+                    effective[i] = e;
+                    changed = true;
+                }
             }
-            if (immediate && 
!properties(this).contains(FunctionProperty.VOLATILE)) {
-                return literal(apply(null));
-            } else if (unchanged) {
-                return this;
-            } else {
-                return recreate(effective);
+            if (this instanceof Node && ((Node) 
this).allowLiteralConversions()) {
+                changed |= optimization.convertLiterals(effective);
+            }
+            if (changed) {
+                optimized = recreate(effective);
+            }
+            if (isImmediate(effective) && 
!properties(this).contains(FunctionProperty.VOLATILE)) {
+                return literal(optimized.apply(null));
             }
+            return optimized;
         }
 
         /**
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java
index 62e7c65fd8..77de191fd5 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java
@@ -315,6 +315,16 @@ public abstract class Node implements Serializable {
         return isVolatile(operands) ? TRANSITIVE_PROPERTIES : Set.of();
     }
 
+    /**
+     * Whether to convert literals to the same type as non-literal parameters 
during the optimization phase.
+     * This is invoked by {@link Optimization} for deciding whether to attempt 
such replacement.
+     *
+     * @return whether it is okay to convert literals in advance.
+     */
+    public boolean allowLiteralConversions() {
+        return false;
+    }
+
     /**
      * Returns the children of this node, or an empty collection if none. This 
is used
      * for information purpose, for example in order to build a string 
representation.

Reply via email to