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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 37c1c6ef59 Support mathematical operations in filters.
https://issues.apache.org/jira/browse/SIS-622
37c1c6ef59 is described below
commit 37c1c6ef5916fb4a323831943dde2a0c29782031
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Oct 30 00:46:44 2025 +0100
Support mathematical operations in filters.
https://issues.apache.org/jira/browse/SIS-622
---
.../apache/sis/cloud/aws/s3/ClientFileSystem.java | 5 +-
.../org.apache.sis.filter.FunctionRegister | 3 +
.../org.apache.sis.feature/main/module-info.java | 3 +
.../feature/internal/shared/FeatureExpression.java | 49 +++-
.../org/apache/sis/filter/ArithmeticFunction.java | 44 +---
.../org/apache/sis/filter/AssociationValue.java | 8 +
.../apache/sis/filter/BinaryGeometryFilter.java | 3 +-
.../org/apache/sis/filter/ComparisonFilter.java | 3 +-
.../apache/sis/filter/DefaultFilterFactory.java | 5 +-
.../org/apache/sis/filter/IdentifierFilter.java | 2 +-
.../main/org/apache/sis/filter/LeafExpression.java | 11 +-
.../main/org/apache/sis/filter/LikeFilter.java | 2 +-
.../main/org/apache/sis/filter/LogicalFilter.java | 4 +-
.../main/org/apache/sis/filter/Optimization.java | 2 +-
.../main/org/apache/sis/filter/PropertyValue.java | 18 +-
.../main/org/apache/sis/filter/TemporalFilter.java | 12 +-
.../sis/filter/{ => function}/BinaryFunction.java | 7 +-
.../sis/filter/{ => function}/ConvertFunction.java | 26 +-
.../{internal => function}/GeometryConverter.java | 4 +-
.../GeometryFromFeature.java | 2 +-
.../sis/filter/{internal => function}/Node.java | 6 +-
.../sis/filter/{ => function}/UnaryFunction.java | 16 +-
.../sis/filter/function/math/BinaryOperator.java | 115 +++++++++
.../apache/sis/filter/function/math/Function.java | 287 +++++++++++++++++++++
.../apache/sis/filter/function/math/Registry.java | 102 ++++++++
.../sis/filter/function/math/UnaryOperator.java | 109 ++++++++
.../{internal => function/math}/package-info.java | 6 +-
.../{internal => function}/package-info.java | 4 +-
.../sis/filter/internal/shared/FunctionNames.java | 2 +-
.../sis/filter/internal/shared/WarningEvent.java | 2 +-
.../sis/filter/internal/shared/package-info.java | 2 +-
.../sis/filter/sqlmm/FunctionDescription.java | 5 +-
.../apache/sis/filter/sqlmm/GeometryParser.java | 2 +-
.../main/org/apache/sis/filter/sqlmm/Registry.java | 4 +-
.../apache/sis/filter/sqlmm/SpatialFunction.java | 14 +-
.../org/apache/sis/filter/sqlmm/package-info.java | 2 +-
.../org/apache/sis/filter/LogicalFilterTest.java | 2 +-
.../sis/filter/function/math/RegistryTest.java | 62 +++++
.../apache/sis/filter/sqlmm/RegistryTestCase.java | 2 +-
.../main/org/apache/sis/util/ObjectConverters.java | 14 +-
.../org/apache/sis/util/collection/Containers.java | 35 ++-
.../apache/sis/util/collection}/DerivedList.java | 28 +-
.../apache/sis/util/collection/package-info.java | 2 +-
netbeans-project/nbproject/project.xml | 2 +
44 files changed, 907 insertions(+), 131 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.cloud.aws/main/org/apache/sis/cloud/aws/s3/ClientFileSystem.java
b/endorsed/src/org.apache.sis.cloud.aws/main/org/apache/sis/cloud/aws/s3/ClientFileSystem.java
index afb73ee54d..b8aa19d3eb 100644
---
a/endorsed/src/org.apache.sis.cloud.aws/main/org/apache/sis/cloud/aws/s3/ClientFileSystem.java
+++
b/endorsed/src/org.apache.sis.cloud.aws/main/org/apache/sis/cloud/aws/s3/ClientFileSystem.java
@@ -37,6 +37,7 @@ import software.amazon.awssdk.core.exception.SdkException;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.collection.Containers;
import org.apache.sis.util.internal.shared.Strings;
@@ -188,7 +189,7 @@ final class ClientFileSystem extends FileSystem {
*/
@Override
public Iterable<Path> getRootDirectories() {
- return new DerivedList<>(client().listBuckets().buckets(), (root) ->
new KeyPath(this, root));
+ return Containers.derivedList(client().listBuckets().buckets(), (root)
-> new KeyPath(this, root));
}
/**
@@ -200,7 +201,7 @@ final class ClientFileSystem extends FileSystem {
*/
@Override
public Iterable<FileStore> getFileStores() {
- return new DerivedList<>(client().listBuckets().buckets(),
BucketStore::new);
+ return Containers.derivedList(client().listBuckets().buckets(),
BucketStore::new);
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/META-INF/services/org.apache.sis.filter.FunctionRegister
b/endorsed/src/org.apache.sis.feature/main/META-INF/services/org.apache.sis.filter.FunctionRegister
new file mode 100644
index 0000000000..5a09fc780c
--- /dev/null
+++
b/endorsed/src/org.apache.sis.feature/main/META-INF/services/org.apache.sis.filter.FunctionRegister
@@ -0,0 +1,3 @@
+# Workaround for Maven bug https://issues.apache.org/jira/browse/MNG-7855
+# Should be used only if the JAR file was on class-path rather than
module-path.
+org.apache.sis.filter.function.math.Registry
diff --git a/endorsed/src/org.apache.sis.feature/main/module-info.java
b/endorsed/src/org.apache.sis.feature/main/module-info.java
index 4930f89a37..732957e8d4 100644
--- a/endorsed/src/org.apache.sis.feature/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/module-info.java
@@ -33,6 +33,9 @@ module org.apache.sis.feature {
uses org.apache.sis.filter.FunctionRegister;
+ provides org.apache.sis.filter.FunctionRegister
+ with org.apache.sis.filter.function.math.Registry;
+
exports org.apache.sis.image;
exports org.apache.sis.coverage;
exports org.apache.sis.coverage.grid;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureExpression.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureExpression.java
index 0c86b600f7..c88f946989 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureExpression.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureExpression.java
@@ -21,7 +21,9 @@ import org.apache.sis.math.FunctionProperty;
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.filter.Optimization;
import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.filter.internal.Node;
+import org.apache.sis.filter.function.ConvertFunction;
+import org.apache.sis.filter.function.Node;
+import org.apache.sis.util.resources.Errors;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.feature.Feature;
@@ -59,19 +61,11 @@ public interface FeatureExpression<R,V> extends
Expression<R,V> {
}
/**
- * Returns the type of values computed by this expression, or {@code
Object.class} if unknown.
- *
- * <h4>Note on type safety</h4>
- * The parameterized type should be {@code <? extends V>} because some
implementations get this
- * information by a call to {@code value.getClass()}. But it should also
be {@code <? super V>}
- * for supporting the {@code Object.class} return value. Those
contradictory requirements force
- * us to use {@code <?>}.
+ * Returns the type of values computed by this expression, or {@code null}
if unknown.
*
* @return the type of values computed by this expression.
*/
- default Class<?> getValueClass() {
- return Object.class;
- }
+ Class<? extends V> getResultClass();
/**
* Provides the expected type of values produced by this expression when a
feature of a given type is evaluated.
@@ -107,6 +101,39 @@ public interface FeatureExpression<R,V> extends
Expression<R,V> {
*/
FeatureProjectionBuilder.Item expectedType(FeatureProjectionBuilder addTo);
+ /**
+ * Returns an expression doing the same evaluation as this method, but
returning results
+ * as values of the specified type. This method can return {@code this} if
this expression
+ * is already guaranteed to provide results of the specified type.
+ *
+ * <h4>Default implementation</h4>
+ * The default implementation returns {@code this} if this expression
already provides values
+ * of the specified type, or otherwise returns an expression doing
conversions on-the-fly.
+ *
+ * @param <N> compile-time value of {@code target} type.
+ * @param target desired type of expression results.
+ * @return expression doing the same operation this this expression but
with results of the specified type.
+ * @throws ClassCastException if the specified type is not a supported
target type.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ default <N> Expression<R,N> toValueType(final Class<N> target) {
+ UnconvertibleObjectException error = null;
+ final Class<? extends V> current = getResultClass();
+ if (current != null) {
+ if (target.isAssignableFrom(current)) {
+ return (Expression<R,N>) this;
+ } else try {
+ return new ConvertFunction<>(this, current, target);
+ } catch (UnconvertibleObjectException e) {
+ error = e;
+ }
+ }
+ var e = new
ClassCastException(Errors.format(Errors.Keys.CanNotConvertValue_2,
getFunctionName(), target));
+ e.initCause(error);
+ throw e;
+ }
+
/**
* Tries to cast or convert the given expression to a {@link
FeatureExpression}.
* If the given expression cannot be cast, then this method creates a copy
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
index 0e5521269e..031b06a7ae 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
@@ -21,9 +21,8 @@ import java.math.BigInteger;
import org.opengis.util.ScopedName;
import org.apache.sis.feature.internal.shared.FeatureExpression;
import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder;
+import org.apache.sis.filter.function.BinaryFunction;
import org.apache.sis.filter.internal.shared.FunctionNames;
-import org.apache.sis.util.UnconvertibleObjectException;
-import org.apache.sis.util.resources.Errors;
import org.apache.sis.math.Fraction;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -40,8 +39,8 @@ import org.opengis.filter.Expression;
*
* @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
*/
-abstract class ArithmeticFunction<R> extends BinaryFunction<R,Number,Number>
- implements FeatureExpression<R,Number>,
Optimization.OnExpression<R,Number>
+abstract class ArithmeticFunction<R> extends BinaryFunction<R, Number, Number>
+ implements FeatureExpression<R, Number>, Optimization.OnExpression<R,
Number>
{
/**
* For cross-version compatibility.
@@ -57,6 +56,14 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R,Number,Number>
super(expression1, expression2);
}
+ /**
+ * Returns the type of values computed by this expression.
+ */
+ @Override
+ public final Class<Number> getResultClass() {
+ return Number.class;
+ }
+
/**
* Creates an attribute type for numeric values of the given name.
* The attribute is mandatory, unbounded and has no default value.
@@ -64,7 +71,7 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R,Number,Number>
* @param name name of the attribute to create.
* @return an attribute of the given name for numbers.
*/
- static AttributeType<Number> createNumericType(final String name) {
+ private static AttributeType<Number> createNumericType(final String name) {
return createType(Number.class, name);
}
@@ -73,14 +80,6 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R,Number,Number>
*/
protected abstract AttributeType<Number> expectedType();
- /**
- * Returns the type of values computed by this expression.
- */
- @Override
- public final Class<?> getValueClass() {
- return Number.class;
- }
-
/**
* Provides the type of results computed by this expression. That type
depends only
* on the {@code ArithmeticFunction} subclass and is given by {@link
#expectedType()}.
@@ -108,25 +107,6 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R,Number,Number>
return null;
}
- /**
- * Returns {@code this} if this expression provides values of the
specified type,
- * or otherwise returns an expression doing conversions on-the-fly.
- *
- * @throws ClassCastException if the specified type is not a supported
target type.
- */
- @Override
- @SuppressWarnings("unchecked")
- public <N> Expression<R,N> toValueType(final Class<N> target) {
- if (target.isAssignableFrom(Number.class)) {
- return (Expression<R,N>) this;
- } else try {
- return new ConvertFunction<>(this, Number.class, target);
- } catch (UnconvertibleObjectException e) {
- throw (ClassCastException) new ClassCastException(Errors.format(
- Errors.Keys.CanNotConvertValue_2, getFunctionName(),
target)).initCause(e);
- }
- }
-
/**
* The "Add" (+) expression.
*
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
index 2e0f126734..8db223d154 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
@@ -98,6 +98,14 @@ final class AssociationValue<V> extends
LeafExpression<Feature, V>
return Feature.class;
}
+ /**
+ * Returns the type of values computed by this expression.
+ */
+ @Override
+ public Class<? extends V> getResultClass() {
+ return accessor.getResultClass();
+ }
+
/**
* Returns the manner in which values are computed from given resources.
* This method assumes an initially empty set of properties, then adds the
transitive properties.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
index d8e1b00f43..52ae4215e2 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
@@ -24,7 +24,7 @@ import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.geometry.wrapper.GeometryWrapper;
import org.apache.sis.geometry.wrapper.SpatialOperationContext;
import org.apache.sis.feature.internal.shared.AttributeConvention;
-import org.apache.sis.filter.internal.Node;
+import org.apache.sis.filter.function.Node;
import org.apache.sis.util.Exceptions;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -85,6 +85,7 @@ abstract class BinaryGeometryFilter<R> extends Node
implements SpatialOperator<R
* @param geometry2 the second of the two expressions to be used by
this function.
* @param systemUnit if the CRS needs to be in some units of
measurement, the {@link Unit#getSystemUnit()} value.
*/
+ @SuppressWarnings("UseSpecificCatch")
protected BinaryGeometryFilter(final Geometries<?> library,
final Expression<R,?> geometry1,
final Expression<R,?> geometry2,
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 04b5b9d050..40d1474a8f 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
@@ -36,7 +36,8 @@ import java.time.chrono.ChronoZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import org.apache.sis.math.Fraction;
-import org.apache.sis.filter.internal.Node;
+import org.apache.sis.filter.function.Node;
+import org.apache.sis.filter.function.BinaryFunction;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.filter.Filter;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
index 22bab587df..156724aa33 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
@@ -29,6 +29,7 @@ import org.apache.sis.setup.GeometryLibrary;
import org.apache.sis.geometry.WraparoundMethod;
import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.feature.internal.Resources;
+import org.apache.sis.filter.function.UnaryFunction;
import org.apache.sis.filter.sqlmm.Registry;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.iso.AbstractFactory;
@@ -1049,9 +1050,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
if (availableFunctions.isEmpty()) {
/*
* Load functions when first needed or if the module path
changed since last invocation.
- * The SQLMM factory is hard-coded because it is considered as
a basic service to
- * be provided by all DefaultFilterFactory implementations,
and for avoiding the
- * need to make SQLMM registry class public.
+ * The SQLMM factory is hard-coded because it depends on the
geometry library.
*/
final Registry r = new Registry(library);
for (final String fn : r.getNames()) {
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
index 39d8bc7b3d..411cf97321 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
@@ -19,7 +19,7 @@ package org.apache.sis.filter;
import java.util.List;
import java.util.Collection;
import java.util.Objects;
-import org.apache.sis.filter.internal.Node;
+import org.apache.sis.filter.function.Node;
import org.apache.sis.feature.internal.shared.AttributeConvention;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
index 8008b303e5..1078a36f3a 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
@@ -28,7 +28,7 @@ import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.collection.WeakValueHashMap;
import org.apache.sis.feature.internal.shared.FeatureExpression;
import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder;
-import org.apache.sis.filter.internal.Node;
+import org.apache.sis.filter.function.Node;
import org.apache.sis.math.FunctionProperty;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -131,8 +131,8 @@ abstract class LeafExpression<R,V> extends Node implements
FeatureExpression<R,V
}
/** Returns the type of values computed by this expression. */
- @Override public Class<?> getValueClass() {
- return (value != null) ? value.getClass() : Object.class;
+ @Override public Class<? extends V> getResultClass() {
+ return Classes.getClass(value);
}
/** Expression evaluation, which just returns the constant value. */
@@ -189,7 +189,10 @@ abstract class LeafExpression<R,V> extends Node implements
FeatureExpression<R,V
*/
@Override
public FeatureProjectionBuilder.Item expectedType(final
FeatureProjectionBuilder addTo) {
- final Class<?> valueType = getValueClass();
+ Class<?> valueType = getResultClass();
+ if (valueType == null) {
+ valueType = Object.class;
+ }
AttributeType<?> propertyType;
synchronized (TYPES) {
propertyType = TYPES.get(valueType);
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LikeFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LikeFilter.java
index 25a68709e0..bf2e46ff25 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LikeFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LikeFilter.java
@@ -20,7 +20,7 @@ import java.util.List;
import java.util.Collection;
import java.util.Objects;
import java.util.regex.Pattern;
-import org.apache.sis.filter.internal.Node;
+import org.apache.sis.filter.function.Node;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.filter.Filter;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LogicalFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LogicalFilter.java
index d641643596..b0913adc9c 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LogicalFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LogicalFilter.java
@@ -21,7 +21,7 @@ import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.filter.internal.Node;
+import org.apache.sis.filter.function.Node;
import org.apache.sis.util.internal.shared.CollectionsExt;
import org.apache.sis.util.internal.shared.UnmodifiableArrayList;
@@ -61,7 +61,7 @@ abstract class LogicalFilter<R> extends Node implements
LogicalOperator<R>, Opti
LogicalFilter(final Collection<? extends Filter<R>> op) {
ArgumentChecks.ensureNonEmpty("operands", op);
operands = op.toArray(Filter[]::new);
- ArgumentChecks.ensureCountBetween("operands", true, 2,
Integer.MAX_VALUE, operands.length);
+ ArgumentChecks.ensureCountBetween("operands", false, 2,
Integer.MAX_VALUE, operands.length);
for (int i=0; i<operands.length; i++) {
ArgumentChecks.ensureNonNullElement("operands", i, operands[i]);
}
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 1c0cb4bc3b..023e15aa45 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
@@ -26,7 +26,7 @@ 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.filter.internal.Node;
+import org.apache.sis.filter.function.Node;
import org.apache.sis.util.internal.shared.CollectionsExt;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
index 8df2b62ed6..c6c0a63b90 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
@@ -160,7 +160,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
/**
* Returns the type of values fetched from {@link Feature} instance.
- * This is the type before conversion to the {@linkplain #getValueClass()
target type}.
+ * This is the type before conversion to the {@linkplain #getResultClass()
target type}.
* The type is always {@link Object} on newly created expression because
the type of feature property
* values is unknown, but may become a specialized type after {@link
Optimization} has been applied.
*/
@@ -177,7 +177,8 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
* @see #expectedType(FeatureProjectionBuilder)
*/
final FeatureProjectionBuilder.Item defaultType(final
FeatureProjectionBuilder addTo) {
- return
addTo.addComputedProperty(addTo.addAttribute(getValueClass()).setMinimumOccurs(0).setName(name),
true);
+ // `getResultClass()` should never return null with our subtypes of
`PropertyValue`.
+ return
addTo.addComputedProperty(addTo.addAttribute(getResultClass()).setMinimumOccurs(0).setName(name),
true);
}
/**
@@ -186,7 +187,8 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
@Override
@SuppressWarnings("unchecked")
public final <N> PropertyValue<N> toValueType(final Class<N> target) {
- if (target.equals(getValueClass())) {
+ // `getResultClass()` should never return null with our subtypes of
`PropertyValue`.
+ if (target == getResultClass()) {
return (PropertyValue<N>) this;
}
final Class<?> source = getSourceClass();
@@ -225,6 +227,14 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
super(name, isVirtual);
}
+ /**
+ * Returns the type of objects retuned by this expression.
+ */
+ @Override
+ public Class<Object> getResultClass() {
+ return Object.class;
+ }
+
/**
* Returns the value of the property of the name given at construction
time.
* If no value is found for the given feature, then this method
returns {@code null}.
@@ -290,7 +300,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
* Returns the type of values computed by this expression.
*/
@Override
- public final Class<V> getValueClass() {
+ public final Class<V> getResultClass() {
return type;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
index 64a5121d8e..b52a8e656a 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.filter;
+import org.apache.sis.filter.function.BinaryFunction;
import java.time.DateTimeException;
import org.apache.sis.util.Classes;
import org.apache.sis.util.resources.Errors;
@@ -126,14 +127,13 @@ class TemporalFilter<R,T> extends BinaryFunction<R,T,T>
/**
* Returns the class of values computed by the given expression, or {@code
type} if unknown.
+ *
+ * @param e the expression from which to get the type.
+ * @param type the base type, used as a default type.
*/
- @SuppressWarnings("unchecked")
- private static <T> Class<? extends T> getValueClass(final Expression<?,?
extends T> e, final Class<T> type) {
+ private static <T> Class<? extends T> getValueClass(final Expression<?, ?
extends T> e, final Class<T> type) {
if (e instanceof FeatureExpression<?,?>) {
- final Class<?> c = ((FeatureExpression<?, ? extends T>)
e).getValueClass();
- if (type.isAssignableFrom(c)) {
- return (Class<? extends T>) c;
- }
+ return ((FeatureExpression<?, ? extends T>) e).getResultClass();
}
return type;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/BinaryFunction.java
similarity index 98%
rename from
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryFunction.java
rename to
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/BinaryFunction.java
index ddafb45f34..3fe34f8462 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/BinaryFunction.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.filter;
+package org.apache.sis.filter.function;
import java.util.List;
import java.util.Collection;
@@ -24,7 +24,6 @@ import java.math.BigDecimal;
import org.apache.sis.util.Numbers;
import org.apache.sis.math.Fraction;
import org.apache.sis.math.DecimalFunctions;
-import org.apache.sis.filter.internal.Node;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.filter.Filter;
@@ -44,7 +43,7 @@ import org.opengis.filter.Expression;
* @param <V1> the type of value computed by the first expression.
* @param <V2> the type of value computed by the second expression.
*/
-abstract class BinaryFunction<R,V1,V2> extends Node {
+public abstract class BinaryFunction<R,V1,V2> extends Node {
/**
* For cross-version compatibility.
*/
@@ -104,7 +103,7 @@ abstract class BinaryFunction<R,V1,V2> extends Node {
*
* @return a list of size 2 containing the two expressions.
*/
- public List<Expression<R,?>> getExpressions() {
+ public final List<Expression<R,?>> getExpressions() {
return List.of(expression1, expression2);
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/ConvertFunction.java
similarity index 91%
rename from
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
rename to
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/ConvertFunction.java
index faa913ae15..af760f3199 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/ConvertFunction.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.filter;
+package org.apache.sis.filter.function;
import java.util.Set;
import java.util.List;
@@ -23,10 +23,11 @@ import org.opengis.util.ScopedName;
import org.apache.sis.util.ObjectConverter;
import org.apache.sis.util.ObjectConverters;
import org.apache.sis.util.UnconvertibleObjectException;
+import org.apache.sis.util.resources.Errors;
import org.apache.sis.feature.internal.shared.FeatureExpression;
import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder;
+import org.apache.sis.filter.Optimization;
import org.apache.sis.math.FunctionProperty;
-import org.apache.sis.util.resources.Errors;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.filter.Expression;
@@ -41,9 +42,9 @@ import org.opengis.filter.Expression;
* @param <S> the type of value computed by the wrapped exception. This is
the type to convert.
* @param <V> the type of value computed by this expression. This is the
type after conversion.
*
- * @see org.apache.sis.filter.internal.shared.GeometryConverter
+ * @see GeometryConverter
*/
-final class ConvertFunction<R,S,V> extends UnaryFunction<R,S>
+public final class ConvertFunction<R,S,V> extends UnaryFunction<R,S>
implements FeatureExpression<R,V>, Optimization.OnExpression<R,V>
{
/**
@@ -70,7 +71,10 @@ final class ConvertFunction<R,S,V> extends UnaryFunction<R,S>
* @param target the desired type for the expression result.
* @throws UnconvertibleObjectException if no converter is found.
*/
- ConvertFunction(final Expression<R, ? extends S> expression, final
Class<S> source, final Class<V> target) {
+ public ConvertFunction(final Expression<R, ? extends S> expression,
+ final Class<? extends S> source,
+ final Class<V> target)
+ {
super(expression);
converter = ObjectConverters.find(source, target);
}
@@ -94,8 +98,8 @@ final class ConvertFunction<R,S,V> extends UnaryFunction<R,S>
public Expression<R,V> recreate(Expression<R,?>[] effective) {
final Expression<R,?> e = effective[0];
if (e instanceof FeatureExpression<?,?>) {
- final Class<? extends V> target = getValueClass();
// This is <V>.
- final Class<?> source = ((FeatureExpression<?,?>)
e).getValueClass(); // May become <S>.
+ final Class<? extends V> target = getResultClass();
// This is <V>.
+ final Class<?> source = ((FeatureExpression<?,?>)
e).getResultClass(); // May become <S>.
if (target.isAssignableFrom(source)) {
return (Expression<R,V>) e;
}
@@ -131,7 +135,7 @@ final class ConvertFunction<R,S,V> extends
UnaryFunction<R,S>
*/
@Override
protected Collection<?> getChildren() {
- return List.of(expression, converter.getSourceClass(),
converter.getTargetClass());
+ return List.of(expression, converter.getSourceClass(),
getResultClass());
}
/**
@@ -158,7 +162,7 @@ final class ConvertFunction<R,S,V> extends
UnaryFunction<R,S>
* Returns the type of values computed by this expression.
*/
@Override
- public Class<? extends V> getValueClass() {
+ public final Class<? extends V> getResultClass() {
return converter.getTargetClass();
}
@@ -175,7 +179,7 @@ final class ConvertFunction<R,S,V> extends
UnaryFunction<R,S>
return null;
}
final FeatureProjectionBuilder.Item item =
addTo.addTemplateProperty(fex);
- item.replaceValueClass((c) -> getValueClass());
+ item.replaceValueClass((c) -> getResultClass());
return item;
}
@@ -191,7 +195,7 @@ final class ConvertFunction<R,S,V> extends
UnaryFunction<R,S>
@Override
@SuppressWarnings("unchecked")
public <N> Expression<R,N> toValueType(final Class<N> target) {
- if (target.isAssignableFrom(getValueClass())) {
+ if (target.isAssignableFrom(getResultClass())) {
return (Expression<R,N>) this;
}
final Class<? super S> source = converter.getSourceClass();
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/GeometryConverter.java
similarity index 99%
rename from
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java
rename to
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/GeometryConverter.java
index 5e77717b8c..aa06be8c95 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/GeometryConverter.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.filter.internal;
+package org.apache.sis.filter.function;
import java.util.List;
import java.util.Collection;
@@ -48,7 +48,7 @@ import org.opengis.coordinate.MismatchedDimensionException;
* @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
* @param <G> the geometry implementation type.
*
- * @see org.apache.sis.filter.ConvertFunction
+ * @see ConvertFunction
*/
class GeometryConverter<R,G> extends Node implements
Optimization.OnExpression<R, GeometryWrapper> {
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryFromFeature.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/GeometryFromFeature.java
similarity index 99%
rename from
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryFromFeature.java
rename to
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/GeometryFromFeature.java
index de82d0562e..9b99ad9ca7 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryFromFeature.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/GeometryFromFeature.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.filter.internal;
+package org.apache.sis.filter.function;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.geometry.wrapper.Geometries;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/Node.java
similarity index 98%
rename from
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
rename to
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/Node.java
index 37951b1e6f..35e356b6d3 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/Node.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.filter.internal;
+package org.apache.sis.filter.function;
import java.util.Set;
import java.util.Map;
@@ -97,7 +97,7 @@ public abstract class Node implements Serializable {
*
* @see Expression#getFunctionName()
*/
- protected static <T> AttributeType<T> createType(final Class<T> type,
final Object name) {
+ public static <T> AttributeType<T> createType(final Class<T> type, final
Object name) {
// We do not use `Map.of(…)` for better exception message in case of
null name.
return new
DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY,
name),
type, 1, 1, null,
(AttributeType<?>[]) null);
@@ -167,7 +167,7 @@ public abstract class Node implements Serializable {
* @param tip the expression name in SIS namespace.
* @return an expression name in the SIS namespace.
*/
- protected static ScopedName createName(final String tip) {
+ public static ScopedName createName(final String tip) {
return Names.createScopedName(SCOPE, null, tip);
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/UnaryFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/UnaryFunction.java
similarity index 92%
rename from
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/UnaryFunction.java
rename to
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/UnaryFunction.java
index ee78c0e1fa..dce8e0173f 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/UnaryFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/UnaryFunction.java
@@ -14,14 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.filter;
+package org.apache.sis.filter.function;
import java.util.List;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
+import org.apache.sis.filter.Optimization;
import org.apache.sis.xml.NilReason;
-import org.apache.sis.filter.internal.Node;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.filter.Filter;
@@ -40,7 +40,7 @@ import org.opengis.filter.NullOperator;
* @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
* @param <V> the type of value computed by the expression.
*/
-class UnaryFunction<R,V> extends Node {
+public class UnaryFunction<R,V> extends Node {
/**
* For cross-version compatibility.
*/
@@ -57,7 +57,7 @@ class UnaryFunction<R,V> extends Node {
/**
* Creates a new unary operator.
*/
- UnaryFunction(final Expression<R, ? extends V> expression) {
+ protected UnaryFunction(final Expression<R, ? extends V> expression) {
this.expression = Objects.requireNonNull(expression);
}
@@ -106,14 +106,14 @@ class UnaryFunction<R,V> extends Node {
*
* @param <R> the type of resources used as inputs.
*/
- static final class IsNull<R> extends UnaryFunction<R,Object>
+ public static final class IsNull<R> extends UnaryFunction<R,Object>
implements NullOperator<R>, Optimization.OnFilter<R>
{
/** For cross-version compatibility. */
private static final long serialVersionUID = 2960285515924533419L;
/** Creates a new operator. */
- IsNull(final Expression<R,?> expression) {
+ public IsNull(final Expression<R,?> expression) {
super(expression);
}
@@ -141,7 +141,7 @@ class UnaryFunction<R,V> extends Node {
*
* @param <R> the type of resources used as inputs.
*/
- static final class IsNil<R> extends UnaryFunction<R,Object>
+ public static final class IsNil<R> extends UnaryFunction<R,Object>
implements NilOperator<R>, Optimization.OnFilter<R>
{
/** For cross-version compatibility. */
@@ -151,7 +151,7 @@ class UnaryFunction<R,V> extends Node {
private final String nilReason;
/** Creates a new operator. */
- IsNil(final Expression<R,?> expression, final String nilReason) {
+ public IsNil(final Expression<R,?> expression, final String nilReason)
{
super(expression);
this.nilReason = nilReason;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/BinaryOperator.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/BinaryOperator.java
new file mode 100644
index 0000000000..6d9714d05b
--- /dev/null
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/BinaryOperator.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter.function.math;
+
+import java.util.Objects;
+import java.util.function.DoubleBinaryOperator;
+import java.io.ObjectStreamException;
+import org.opengis.util.ScopedName;
+import org.apache.sis.filter.Optimization;
+import org.apache.sis.filter.function.BinaryFunction;
+import org.apache.sis.feature.internal.shared.FeatureExpression;
+import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.filter.Expression;
+
+
+/**
+ * An operation upon two operands.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
+ */
+final class BinaryOperator<R> extends BinaryFunction<R, Number, Number>
+ implements FeatureExpression<R, Double>, Optimization.OnExpression<R,
Double>
+{
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 8021641013005967925L;
+
+ /**
+ * The function to apply.
+ */
+ private final Function function;
+
+ /**
+ * The {@link Function#binary} value, guaranteed non-null.
+ */
+ private final transient DoubleBinaryOperator math;
+
+ /**
+ * Creates a new function.
+ */
+ BinaryOperator(final Function function,
+ final Expression<R, ? extends Number> expression1,
+ final Expression<R, ? extends Number> expression2)
+ {
+ super(expression1, expression2);
+ this.function = function;
+ math = Objects.requireNonNull(function.binary);
+ }
+
+ /**
+ * Invoked at deserialization time for setting the {@link #math} field.
+ */
+ private Object readResolve() throws ObjectStreamException {
+ return new BinaryOperator<>(function, expression1, expression2);
+ }
+
+ /**
+ * Returns the name of the function to be called.
+ */
+ @Override
+ public ScopedName getFunctionName() {
+ return function.getFunctionName();
+ }
+
+ /**
+ * Returns the type of values computed by this expression.
+ */
+ @Override
+ public final Class<Double> getResultClass() {
+ return Double.class;
+ }
+
+ /**
+ * Provides the type of results computed by this expression. That type
depends only
+ * on the {@code ArithmeticFunction} subclass and is given by {@link
#expectedType()}.
+ */
+ @Override
+ public final FeatureProjectionBuilder.Item
expectedType(FeatureProjectionBuilder addTo) {
+ return addTo.addSourceProperty(function.getResultType(), false);
+ }
+
+ /**
+ * Evaluates the expression.
+ */
+ @Override
+ public final Double apply(final R feature) {
+ final Number left = expression1.apply(feature);
+ if (left != null) {
+ final Number right = expression2.apply(feature);
+ if (right != null) {
+ return math.applyAsDouble(left.doubleValue(),
right.doubleValue());
+ }
+ }
+ return null;
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/Function.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/Function.java
new file mode 100644
index 0000000000..74694d827a
--- /dev/null
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/Function.java
@@ -0,0 +1,287 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter.function.math;
+
+import java.util.Map;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.DoubleUnaryOperator;
+import java.util.function.DoubleBinaryOperator;
+import org.opengis.util.TypeName;
+import org.opengis.util.LocalName;
+import org.opengis.util.ScopedName;
+import org.opengis.parameter.ParameterDescriptor;
+import org.apache.sis.parameter.DefaultParameterDescriptor;
+import org.apache.sis.feature.internal.shared.FeatureExpression;
+import org.apache.sis.filter.function.Node;
+import org.apache.sis.util.iso.Names;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.filter.capability.AvailableFunction;
+import org.opengis.feature.AttributeType;
+
+
+/**
+ * Descriptions of mathematical operations.
+ * These functions are not standard in the ANSI SQL-92 specification,
+ * therefore they may or may not be available on a specific database.
+ * However, most of them seem available on major database systems.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+enum Function implements AvailableFunction {
+ /*
+ * MIN and MAX are omitted because it needs more generic code working with
Comparable.
+ * We may need specializations here for the handling of NaN, but this is
deferred to a
+ * future version.
+ */
+
+ /**
+ * The absolute value of <var>x</var>.
+ */
+ ABS(Math::abs),
+
+ /**
+ * The sign of input <var>x</var> as -1, 0, or 1.
+ */
+ SIGN(Math::signum),
+
+ /**
+ * The value of <var>x</var> rounded to the nearest whole integer.
+ */
+ ROUND(Math::rint),
+
+ /**
+ * The largest integer value that is less than or equal to <var>x</var>.
+ */
+ FLOOR(Math::floor),
+
+ /**
+ * The smallest integer value that is greater than or equal to
<var>x</var>.
+ * This is named {@code CEILING} in some databases.
+ */
+ CEIL(Math::ceil),
+
+ /**
+ * The logarithm in base 10 of <var>x</var>.
+ */
+ LOG10(Math::log10),
+
+ /**
+ * The logarithm in base {@linkplain Math#E e} of <var>x</var>.
+ */
+ LOG(Math::log),
+
+ /**
+ * The value {@linkplain Math#E e} raised to power <var>x</var>.
+ */
+ EXP(Math::exp),
+
+ /**
+ * The value of <var>x</var> raised to the power of <var>y</var>.
+ */
+ POWER(Math::pow),
+
+ /**
+ * The square-root value of <var>x</var>.
+ */
+ SQRT(Math::sqrt),
+
+ /**
+ * The cubic-root value of <var>x</var>.
+ */
+ CBRT(Math::cbrt),
+
+ /**
+ * Hypotenuse of <var>x</var> and <var>y</var>.
+ */
+ HYPOT(Math::hypot),
+
+ /**
+ * The arc sine of <var>x</var>.
+ */
+ ASIN(Math::asin),
+
+ /**
+ * The arc cosine of <var>x</var>.
+ */
+ ACOS(Math::acos),
+
+ /**
+ * The arc tangent of <var>x</var>.
+ */
+ ATAN(Math::atan),
+
+ /**
+ * The arc tangent of <var>y</var>/<var>x</var>.
+ * Note that <var>y</var> is the first argument and <var>x</var> is the
second argument.
+ */
+ ATAN2(Math::atan2),
+
+ /**
+ * The hyperbolic sine of <var>x</var>.
+ */
+ SINH(Math::sinh),
+
+ /**
+ * The hyperbolic cosine of <var>x</var>.
+ */
+ COSH(Math::cosh),
+
+ /**
+ * The hyperbolic tangent of <var>x</var>.
+ */
+ TANH(Math::tanh);
+
+ /**
+ * The mathematical function to invoke if this operation is unary, or
{@code null}.
+ */
+ @SuppressWarnings("serial")
+ final DoubleUnaryOperator unary;
+
+ /**
+ * The mathematical function to invoke if this operation is binary, or
{@code null}.
+ */
+ @SuppressWarnings("serial")
+ final DoubleBinaryOperator binary;
+
+ /**
+ * The name of this function, created when first needed.
+ *
+ * @see #getFunctionName()
+ */
+ private ScopedName name;
+
+ /**
+ * Description of the result, created when first needed.
+ *
+ * @see #getResultType()
+ */
+ private AttributeType<Double> resultType;
+
+ /**
+ * Creates a new function description for a unary operation.
+ */
+ private Function(final DoubleUnaryOperator math) {
+ unary = math;
+ binary = null;
+ }
+
+ /**
+ * Creates a new function description for a binary operation.
+ */
+ private Function(final DoubleBinaryOperator math) {
+ unary = null;
+ binary = math;
+ }
+
+ /**
+ * Returns the minimum number of parameters expected by this function.
+ */
+ final int getMinParameterCount() {
+ if (unary != null) return 1;
+ if (binary != null) return 2;
+ return 0;
+ }
+
+ /**
+ * Returns the maximum number of parameters expected by this function.
+ */
+ final int getMaxParameterCount() {
+ if (binary != null) return 2;
+ if (unary != null) return 1;
+ return 0;
+ }
+
+ /**
+ * Returns the function name.
+ */
+ @Override
+ public LocalName getName() {
+ return getFunctionName().tip();
+ }
+
+ /**
+ * Returns the function name returned by the expression.
+ *
+ * @see FeatureExpression#getFunctionName()
+ */
+ final synchronized ScopedName getFunctionName() {
+ if (name == null) {
+ name = Node.createName(camelCaseName());
+ }
+ return name;
+ }
+
+ /**
+ * Returns the function name in the case to show to users.
+ */
+ final String camelCaseName() {
+ return name().toLowerCase(Locale.US).intern();
+ }
+
+ /**
+ * Returns the attribute type to declare in feature types that store
result of this function.
+ */
+ final synchronized AttributeType<Double> getResultType() {
+ if (resultType == null) {
+ resultType = Node.createType(Double.class, camelCaseName());
+ }
+ return resultType;
+ }
+
+ /**
+ * Returns the type of return value.
+ */
+ @Override
+ public TypeName getReturnType() {
+ return RESULT_TYPE;
+ }
+
+ /**
+ * The type of values produced by all functions in this enumeration.
+ */
+ private static final TypeName RESULT_TYPE =
Names.createTypeName(Double.class);
+
+ /**
+ * Returns the list of arguments expected by the function.
+ */
+ @Override
+ public List<? extends ParameterDescriptor<?>> getArguments() {
+ if (unary != null) {
+ return List.of(X);
+ } else if (this != ATAN2) {
+ return List.of(X, Y);
+ } else {
+ return List.of(Y, X); // Special case for `atan2(y, x)`.
+ }
+ }
+
+ /**
+ * Description of a function parameter.
+ */
+ private static final DefaultParameterDescriptor<Number> X =
parameter("x"), Y = parameter("y");
+
+ /**
+ * Creates a parameter descriptor of the given name.
+ */
+ private static DefaultParameterDescriptor<Number> parameter(final String
name) {
+ return new DefaultParameterDescriptor<>(
+ Map.of(DefaultParameterDescriptor.NAME_KEY, name),
+ 1, 1, Number.class, null, null, null);
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/Registry.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/Registry.java
new file mode 100644
index 0000000000..bc5dd1d342
--- /dev/null
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/Registry.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter.function.math;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Locale;
+import org.apache.sis.filter.FunctionRegister;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.collection.Containers;
+import org.apache.sis.util.internal.shared.Constants;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.filter.Expression;
+import org.opengis.filter.capability.AvailableFunction;
+
+
+/**
+ * A register of mathematical functions.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ */
+public final class Registry implements FunctionRegister {
+ /**
+ * Creates a new registry.
+ *
+ * @todo Replace by a static {@code provider()} method after we abandon
classpath support.
+ */
+ public Registry() {
+ }
+
+ /**
+ * Returns the name of body defining the functions.
+ * Since we have no standard to refer to, we use "SIS" for now.
+ */
+ @Override
+ public String getAuthority() {
+ return Constants.SIS;
+ }
+
+ /**
+ * Returns the names of all functions that this factory can create.
+ */
+ @Override
+ public Collection<String> getNames() {
+ return Containers.derivedList(Arrays.asList(Function.values()),
Function::camelCaseName);
+ }
+
+ /**
+ * Describes the parameters of a function.
+ *
+ * @param name name of the function to describe (not null).
+ * @return description of the function parameters.
+ * @throws IllegalArgumentException if function name is unknown..
+ */
+ @Override
+ public AvailableFunction describe(String name) {
+ return Function.valueOf(name.toUpperCase(Locale.US));
+ }
+
+ /**
+ * Creates a new function of the given name with given parameters.
+ *
+ * @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
+ * @param name name of the function to create (not null).
+ * @param parameters function parameters.
+ * @return function for the given name and parameters.
+ * @throws IllegalArgumentException if function name is unknown or some
parameters are illegal.
+ */
+ @Override
+ public <R> Expression<R, ?> create(final String name, final
Expression<R,?>[] parameters) {
+ final Function function =
Function.valueOf(name.toUpperCase(Locale.US));
+ ArgumentChecks.ensureCountBetween("parameters", false,
+ function.getMinParameterCount(),
+ function.getMaxParameterCount(),
+ parameters.length);
+ switch (parameters.length) {
+ case 1: return new UnaryOperator<>(function,
+ parameters[0].toValueType(Number.class));
+
+ case 2: return new BinaryOperator<>(function,
+ parameters[0].toValueType(Number.class),
+ parameters[1].toValueType(Number.class));
+ }
+ throw new IllegalArgumentException(); // Should never happen.
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/UnaryOperator.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/UnaryOperator.java
new file mode 100644
index 0000000000..b308a070b4
--- /dev/null
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/UnaryOperator.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter.function.math;
+
+import java.util.Objects;
+import java.util.function.DoubleUnaryOperator;
+import java.io.ObjectStreamException;
+import org.opengis.util.ScopedName;
+import org.apache.sis.filter.Optimization;
+import org.apache.sis.filter.function.UnaryFunction;
+import org.apache.sis.feature.internal.shared.FeatureExpression;
+import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.filter.Expression;
+
+
+/**
+ * An operation on a single operand.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
+ */
+final class UnaryOperator<R> extends UnaryFunction<R, Number>
+ implements FeatureExpression<R, Double>, Optimization.OnExpression<R,
Double>
+{
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -6215509464490587978L;
+
+ /**
+ * The function to apply.
+ */
+ private final Function function;
+
+ /**
+ * The {@link Function#binary} value, guaranteed non-null.
+ */
+ private final transient DoubleUnaryOperator math;
+
+ /**
+ * Creates a new function.
+ */
+ UnaryOperator(final Function function, final Expression<R, ? extends
Number> expression) {
+ super(expression);
+ this.function = function;
+ math = Objects.requireNonNull(function.unary);
+ }
+
+ /**
+ * Invoked at deserialization time for setting the {@link #math} field.
+ */
+ private Object readResolve() throws ObjectStreamException {
+ return new UnaryOperator<>(function, expression);
+ }
+
+ /**
+ * Returns the name of the function to be called.
+ */
+ @Override
+ public ScopedName getFunctionName() {
+ return function.getFunctionName();
+ }
+
+ /**
+ * Returns the type of values computed by this expression.
+ */
+ @Override
+ public final Class<Double> getResultClass() {
+ return Double.class;
+ }
+
+ /**
+ * Provides the type of results computed by this expression. That type
depends only
+ * on the {@code ArithmeticFunction} subclass and is given by {@link
#expectedType()}.
+ */
+ @Override
+ public final FeatureProjectionBuilder.Item
expectedType(FeatureProjectionBuilder addTo) {
+ return addTo.addSourceProperty(function.getResultType(), false);
+ }
+
+ /**
+ * Evaluates the expression.
+ */
+ @Override
+ public final Double apply(final R feature) {
+ final Number value = expression.apply(feature);
+ if (value != null) {
+ return math.applyAsDouble(value.doubleValue());
+ }
+ return null;
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/package-info.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/package-info.java
similarity index 79%
copy from
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/package-info.java
copy to
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/package-info.java
index b43d313abf..911de396d2 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/package-info.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/math/package-info.java
@@ -16,9 +16,11 @@
*/
/**
- * Base implementation shared by the main {@code filter} package and the SQLMM
extension.
+ * Partial implementation of mathematical operations as filter expressions.
+ * The main public class in this package is {@link Registry},
+ * which is the single entry point for all functions.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
-package org.apache.sis.filter.internal;
+package org.apache.sis.filter.function.math;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/package-info.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/package-info.java
similarity index 92%
rename from
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/package-info.java
rename to
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/package-info.java
index b43d313abf..b7b091de5c 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/package-info.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/function/package-info.java
@@ -16,9 +16,9 @@
*/
/**
- * Base implementation shared by the main {@code filter} package and the SQLMM
extension.
+ * Base implementation shared by the main {@code filter} package and the
<abbr>SQLMM</abbr> extension.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
-package org.apache.sis.filter.internal;
+package org.apache.sis.filter.function;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/FunctionNames.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/FunctionNames.java
index e248d4b306..de621e6fad 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/FunctionNames.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/FunctionNames.java
@@ -20,7 +20,7 @@ import org.apache.sis.filter.sqlmm.SQLMM;
/**
- * Names of some expressions used in Apache SIS.
+ * Names of some expressions used in Apache <abbr>SIS</abbr>.
* This class defines only the names that need to be referenced from at least
two different classes.
*
* @author Martin Desruisseaux (Geomatys)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/WarningEvent.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/WarningEvent.java
index bea30abf46..fd9a542401 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/WarningEvent.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/WarningEvent.java
@@ -19,7 +19,7 @@ package org.apache.sis.filter.internal.shared;
import java.util.Optional;
import java.util.function.Consumer;
import org.opengis.util.ScopedName;
-import org.apache.sis.filter.internal.Node;
+import org.apache.sis.filter.function.Node;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.util.CodeList;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/package-info.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/package-info.java
index bbc43f568a..5d0794eb45 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/package-info.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/package-info.java
@@ -16,7 +16,7 @@
*/
/**
- * A set of helper classes for the SIS implementation.
+ * A set of helper classes for the <abbr>SIS</abbr> implementation.
* also contains classes that may move to the public API someday,
* but are considered not yet ready.
*
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionDescription.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionDescription.java
index 2f60877371..78c8aaf0a3 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionDescription.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionDescription.java
@@ -24,6 +24,7 @@ import org.opengis.util.LocalName;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.filter.capability.AvailableFunction;
+import org.apache.sis.pending.jdk.Record;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.iso.Names;
@@ -35,7 +36,7 @@ import org.apache.sis.referencing.NamedIdentifier;
/**
- * Description of a SQLMM function with its parameters.
+ * Description of a <abbr>SQLMM</abbr> function with its parameters.
*
* @todo Argument descriptions are incomplete. They have no good names,
* and the types are missing (they are {@code null}) except for geometry
types.
@@ -44,7 +45,7 @@ import org.apache.sis.referencing.NamedIdentifier;
*
* @see SQLMM#description(Geometries)
*/
-final class FunctionDescription implements AvailableFunction, Serializable {
+final class FunctionDescription extends Record implements AvailableFunction,
Serializable {
/**
* For cross-version compatibility.
*/
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
index c9fe68215e..210d419f2e 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
@@ -101,7 +101,7 @@ abstract class GeometryParser<R,G> extends
GeometryConstructor<R,G> {
case ST_BdMPolyFromText: break;
default: warning(new
InvalidFilterValueException(Errors.format(
Errors.Keys.IllegalArgumentClass_3, inputName(),
- getValueClass(),
+ getResultClass(),
Classes.getClass(library.getGeometry(result)))), true);
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
index 632b473cdf..d0a7a26723 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
@@ -20,7 +20,7 @@ import java.util.Arrays;
import java.util.Collection;
import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.filter.FunctionRegister;
-import org.apache.sis.pending.jdk.JDK16;
+import org.apache.sis.util.collection.Containers;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.filter.Expression;
@@ -62,7 +62,7 @@ public final class Registry implements FunctionRegister {
*/
@Override
public Collection<String> getNames() {
- return JDK16.toList(Arrays.stream(SQLMM.values()).map(SQLMM::name));
+ return Containers.derivedList(Arrays.asList(SQLMM.values()),
SQLMM::name);
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
index 64200551e3..87e11fe356 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
@@ -21,7 +21,7 @@ import java.util.Collection;
import org.opengis.util.LocalName;
import org.opengis.util.ScopedName;
import org.apache.sis.filter.Optimization;
-import org.apache.sis.filter.internal.Node;
+import org.apache.sis.filter.function.Node;
import org.apache.sis.feature.internal.Resources;
import org.apache.sis.feature.internal.shared.FeatureExpression;
import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder;
@@ -43,7 +43,9 @@ import org.opengis.filter.InvalidFilterValueException;
*
* @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
*/
-abstract class SpatialFunction<R> extends Node implements
FeatureExpression<R,Object>, Optimization.OnExpression<R,Object> {
+abstract class SpatialFunction<R> extends Node
+ implements FeatureExpression<R, Object>, Optimization.OnExpression<R,
Object>
+{
/**
* For cross-version compatibility.
*/
@@ -78,7 +80,7 @@ abstract class SpatialFunction<R> extends Node implements
FeatureExpression<R,Ob
*/
SpatialFunction(final SQLMM operation, final Expression<R,?>[] parameters)
{
this.operation = operation;
- ArgumentChecks.ensureCountBetween("parameters", true,
+ ArgumentChecks.ensureCountBetween("parameters", false,
operation.minParamCount, operation.maxParamCount,
parameters.length);
}
@@ -149,7 +151,7 @@ abstract class SpatialFunction<R> extends Node implements
FeatureExpression<R,Ob
* Returns the kind of objects evaluated by this expression.
*/
@Override
- public final Class<?> getValueClass() {
+ public final Class<?> getResultClass() {
return operation.getReturnType(getGeometryLibrary());
}
@@ -160,7 +162,7 @@ abstract class SpatialFunction<R> extends Node implements
FeatureExpression<R,Ob
@Override
@SuppressWarnings("unchecked")
public final <N> Expression<R,N> toValueType(final Class<N> target) {
- if (target.isAssignableFrom(getValueClass())) {
+ if (target.isAssignableFrom(getResultClass())) {
return (Expression<R,N>) this;
} else {
throw new
ClassCastException(Errors.format(Errors.Keys.CanNotConvertValue_2,
getFunctionName(), target));
@@ -200,6 +202,6 @@ abstract class SpatialFunction<R> extends Node implements
FeatureExpression<R,Ob
}
throw new
InvalidFilterValueException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
}
- return
addTo.addComputedProperty(addTo.addAttribute(getValueClass()).setName(getFunctionName()),
false);
+ return
addTo.addComputedProperty(addTo.addAttribute(getResultClass()).setName(getFunctionName()),
false);
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/package-info.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/package-info.java
index 303c7d65f5..78b67c5ab6 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/package-info.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/package-info.java
@@ -16,7 +16,7 @@
*/
/**
- * Partial implementation of SQLMM operations as filter expressions.
+ * Partial implementation of <abbr>SQLMM</abbr> operations as filter
expressions.
* This package supports only for the simplest types (point, line string,
polygon).
* Other types (curve, circular string, compound curve, curve polygon,
triangle,
* polyhedral surface, TIN, multi curve, multi surface) are not supported.
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
index 39c3c62c35..e4dade8c0e 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
@@ -260,7 +260,7 @@ public final class LogicalFilterTest extends TestCase {
final var property = assertInstanceOf(PropertyValue.class,
optimized.getParameters().get(0));
assertEquals(String.class, property.getSourceClass());
- assertEquals(Number.class, property.getValueClass());
+ assertEquals(Number.class, property.getResultClass());
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/function/math/RegistryTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/function/math/RegistryTest.java
new file mode 100644
index 0000000000..52f6861c3e
--- /dev/null
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/function/math/RegistryTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter.function.math;
+
+import java.util.Map;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.DefaultFilterFactory;
+
+// Test dependencies
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+
+/**
+ * Tests expressions using mathematical functions.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+public final class RegistryTest {
+ /**
+ * Type of features used for testing.
+ */
+ private final DefaultFeatureType feature;
+
+ /**
+ * Creates a new test case.
+ */
+ public RegistryTest() {
+ feature = new DefaultFeatureType(Map.of(DefaultFeatureType.NAME_KEY,
"Test"), false, null,
+ new
DefaultAttributeType<>(Map.of(DefaultAttributeType.NAME_KEY, "value"),
Double.class, 1, 1, null));
+ }
+
+ /**
+ * Tests {@link Function#ABS}.
+ */
+ @Test
+ public void testAbs() {
+ final var ff = DefaultFilterFactory.forFeatures();
+ final var ex = ff.function("abs", ff.property("value"));
+
+ final var f = feature.newInstance();
+ f.setPropertyValue("value", 12.5);
+ assertEquals(12.5, ex.apply(f));
+ f.setPropertyValue("value", -18.25);
+ assertEquals(18.25, ex.apply(f));
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/RegistryTestCase.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/RegistryTestCase.java
index 70fe0ae7e2..59111a9524 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/RegistryTestCase.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/RegistryTestCase.java
@@ -28,7 +28,7 @@ import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.filter.Optimization;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.WraparoundMethod;
-import org.apache.sis.filter.internal.Node;
+import org.apache.sis.filter.function.Node;
import org.apache.sis.geometry.wrapper.Dimensions;
import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.geometry.wrapper.GeometryType;
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ObjectConverters.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ObjectConverters.java
index 5d76ea2a72..bdff2bd324 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ObjectConverters.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ObjectConverters.java
@@ -59,7 +59,7 @@ import org.apache.sis.converter.SystemRegistry;
* }
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 0.3
+ * @version 1.6
*
* @see ObjectConverter
*
@@ -93,12 +93,20 @@ public final class ObjectConverters {
* @return the converter from the specified source class to the target
class.
* @throws UnconvertibleObjectException if no converter is found.
*/
- public static <S,T> ObjectConverter<? super S, ? extends T> find(final
Class<S> source, final Class<T> target)
+ @SuppressWarnings("unchecked")
+ public static <S,T> ObjectConverter<? super S, ? extends T> find(
+ final Class<? extends S> source,
+ final Class<? super T> target)
throws UnconvertibleObjectException
{
ArgumentChecks.ensureNonNull("source", source);
ArgumentChecks.ensureNonNull("target", target);
- return SystemRegistry.INSTANCE.find(source, target);
+ /*
+ * The `(Class<S>)` cast is safe because the generic type of the
returned converter is `<? super S>`.
+ * Therefore, even if the actual class is a subtype of `S`, the `?
super S` declaration stay valid.
+ * A similar argument applies also to the `(Class<T>)` cast.
+ */
+ return SystemRegistry.INSTANCE.find((Class<S>) source, (Class<T>)
target);
}
/**
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Containers.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Containers.java
index 5448bb1195..a8110729ba 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Containers.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Containers.java
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Iterator;
import java.util.Collection;
import java.util.Objects;
+import java.util.function.Function;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ObjectConverter;
import org.apache.sis.util.resources.Errors;
@@ -34,7 +35,7 @@ import
org.apache.sis.util.internal.shared.UnmodifiableArrayList;
* in this class implement the {@code CheckedContainer} interface.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.6
* @since 0.3
*/
public final class Containers {
@@ -126,7 +127,7 @@ public final class Containers {
* Returns a set whose elements are derived <i>on-the-fly</i> from the
given set.
* Conversions from the original elements to the derived elements are
performed when needed
* by invoking the {@link ObjectConverter#apply(Object)} method on the
given converter.
- * Those conversions are repeated every time a {@code Set} method is
invoked; there is no cache.
+ * Those conversions are repeated every time that a {@code Set} method
needs to access values.
* Consequently, any change in the original set is immediately visible in
the derived set,
* and conversely.
*
@@ -162,11 +163,39 @@ public final class Containers {
return DerivedSet.create(storage, converter);
}
+ /**
+ * Returns a list whose elements are derived <i>on-the-fly</i> from the
given list.
+ * Conversions from the original elements to the derived elements are
performed when needed
+ * by invoking the {@link Function#apply(Object)} method on the given
converter.
+ * Those conversions are repeated every time that a {@code List} method
needs to access values.
+ * Consequently, any change in the original list is immediately visible in
the derived list.
+ *
+ * <p>The returned list can be serialized if the given list and converter
are serializable.
+ * The returned list is not synchronized by itself, but is nevertheless
thread-safe if the
+ * given list (including its iterator) and converter are thread-safe.</p>
+ *
+ * @param <S> the type of elements in the storage (original) list.
+ * @param <E> the type of elements in the derived list.
+ * @param storage the storage list containing the original elements,
or {@code null}.
+ * @param converter the converter from the elements in the storage list
to the elements in the derived list.
+ * @return a view over the {@code storage} list containing all elements
converted by the given converter,
+ * or {@code null} if {@code storage} was null.
+ *
+ * @since 1.6
+ */
+ public static <S,E> List<E> derivedList(final List<S> storage, final
Function<S,E> converter) {
+ ArgumentChecks.ensureNonNull("converter", converter);
+ if (storage == null) {
+ return null;
+ }
+ return new DerivedList<>(storage, converter);
+ }
+
/**
* Returns a map whose keys and values are derived <i>on-the-fly</i> from
the given map.
* Conversions from the original entries to the derived entries are
performed when needed
* by invoking the {@link ObjectConverter#apply(Object)} method on the
given converters.
- * Those conversions are repeated every time a {@code Map} method is
invoked; there is no cache.
+ * Those conversions are repeated every time that a {@code Map} method
needs to access values.
* Consequently, any change in the original map is immediately visible in
the derived map,
* and conversely.
*
diff --git
a/endorsed/src/org.apache.sis.cloud.aws/main/org/apache/sis/cloud/aws/s3/DerivedList.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/DerivedList.java
similarity index 82%
rename from
endorsed/src/org.apache.sis.cloud.aws/main/org/apache/sis/cloud/aws/s3/DerivedList.java
rename to
endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/DerivedList.java
index 45aff686e6..3639be2413 100644
---
a/endorsed/src/org.apache.sis.cloud.aws/main/org/apache/sis/cloud/aws/s3/DerivedList.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/DerivedList.java
@@ -14,8 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.cloud.aws.s3;
+package org.apache.sis.util.collection;
+import java.io.Serializable;
import java.util.List;
import java.util.AbstractList;
import java.util.Iterator;
@@ -26,7 +27,7 @@ import java.util.function.Consumer;
/**
* A list in which values are derived from another list using a given function.
- * The conversion is done the fly every times an element is accessed.
+ * The conversion is done on-the-fly every times that an element is accessed.
* Consequently, this wrapper should be used only for elements that are cheap
to wrap.
*
* @author Martin Desruisseaux (Geomatys)
@@ -34,15 +35,22 @@ import java.util.function.Consumer;
* @param <S> type of elements in the source list.
* @param <E> type of elements in this list.
*/
-final class DerivedList<S,E> extends AbstractList<E> {
+final class DerivedList<S,E> extends AbstractList<E> implements Serializable {
+ /**
+ * Serial number for inter-operability with different versions.
+ */
+ private static final long serialVersionUID = 5616103170191124327L;
+
/**
* The list of source elements.
*/
+ @SuppressWarnings("serial") // Not statically typed as
Serializable.
private final List<S> source;
/**
* The function for deriving an element in this list from an element in
the source list.
*/
+ @SuppressWarnings("serial")
private final Function<S,E> adapter;
/**
@@ -89,8 +97,18 @@ final class DerivedList<S,E> extends AbstractList<E> {
}
/**
- * An iterator over the elements in the source list,
- * converted on-the-fly to elements of type {@code <E>}.
+ * Returns an iterator over the elements in this list.
+ *
+ * @return a new iterator.
+ */
+ @Override
+ public Iterator<E> iterator() {
+ return new Iter<>(source.iterator(), adapter);
+ }
+
+ /**
+ * An iterator over the elements in the source list, converted on-the-fly
to elements of type {@code <E>}.
+ * Contrarily to {@link DerivedIterator}, this iterator does not skip null
elements.
*/
private static final class Iter<S,E> implements Iterator<E> {
/** The iterator over source elements. */
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/package-info.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/package-info.java
index fae84cb6a0..ab988b178c 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/package-info.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/package-info.java
@@ -51,7 +51,7 @@
* </ul>
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 0.3
*/
package org.apache.sis.util.collection;
diff --git a/netbeans-project/nbproject/project.xml
b/netbeans-project/nbproject/project.xml
index 558c4502b4..bf34c520bf 100644
--- a/netbeans-project/nbproject/project.xml
+++ b/netbeans-project/nbproject/project.xml
@@ -35,8 +35,10 @@
<word>geospatial</word>
<word>Molodensky</word>
<word>namespace</word>
+ <word>nullary</word>
<word>programmatically</word>
<word>transformative</word>
+ <word>unary</word>
<word>untiled</word>
</spellchecker-wordlist>
</configuration>