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 <martin.desruisse...@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.