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.