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.
