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/04 22:39:16 UTC

[sis] 02/02: Provide a way to tell whether an expression is a volatile function. This is internal API for now; we have not yet determined where would be a public API.

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 d6577877ca77379c7a38d23fd87c56d0566d9210
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri May 5 00:24:08 2023 +0200

    Provide a way to tell whether an expression is a volatile function.
    This is internal API for now; we have not yet determined where would be a public API.
---
 .../org/apache/sis/filter/AssociationValue.java    |  9 +++
 .../org/apache/sis/filter/ConvertFunction.java     | 10 +++
 .../java/org/apache/sis/filter/LeafExpression.java | 18 +++++
 .../java/org/apache/sis/filter/Optimization.java   | 41 ++++++++++
 .../java/org/apache/sis/filter/UnaryFunction.java  | 11 +++
 .../sis/internal/feature/FeatureExpression.java    | 15 +++-
 .../java/org/apache/sis/internal/filter/Node.java  | 88 ++++++++++++++++++++++
 .../org/apache/sis/filter/LogicalFilterTest.java   | 26 +++++++
 .../java/org/apache/sis/math/FunctionProperty.java | 12 ++-
 9 files changed, 227 insertions(+), 3 deletions(-)

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 c8d714b8d5..3a31459b1f 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
@@ -25,6 +25,7 @@ import java.util.StringJoiner;
 import org.apache.sis.feature.Features;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
 import org.apache.sis.feature.builder.PropertyTypeBuilder;
+import org.apache.sis.math.FunctionProperty;
 
 // Branch-dependent imports
 import org.opengis.feature.Feature;
@@ -102,6 +103,14 @@ final class AssociationValue<V> extends LeafExpression<Feature, V>
         return Feature.class;
     }
 
+    /**
+     * Returns the manner in which values are computed from given resources.
+     */
+    @Override
+    public Set<FunctionProperty> properties() {
+        return properties(accessor);
+    }
+
     /**
      * For {@link #toString()} implementation.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
index 3095f4cb58..d4627844fd 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.filter;
 
+import java.util.Set;
 import java.util.List;
 import java.util.Collection;
 import org.opengis.util.ScopedName;
@@ -26,6 +27,7 @@ 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.internal.feature.FeatureExpression;
+import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
@@ -119,6 +121,14 @@ final class ConvertFunction<R,S,V> extends UnaryFunction<R,S>
         return NAME;
     }
 
+    /**
+     * Returns the manner in which values are computed from given resources.
+     */
+    @Override
+    public Set<FunctionProperty> properties() {
+        return properties(expression, converter);
+    }
+
     /**
      * Returns the singleton expression tested by this operator
      * together with the source and target classes.
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 18e6864560..1a34137d86 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
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.filter;
 
+import java.util.Set;
 import java.util.List;
 import java.util.Collection;
 import java.util.Collections;
@@ -29,6 +30,7 @@ import org.apache.sis.internal.feature.FeatureExpression;
 import org.apache.sis.internal.filter.Node;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
 import org.apache.sis.feature.builder.PropertyTypeBuilder;
+import org.apache.sis.math.FunctionProperty;
 
 // Branch-dependent imports
 import org.opengis.feature.FeatureType;
@@ -70,6 +72,14 @@ abstract class LeafExpression<R,V> extends Node implements FeatureExpression<R,V
         return List.of();
     }
 
+    /**
+     * Returns the manner in which values are computed from given resources.
+     */
+    @Override
+    public Set<FunctionProperty> properties() {
+        return Set.of();
+    }
+
 
 
 
@@ -194,6 +204,14 @@ abstract class LeafExpression<R,V> extends Node implements FeatureExpression<R,V
             this.original = original;
         }
 
+        /**
+         * Returns the manner in which values are computed from given resources.
+         */
+        @Override
+        public Set<FunctionProperty> properties() {
+            return properties(original);
+        }
+
         /**
          * Returns the same literal without the reference to the original expression.
          * Since this {@code Transformed} instance will not longer be unwrapped,
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 0e6d2f985c..ea85a11656 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
@@ -17,13 +17,16 @@
 package org.apache.sis.filter;
 
 import java.util.Map;
+import java.util.Set;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.ConcurrentModificationException;
 import java.util.function.Predicate;
 import org.opengis.util.CodeList;
+import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.filter.Node;
 import org.apache.sis.internal.util.CollectionsExt;
 
 // Branch-dependent imports
@@ -483,6 +486,44 @@ public class Optimization {
         throw new IllegalArgumentException();
     }
 
+    /**
+     * Returns the manner in which values are computed from resources given to the specified filter.
+     * This set of properties may determine which optimizations are allowed.
+     * The values of particular interest are:
+     *
+     * <ul>
+     *   <li>{@link FunctionProperty#VOLATILE} if the computed value changes each time that the filter is evaluated,
+     *       even if the resource to evaluate stay the same immutable instance.</li>
+     * </ul>
+     *
+     * @param  filter  the filter for which to query function properties.
+     * @return the manners in which values are computed from resources.
+     *
+     * @since 1.4
+     */
+    public static Set<FunctionProperty> properties(final Filter<?> filter) {
+        return Node.properties(filter.getExpressions());
+    }
+
+    /**
+     * Returns the manner in which values are computed from resources given to the specified expression.
+     * This set of properties may determine which optimizations are allowed.
+     * The values of particular interest are:
+     *
+     * <ul>
+     *   <li>{@link FunctionProperty#VOLATILE} if the computed value changes each time that the expression is evaluated,
+     *       even if the resource to evaluate stay the same immutable instance.</li>
+     * </ul>
+     *
+     * @param  expression  the expression for which to query function properties.
+     * @return the manners in which values are computed from resources.
+     *
+     * @since 1.4
+     */
+    public static Set<FunctionProperty> properties(final Expression<?,?> expression) {
+        return Node.properties(expression);
+    }
+
     /**
      * Creates a constant, literal value that can be used in expressions.
      * This is a helper methods for optimizations which simplified an expression to a constant value.
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 cff832db14..c7539db5dc 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
@@ -16,12 +16,15 @@
  */
 package org.apache.sis.filter;
 
+import java.util.Set;
 import java.util.List;
 import java.util.Collection;
 import java.util.Optional;
 import org.apache.sis.xml.NilReason;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.internal.filter.Node;
+import org.apache.sis.internal.feature.FeatureExpression;
 
 // Branch-dependent imports
 import org.opengis.filter.Filter;
@@ -102,6 +105,14 @@ class UnaryFunction<R,V> extends Node {
         return getExpressions();
     }
 
+    /**
+     * Returns the manner in which values are computed from given resources.
+     * Defined for {@link FeatureExpression#properties()} implementations.
+     */
+    public Set<FunctionProperty> properties() {
+        return properties(expression);
+    }
+
 
     /**
      * Filter operator that checks if an expression's value is {@code null}.  A {@code null}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
index 86b379a201..edb5b1f899 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
@@ -16,16 +16,19 @@
  */
 package org.apache.sis.internal.feature;
 
+import java.util.Set;
 import org.opengis.feature.FeatureType;
 import org.opengis.feature.AttributeType;
 import org.opengis.filter.Literal;
 import org.opengis.filter.Expression;
 import org.opengis.filter.ValueReference;
+import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.filter.Optimization;
 import org.apache.sis.filter.DefaultFilterFactory;
 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.internal.filter.Node;
 
 
 /**
@@ -37,7 +40,7 @@ import org.apache.sis.feature.builder.AttributeTypeBuilder;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <V>  the type of values computed by the expression.
@@ -45,6 +48,16 @@ import org.apache.sis.feature.builder.AttributeTypeBuilder;
  * @since 1.0
  */
 public interface FeatureExpression<R,V> extends Expression<R,V> {
+    /**
+     * Returns the manner in which values are computed from given resources.
+     * The default implementation combines the properties of all parameters.
+     *
+     * @return the manners in which values are computed from resources.
+     */
+    default Set<FunctionProperty> properties() {
+        return Node.properties(getParameters());
+    }
+
     /**
      * Returns the type of values computed by this expression, or {@code Object.class} if unknown.
      *
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 581588efb3..629348d66a 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
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.filter;
 
+import java.util.Set;
 import java.util.Map;
 import java.util.IdentityHashMap;
 import java.util.Collection;
@@ -26,11 +27,14 @@ import java.io.Serializable;
 import org.opengis.util.CodeList;
 import org.opengis.util.LocalName;
 import org.opengis.util.ScopedName;
+import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.feature.DefaultAttributeType;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.internal.feature.GeometryWrapper;
+import org.apache.sis.internal.feature.FeatureExpression;
 import org.apache.sis.util.iso.Names;
+import org.apache.sis.util.ObjectConverter;
 import org.apache.sis.util.collection.DefaultTreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.TreeTable;
@@ -222,6 +226,90 @@ public abstract class Node implements Serializable {
         throw new InvalidFilterValueException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
     }
 
+    /**
+     * The set of all properties that make sense for {@link FeatureExpression#properties()}.
+     * In current version, only one property makes sense and that property is combined with
+     * all other property sets using a logical {@code OR} operation. More properties may be
+     * added in the future, and the logical operation will not necessarily be always "OR".
+     */
+    private static final Set<FunctionProperty> SUPPORTED_PROPERTIES = Set.of(FunctionProperty.VOLATILE);
+
+    /**
+     * Whether the given set of properties contains the {@link #SUPPORTED_PROPERTIES} singleton value.
+     * If a future version recognizes more properties, the return type will no longer be a boolean.
+     */
+    private static boolean isVolatile(final Set<FunctionProperty> properties) {
+        return properties.contains(FunctionProperty.VOLATILE);
+    }
+
+    /**
+     * Whether the combination of the function properties of all given expression is {@link #SUPPORTED_PROPERTIES}.
+     * This method assumes that {@code SUPPORTED_PROPERTIES} is a singleton and that the property can be combined
+     * by a logical {@code OR} operation.
+     */
+    private static <R> boolean isVolatile(final Iterable<Expression<R,?>> operands) {
+        for (final Expression<R,?> operand : operands) {
+            if (operand instanceof FeatureExpression<?,?>) {
+                if (isVolatile(((FeatureExpression<?,?>) operand).properties())) {
+                    return true;    // Short-circuit for `OR` logical operation.
+                }
+            } else {
+                if (isVolatile(operand.getParameters())) {
+                    return true;    // Short-circuit for `OR` logical operation.
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the manner in which values are computed from resources.
+     *
+     * @param  operand  the expression for which to query function properties, or {@code null}.
+     * @return the manners in which values are computed from resources.
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Because immutable.
+    public static Set<FunctionProperty> properties(final Expression<?,?> operand) {
+        if (operand instanceof FeatureExpression<?,?>) {
+            return ((FeatureExpression<?,?>) operand).properties();
+        } else if (operand != null && isVolatile(operand.getParameters())) {
+            return SUPPORTED_PROPERTIES;
+        }
+        return Set.of();
+    }
+
+    /**
+     * Returns the manner in which values are computed from resources in an expression followed by a conversion.
+     *
+     * @param  operand    the expression for which to query function properties, or {@code null}.
+     * @param  converter  conversion applied on the expression results.
+     * @return combined properties.
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")             // Because immutable.
+    protected static Set<FunctionProperty> properties(final Expression<?,?> operand, final ObjectConverter<?,?> converter) {
+        if (isVolatile(properties(operand)) || isVolatile(converter.properties())) {
+            return SUPPORTED_PROPERTIES;
+        }
+        return Set.of();
+    }
+
+    /**
+     * Returns the manner in which values are computed from resources in an expression having the given operands.
+     *
+     * @param  <R>       the type of resources.
+     * @param  operands  the expressions for which to query function properties.
+     * @return the manners in which values are computed from resources.
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")             // Because immutable.
+    public static <R> Set<FunctionProperty> properties(final Iterable<Expression<R,?>> operands) {
+        for (final Expression<?,?> operand : operands) {
+            if (isVolatile(properties(operand))) {
+                return SUPPORTED_PROPERTIES;
+            }
+        }
+        return Set.of();
+    }
+
     /**
      * 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.
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java
index 5111178236..8555b04741 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java
@@ -23,6 +23,7 @@ import java.util.function.Function;
 import java.util.function.BiFunction;
 import java.util.function.Predicate;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
+import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -89,6 +90,7 @@ public final class LogicalFilterTest extends TestCase {
         assertArrayEquals(new Filter<?>[] {operand}, filter.getOperands().toArray());
         assertTrue(filter.test(null));
         assertSerializedEquals(filter);
+        assertFalse(isVolatile(filter));
     }
 
     /**
@@ -101,6 +103,21 @@ public final class LogicalFilterTest extends TestCase {
         assertInstanceOf("Predicate.negate()", LogicalFilter.Not.class, operand.negate());
     }
 
+    /**
+     * Tests a filter having a volatile expression.
+     */
+    @Test
+    public void testVolatile() {
+        final var literal = new LeafExpression.Literal<Feature,Object>("test") {
+            @Override public Set<FunctionProperty> properties() {
+                return Set.of(FunctionProperty.VOLATILE);
+            }
+        };
+        final Filter<Feature>          operand = factory.isNull(literal);
+        final LogicalOperator<Feature> filter  = factory.not(operand);
+        assertTrue(isVolatile(filter));
+    }
+
     /**
      * Implementation of {@link #testAnd()} and {@link #testOr()}.
      *
@@ -142,6 +159,7 @@ public final class LogicalFilterTest extends TestCase {
         assertArrayEquals(new Filter<?>[] {f1, f2}, filter.getOperands().toArray());
         assertEquals(expected, filter.test(null));
         assertSerializedEquals(filter);
+        assertFalse(isVolatile(filter));
         /*
          * Same test, using the constructor accepting any number of operands.
          */
@@ -149,6 +167,7 @@ public final class LogicalFilterTest extends TestCase {
         assertArrayEquals(new Filter<?>[] {f1, f2, f1}, filter.getOperands().toArray());
         assertEquals(expected, filter.test(null));
         assertSerializedEquals(filter);
+        assertFalse(isVolatile(filter));
         /*
          * Test the `Predicate` methods, which should be overridden by `Optimization.OnFilter`.
          */
@@ -250,4 +269,11 @@ public final class LogicalFilterTest extends TestCase {
         assertEquals(String.class, p.getSourceClass());
         assertEquals(Number.class, p.getValueClass());
     }
+
+    /**
+     * Returns {@code true} if the given filter is declared volatile.
+     */
+    private static boolean isVolatile(final Filter<?> filter) {
+        return Optimization.properties(filter).equals(Set.of(FunctionProperty.VOLATILE));
+    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/FunctionProperty.java b/core/sis-utility/src/main/java/org/apache/sis/math/FunctionProperty.java
index 62de9c1ada..2e72f32e9d 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/FunctionProperty.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/FunctionProperty.java
@@ -54,7 +54,7 @@ import java.util.EnumSet;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.3
+ * @version 1.4
  *
  * @see org.apache.sis.util.ObjectConverter#properties()
  *
@@ -136,7 +136,15 @@ public enum FunctionProperty {
      * @see #ORDER_PRESERVING
      * @see #isMonotonic(Set)
      */
-    ORDER_REVERSING;
+    ORDER_REVERSING,
+
+    /**
+     * A function is volatile if the computed value changes each time that the function is evaluated.
+     * It may be for example a random number generator, or a function returning the current date and time.
+     *
+     * @since 1.4
+     */
+    VOLATILE;
 
     /**
      * Bijective functions shall contain all the value in this set.